Observations
The Observation API reads the same tenant-scoped redacted evidence shown in the workspace console. It never returns raw request bodies, response bodies, headers, secrets, or unhashed business identifiers.
Authentication and scope
Use either a workspace API key carrying observations:read or the setup OAuth
access token managed by the Seamward CLI:
Code example
Authorization: Bearer $SEAMWARD_API_KEYThe CLI setup token requires seamward:setup:observations:read, is bound to one
workspace environment, and may read only an Integration approved in that setup
grant. Supply integrationId on list requests and as a query parameter on
detail requests when using setup OAuth. A missing or foreign binding returns
integration_scope_mismatch.
An ingest token can write observations but cannot read them. A workspace API key can read only its own workspace. A foreign observation id returns observation_not_found.
List observations
Code example
GET /v1/observationsSupported query parameters:
| Parameter | Meaning |
|---|---|
integrationId | One integration |
environmentId | One workspace environment |
eventType | Exact event type |
operationKey | Exact persisted operation identity key |
statusCode | Exact status from 0 through 599; v0.2 uses 0 or 100 through 599 |
retryOnly | true for attempts greater than one, false for first attempts |
hasFindings | true or false based on relational drift-finding evidence |
hasIncidents | true or false based on relational incident evidence |
from / to | ISO-8601 received-time range. from is inclusive and to is exclusive |
q | Case-insensitive search across id, event type, route, release, and commit |
limit | Page size from 1 to 100; default 25 |
cursor | Opaque nextCursor from the prior response |
page | Numbered page from 1 to 100000; cannot be combined with cursor |
sortBy | receivedAt or durationMs; defaults to receivedAt |
sortOrder | asc or desc; defaults to desc |
Use cursor for stable sequential API processing. Use page for interactive tables that need page numbers and a total row count. Cursor pagination supports the default receivedAt desc order. Numbered pagination supports both documented sort fields.
Code example
{ "observations": [ { "id": "evt_01J8ZQ4X2KD9", "envelopeVersion": "0.2", "integration": { "id": "int_orders", "name": "Order sync", "provider": "Acme ERP" }, "environmentId": "env_production", "occurredAt": "2026-08-21T11:59:59.000Z", "receivedAt": "2026-08-21T12:00:00.000Z", "direction": "outbound", "protocol": "http-api", "eventType": "order.created", "method": "POST", "routeTemplate": "/orders", "payloadLocation": "response", "statusCode": 202, "durationMs": 48, "attempt": 1, "operationIdentityKey": "op_v1_orders_create_response", "outcome": { "accepted": true, "businessObjectType": "order" }, "deployment": { "service": "orders-api", "release": "release-42", "commitSha": "edfe985ab2486420645673ef50e12491d498f379" }, "observedFingerprint": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "findingCount": 0, "incidentCount": 0, "state": "recorded" } ], "nextCursor": null, "pagination": null}When page is supplied, nextCursor is null and the response includes:
Code example
{ "pagination": { "page": 2, "pageSize": 25, "totalCount": 61, "totalPages": 3 }}The state is a review aid, not a billing rule. incident has highest priority, then finding. Status 0 is classified as transport_error before retry state; for responses, retry takes priority over transport_error. A non-retry rejected outcome is outcome_rejected; otherwise the state is recorded.
For v0.2, status 0 is the transport-failure sentinel: no protocol response was received. It is returned with state transport_error; a v0.2 envelope cannot combine status 0 with outcome.accepted: true.
Legacy v0.1 observations remain readable with their original 0 through 599 status range and legacy state semantics. New v0.2 collectors emit only 0 or an HTTP status from 100 through 599.
Get one observation
Code example
GET /v1/observations/{observationId}The response adds the complete redacted envelope plus linked finding and incident summaries. The envelope contains shape metadata, fingerprints, and hashes only. Links are relational, so evidence remains queryable when an incident is updated or a finding projection changes.
Get usage
Code example
GET /v1/observations/usageCode example
{ "usage": { "period": "utc_calendar_month", "periodStart": "2026-08-01T00:00:00.000Z", "periodEnd": "2026-09-01T00:00:00.000Z", "planCode": "developer", "monthlyLimit": 50000, "recordedCount": 125, "remainingCount": 49875, "usagePercent": 0.25, "evidenceRetentionDays": 7, "daily": [{ "date": "2026-08-21", "recordedCount": 125 }] }}One usage unit is one unique, successfully persisted redacted envelope. Healthy and unhealthy operations both count. An invalid rejected envelope does not count. Re-delivering the same eventId is idempotent and does not count again. A genuine retry with a new event id is a separate observation. Usage buckets remain after evidence rows expire under the workspace retention policy.
This route reports usage for the authenticated workspace. Its remainingCount is the difference between that workspace's count and its configured plan limit; it is not the remaining shared account allowance. Account administrators can read the pooled monthly total through GET /billing-accounts/{accountId}/usage with a browser session.
Read shadow usage by mode
The compatibility bridge adds a separate read endpoint:
Code example
GET /v1/observations/usage-by-modeUse a workspace API key with observations:read. Setup OAuth and ingest tokens cannot read these workspace aggregates.
Code example
curl --fail-with-body https://api.seamward.com/v1/observations/usage-by-mode \ -H "Authorization: Bearer $SEAMWARD_API_KEY"Code example
{ "usage": { "period": "utc_calendar_month", "periodStart": "2026-09-01T00:00:00.000Z", "periodEnd": "2026-10-01T00:00:00.000Z", "accounting": "shadow", "enforcement": "disabled", "enforcementScope": "sandbox", "startedAt": "2026-09-20T00:00:00.000Z", "coverage": "partial", "recordedCount": 10, "unclassifiedCount": 5, "modes": { "sandbox": { "recordedCount": 3 }, "live": { "recordedCount": 2 } } }}The compatibility counters measure accepted observations within this workspace. accounting: "shadow" retains the bridge response format. enforcement reports disabled, grace, or active from the account policy, and enforcementScope is always sandbox. This schedule cannot introduce Live quota rejection. Querying this endpoint does not change the policy. During the bridge, stored Development and Staging environments classify as Sandbox; stored Production classifies as Live. The authenticated connection determines the stored environment. A caller's mode flag, workspace name, or application hostname does not change classification.
recordedCount equals the two mode counts plus unclassifiedCount. The historical aggregate is frozen when accounting begins and remains unclassified. New mode counters are reconciled against that independent baseline, so a missing increment cannot be mistaken for old usage. startedAt is the accounting epoch, not the time of your first historical observation. coverage is not_started when no epoch exists, partial when the month is not fully classified, and complete when accounting covers the full month with no unclassified usage.
The counters survive evidence retention and connection deletion. They report the authenticated workspace only. Account-level remaining allowances require a separate account usage credential. This endpoint exposes no other workspace and reports no pooled remaining allowance. The console's Test mode switch selects existing testing or Live environments and shows a testing banner. Existing-workspace conversion still requires reviewed migration. Sandbox enforcement stays off until the published grace period ends. Live plan usage and remaining allowance are informational.
Errors
| Status | Body | Meaning |
|---|---|---|
400 | {"error":"invalid_observation_filter"} | A filter is malformed or outside its allowed range |
400 | {"error":"invalid_observation_cursor"} | The opaque pagination cursor is invalid |
401 | {"error":"unauthenticated"} | The key is missing, malformed, expired, or revoked |
403 | {"error":"insufficient_scope"} | The key lacks observations:read |
403 | {"error":"integration_scope_mismatch"} | Setup OAuth is missing the requested Integration |
404 | {"error":"observation_not_found"} | The observation is absent from this workspace |
404 | {"error":"workspace_not_found"} | The authenticated workspace no longer exists |
503 | {"error":"mode_usage_inconsistent"} | Shadow counters or their epoch do not reconcile |
503 | {"error":"mode_accounting_paused"} | Capture is paused pending reviewed reconciliation |
If mode_usage_inconsistent occurs, retain the response and contact support with the workspace and request time. Do not use an inconsistent response to change allowances. Retry transient failures; persistent disagreement requires an accounting investigation.
If mode_accounting_paused occurs, contact support. Mode counts remain unavailable until capture and accounting have been reviewed. The existing GET /v1/observations/usage route continues reporting the original workspace ledger.
Retention and billing
Evidence retention and usage accounting are separate. Expiring an old observation removes its detail row and evidence links, but does not reduce the historical usage total for that billing period. Seamward does not automatically charge an overage when the displayed plan limit is reached.
Next steps
- Observation envelope: the exact redacted wire contract.
- Ingest observations: write evidence with idempotent event ids.
- Observation concept: how evidence feeds drift, outcomes, and incidents.
