API reference
Ingest: POST /v1/ingest
Content-Type: application/errorcore+json. This is the only production ingest contract; there is no plaintext JSON alternative.
Headers
| Header | Meaning |
|---|---|
Authorization: Bearer ec_live_… | Ingestion API key. The sole source of organization, project, and environment scope. |
X-Errorcore-Event-Id | Routing hint. Must equal the envelope eventId. |
X-Errorcore-Key-Id | Routing hint. Must equal the envelope keyId. |
X-Errorcore-Payload-Kind | Routing hint. Must equal the envelope kind. |
Body
{
"v": 2,
"eventId": "<uuid>",
"kind": "error",
"sdk": { "name": "errorcore", "version": "<semver>" },
"keyId": "<16 hex chars>",
"iv": "<base64>",
"ciphertext": "<base64>",
"authTag": "<base64>",
"hmac": "<base64>",
"compressed": false,
"producedAt": "<ISO 8601>"
}kind is error or payload_blob. blobId is required when kind is payload_blob and absent otherwise; for a blob, eventId is the parent error's id.
The ciphertext decrypts to an SDK ErrorPackage. Accepted inner schemaVersion values are 1.1.0, 1.2.0, and 1.3.0; the current SDK emits 1.3.0. The inner eventId must equal the envelope eventId: a mismatch is quarantined, never silently accepted.
Cryptography
- Encryption key = HKDF-SHA256 of your DEK; MAC key derived the same way unless self-managed.
keyId= first 8 bytes ofsha256(derived encryption key), hex.- AES-256-GCM with AAD
"2|" + keyId + "|" + sdkVersion + "|" + eventId + "|" + kind + "|" + (blobId ?? ""). - Outer
hmac= HMAC-SHA256 overiv ‖ ciphertext ‖ authTag ‖ AAD, verified timing-safe before any decryption. - Plaintext larger than 8 KiB is raw-deflate compressed before encryption; inflation is bounded on both sides.
Order of checks
- Authenticate the API key → tenant scope. Otherwise
401. - Enforce content type (
415), streaming body cap (413), bounded JSON parse. - Validate envelope shape and header/envelope agreement (
422). - Resolve the project encryption key by scope and
keyId. Unknown or retired →403, zero quota. - Verify the outer HMAC. Failure →
403, zero quota. - Rate limit →
429withRetry-After, zero quota, nothing persisted. - Admission: reserve exactly one usage unit atomically, or reject
403plan_limit. - Store the envelope bytes as received, enqueue the job, return
202.
Responses
| Status | Meaning | Consumes allowance | SDK retries |
|---|---|---|---|
202 | Accepted. Envelope, admission, and job are committed. | one unit for a new kind=error | n/a |
202 with duplicate: true | Replay of an existing receipt for the same identity and digest. | no | n/a |
401 | API key missing, unknown, or revoked. | no | no |
403 plan_limit | The organization's allowance is used up. | no | no |
403 envelope auth | Unknown or retired keyId, or HMAC verification failed. | no | no |
409 | Same event identity, different content digest. | no | no |
413 | Body exceeds the configured cap. | no | no |
415 | Wrong content type. | no | no |
422 | Envelope failed shape validation. | no | no |
429 | Rate limited. | no | yes |
500, 502, 503, 504 | Transient server or dependency failure. | no | yes |
403 plan_limit and 429 are deliberately distinct. A plan limit means the allowance is exhausted and retrying cannot help; upgrade or wait for the period reset. Rate limiting means slow down; the SDK retries with jitter and honors Retry-After. See billing and usage.
A 202 means the event was durably admitted, not that reconstruction finished. Idempotency identity is (scope, eventId, kind, blobId?); retry an unknown outcome with the same eventId.
Console endpoints
The browser calls authenticated same-origin endpoints. Server handlers derive the user and active organization from the session, validate the selected project and environment against that organization, then call the errorcore backend. A caller-supplied organization id is never accepted.
GET /api/console/workspaces: projects authorized for the active organization.GET /api/console/bootstrap?projectId=…&environment=…: org account snapshot and overview state.GET /api/console/search?projectId=…&environment=…&q=…: at most 20 case and event results.GET /api/console/setup/first-event?projectId=…&environment=…: latest setup stage.PATCH /api/console/cases/{caseId}/status?projectId=…&environment=…: persists a case state.POST /api/console/dlq/{failureId}/retry?projectId=…&environment=…: queues an admin-authorized retry.
Mutations require an Idempotency-Key. Responses use private, no-store caching. Errors are returned as safe categories (authentication, authorization, invalid request, conflict, rate limited, timeout, unavailable, upstream contract) without backend detail.
Backend boundary
Every console request is scoped beneath:
/v1/console/organizations/{authenticatedOrgId}/projects/{projectId}/environments/{environment}Organization-level operations (account, provisioning, project creation, billing sessions) sit directly under the organization root. Every response DTO is validated before it is rendered.
Credential plaintext is derived from a secret-manager CSPRNG secret and a tenant-scoped operation nonce, then shown once. Retrying the same idempotency key derives the same value and cannot create a second credential; different tenants, workspaces, targets, and operation keys derive unrelated values. The backend marks replayed mutations, and replayed responses never reveal plaintext; a lost first reveal requires rotation or reissue. Only a keyed digest, prefix, and last four characters cross the persistence boundary, and the returned mask must match before plaintext is revealed.
Decrypted event payloads are never returned by console endpoints.