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.

Account management can also use CLI OAuth with X-Seamward-Workspace. Its current account membership and explicit permission scopes are checked separately from SDK ingestion and project setup. Credential issuance, rotation and recovery are unavailable to CLI OAuth. Observation reads require an exact integrationId; contract paths select their exact connection.

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_KEY

The 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/observations

Supported query parameters:

ParameterMeaning
integrationIdOne integration
environmentIdOne workspace environment
eventTypeExact event type
operationKeyExact persisted operation identity key
statusCodeExact status from 0 through 599; v0.2 uses 0 or 100 through 599
retryOnlytrue for attempts greater than one, false for first attempts
hasFindingstrue or false based on relational drift-finding evidence
hasIncidentstrue or false based on relational incident evidence
from / toISO-8601 received-time range. from is inclusive and to is exclusive
qCase-insensitive search across id, event type, route, release, and commit
limitPage size from 1 to 100; default 25
cursorOpaque nextCursor from the prior response
pageNumbered page from 1 to 100000; cannot be combined with cursor
sortByreceivedAt or durationMs; defaults to receivedAt
sortOrderasc 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",      "displayRouteTemplate": null,      "payloadLocation": "response",      "statusCode": 202,      "statusState": "known",      "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  }}

Two fields describe how an operation and its status read:

FieldTypeMeaning
displayRouteTemplatestring or nullThe route with named parameters, such as /orders/:orderId, when the collector recorded one. null for static routes and for observations recorded before envelope 0.4. routeTemplate remains the grouping identity
statusStateenumknown, no-response, or unknown. Always present. For observations recorded before envelope 0.4 it is derived: status 0 is no-response, anything else is known

For a 0.4 observation of GET /orders/{} whose status could not be read, the same fields are:

Code example

{  "envelopeVersion": "0.4",  "method": "GET",  "routeTemplate": "/orders/{}",  "displayRouteTemplate": "/orders/:orderId",  "statusCode": 0,  "statusState": "unknown",  "state": "recorded"}

The state is a review aid, not a billing rule. incident has highest priority, then finding. A no-response status 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.

Status 0 means no HTTP status is known. With statusState: "no-response", no response was received: the state is transport_error, and the envelope cannot carry outcome.accepted: true. With statusState: "unknown", a response may exist but its status could not be read: the observation is not a transport error and falls through to the remaining states. See Status state.

Legacy v0.1 observations remain readable with their original 0 through 599 status range and legacy state semantics; their statusState is always known. Collectors from v0.2 onward 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/usage

Code 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 compatibility route reports the authenticated workspace's all-mode ledger, regardless of the mode selected in the console. The console uses classified mode counts and account references from the mode usage read below. 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-mode

Use 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 is limited only on Developer, which stops recording Live observations at the account's monthly allowance; Pro and Enterprise keep recording.

Include allowance and retention references

Use includeReference=true to include the shared account allowances beside this workspace's mode counts. The default response remains unchanged. This request uses the same workspace API key and observations:read scope.

Code example

curl --fail-with-body 'https://api.seamward.com/v1/observations/usage-by-mode?includeReference=true' \  -H "Authorization: Bearer $SEAMWARD_API_KEY"

The response keeps the fields above and adds usage.reference:

Code example

{  "scope": "account",  "sandboxMonthlyLimit": 10000,  "liveMonthlyLimit": 50000,  "liveRetentionDays": 30,  "sandboxRetentionDays": 7,  "sandboxRetentionCutoverAt": null}
FieldMeaning
scopeaccount: each allowance is shared across that account's workspaces.
sandboxMonthlyLimitPublished monthly Sandbox allowance, default 10,000.
liveMonthlyLimitPublished account plan allowance for Live.
liveRetentionDaysCurrent workspace evidence retention setting.
sandboxRetentionDaysProspective Sandbox retention, default 7 days.
sandboxRetentionCutoverAtVerified cutover time for new Sandbox evidence, or null before activation. Earlier evidence keeps its existing retention policy.

includeReference is optional and accepts only the strings true and false; omitting it or using false returns the original response. Repeated or invalid values return 400 mode_usage_query_invalid.

The Observations page shows this workspace's classified Test or Live count as a contribution to the shared allowance. That contribution is not the account's total usage or remaining quota. It includes all connections in the selected mode and ignores list filters. Partial accounting is labelled incomplete; unclassified history is shown separately and belongs to account Live usage. If accounting is paused or inconsistent, mode usage is unavailable while readable observation records remain visible. Account owners and billing administrators can review pooled totals on Account usage and API keys or through the account mode usage API.

Errors

StatusBodyMeaning
400{"error":"mode_usage_query_invalid"}Invalid or repeated includeReference
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