Workspace API
A workspace-scoped HTTP API for automation. Integration inventory, observation evidence reads, usage, contract lifecycle, and explicit mode-scoped incident and repair provenance reads are available.
Choose a mode and scope
Incident and repair reads require an explicit mode=sandbox or mode=live.
Use incidents:read for incident reads and repairs:read for proposal
provenance. Existing observation keys do not acquire either permission.
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.
Status
Integration inventory is available below. Observation list, detail, and usage endpoints are available through the Observation API. Contract registration, inspection, and activation are available through the Contract API. Workspace API keys are hashed, expiring, revocable credentials with explicit scopes. Browser session cookies do not authenticate /v1 routes.
Console session workspace management
These routes serve the signed-in console and require an authenticated, email-verified browser session. They are separate from the workspace API key routes under /v1. A workspace API key cannot create another workspace. Public automation for account-level creation is not available.
| Method and path | Result |
|---|---|
GET /workspaces | Lists every workspace membership of the signed-in user, independent of plan limits. Includes archived memberships and canManageLifecycle for account administrators who also own the workspace. |
POST /workspaces | Creates a workspace and owner membership under an account the user owns or administers. If the user has no account, creates their first Developer account. |
GET /billing-accounts | Lists accounts the user owns or administers, with the active workspace count. |
GET /billing-accounts/{accountId}/usage | Returns the account's UTC calendar-month observation total, limit, remaining count, and per-workspace contributions. |
POST /workspaces/{workspaceSlug}/archive | Archives a workspace owned by the account. New ingestion and customer writes stop. |
POST /workspaces/{workspaceSlug}/restore | Restores an archived workspace if the current plan permits it. |
Create with Content-Type: application/json and a body such as:
Code example
{ "name": "Hired by Code", "billingAccountId": "ba_example" }name must contain 2 to 80 characters. billingAccountId is required when the user administers more than one account. An optional lowercase slug can be supplied; otherwise the service derives one from the name. Success returns 201 with the workspace summary:
Code example
{ "workspace": { "id": "org_example", "tenantId": "ten_example", "name": "Hired by Code", "slug": "hired-by-code", "role": "owner", "active": false, "environments": [ { "id": "env_sandbox", "name": "Sandbox", "slug": "sandbox", "category": "sandbox", "isDefault": true }, { "id": "env_live", "name": "Live", "slug": "live", "category": "live", "isDefault": false } ] }}The console activates the workspace after the create request commits, so a failed activation does not undo the workspace.
Creation errors use stable {"error":"code"} bodies: unauthenticated (401); email_verification_required (403); account_not_found (404); invalid_workspace_name, invalid_workspace_slug, invalid_billing_account_id, account_required (400); account_unavailable, workspace_limit_reached, workspace_slug_unavailable (409); and workspace_creation_rate_limited (429, with Retry-After). The Developer limit is three active workspaces per account. The server checks the account role and limit inside the creation transaction; it never accepts a caller-supplied owner role, tenant ID, or plan.
Archival preserves evidence and audit history. The restore route checks the current account entitlement. A workspace owner who also owns or administers its account can archive it in workspace Settings and restore it from the switcher or the no-workspace recovery screen. Both routes return {"workspace":{"slug":"...","status":"archived"}} or status:"active" on success. A missing workspace or insufficient permission returns workspace_not_found (404); a restore above the Developer cap returns workspace_limit_reached (409); an unavailable account returns account_unavailable (409).
Console session advisories, connection health and recovery
These routes serve the signed-in console for one workspace. They require the same authenticated, email-verified browser session as the routes above, and the member needs the listed permission in that workspace. A workspace API key does not authenticate them. For automation, the CLI reads advisories, affected records, the recovery plan and the impact summary with account OAuth.
| Method and path | Permission | Result | Success |
|---|---|---|---|
GET /workspaces/{workspaceSlug}/advisories | incidents:read | Lists up to 100 advisories, most urgent first, with a count per status. | 200 |
POST /workspaces/{workspaceSlug}/advisories/{advisoryId}/status | incidents:manage | Acknowledges, dismisses or reopens an advisory, with an optional note. Audited. | 200 |
GET /workspaces/{workspaceSlug}/integrations/{integrationId}/provider-notices | integrations:read | Returns the API versions a connection is served under and each operation's deprecation notice. | 200 |
GET /workspaces/{workspaceSlug}/integrations/{integrationId}/contract-source | integrations:read | Returns the published contract watched for a connection, or null. | 200 |
PUT /workspaces/{workspaceSlug}/integrations/{integrationId}/contract-source | contracts:write | Sets the address of the provider's OpenAPI document and queues its first check. Audited. | 200 |
DELETE /workspaces/{workspaceSlug}/integrations/{integrationId}/contract-source | contracts:write | Stops watching the published contract. Audited. | 204 |
GET /workspaces/{workspaceSlug}/integrations/{integrationId}/health | integrations:read | Returns declared credential expiry dates, the tightest rate limit of the last 24 hours, and webhook signature results. | 200 |
POST /workspaces/{workspaceSlug}/integrations/{integrationId}/provider-credentials | integrations:manage | Records when a credential expires: a kind, a name and a date. Audited. | 201 |
PATCH /workspaces/{workspaceSlug}/integrations/{integrationId}/provider-credentials/{credentialId} | integrations:manage | Changes the name, date or warning period. Audited. | 200 |
DELETE /workspaces/{workspaceSlug}/integrations/{integrationId}/provider-credentials/{credentialId} | integrations:manage | Removes a recorded expiry date. Audited. | 204 |
GET /workspaces/{workspaceSlug}/incidents/{incidentId}/affected-records | incidents:read | Pages through an incident's affected records by hashed identifier, with each one's recovery status. Audited. | 200 |
GET /workspaces/{workspaceSlug}/incidents/{incidentId}/recovery-plan | incidents:read | Returns the records still missing, in the order to request them again. Audited. | 200 |
GET /workspaces/{workspaceSlug}/incidents/{incidentId}/impact | incidents:read | Returns a plain summary built only from counted facts, and affected accounts by hash. | 200 |
GET /workspaces/{workspaceSlug}/incidents/{incidentId}/recovery-plans | incidents:read | Lists the recovery plans handed out for an incident, newest first, with how many of each plan's events have recovered. | 200 |
GET /workspaces/{workspaceSlug}/incidents/{incidentId}/recovery-plans/{planId} | incidents:read | Returns one kept recovery plan with each event's status: pending, recovered or unknown. | 200 |
GET /workspaces/{workspaceSlug}/incidents/{incidentId}/report | incidents:read | Returns the incident report: timeline, scope, validation, approval, delivery and recovery, as a document and as Markdown. Audited. | 200 |
Errors use stable {"error":"code"} bodies. A workspace the member does not belong to returns workspace_not_found (404), a missing permission returns forbidden (403), and a record of another workspace returns the same 404 as one that does not exist. Unknown body fields are refused on every write.
Advisories
An advisory is something a provider has announced, or a date your team declared, that has not broken anything yet. GET /workspaces/{workspaceSlug}/advisories takes three optional filters:
| Query | Values | Default |
|---|---|---|
status | active (open and acknowledged), open, acknowledged, dismissed, withdrawn, all | active |
environmentId | One workspace environment, such as env_live | All |
integrationId | One connection | All |
counts follows the environment and connection filters and ignores status, so it can label every status at once. detail depends on kind: upcoming-deprecation carries the operation and its dates, upstream-contract-change carries up to 20 changes from the provider's published document, and credential-expiring carries the kind, name and date your team recorded.
Code example
{ "advisories": [ { "id": "adv_example", "kind": "upcoming-deprecation", "status": "open", "urgency": "scheduled", "summary": "The provider has announced an end date for GET /v1/messages/{}.", "effectiveAt": "2027-03-01T00:00:00.000Z", "firstSeenAt": "2026-10-01T09:12:00.000Z", "lastSeenAt": "2026-10-04T11:58:00.000Z", "statusChangedAt": null, "statusNote": null, "integration": { "id": "int_example", "name": "Notifications API", "provider": "Example Provider", "environmentId": "env_live" }, "detail": { "method": "GET", "routeTemplate": "/v1/messages/{}", "displayRouteTemplate": "/v1/messages/:messageId", "deprecated": true, "deprecatedAt": "2026-09-01T00:00:00.000Z", "sunsetAt": "2027-03-01T00:00:00.000Z", "apiVersion": "2026-06" } } ], "counts": { "open": 1, "acknowledged": 0, "dismissed": 0, "withdrawn": 0 }}urgency is passed, imminent, scheduled or undated. A decision is one of open, acknowledged or dismissed, with an optional note of up to 500 characters. It returns {"advisory": {...}} in the shape above and writes an audit event with the member who decided.
Code example
{ "status": "acknowledged", "note": "Migration planned for January" }Seamward withdraws an advisory itself when its cause goes away, for example when a recorded expiry date is removed. A withdrawn advisory cannot be decided on.
| Status | Error | Recovery |
|---|---|---|
| 400 | invalid_advisory_filter | Use a listed status and a connection ID that starts with int_. |
| 400 | invalid_environment_filter | Use an environment ID of this workspace. |
| 400 | invalid_advisory_status | Send open, acknowledged or dismissed, and a note of 500 characters or fewer. |
| 404 | advisory_not_found | Refresh the list. |
| 409 | advisory_withdrawn | Nothing to decide. The cause is gone. |
Provider notices and the watched contract
GET .../provider-notices returns what the provider's own response headers said about each operation a connection calls: apiVersions, earliestSunsetAt, and up to 200 operations, each with method, routeTemplate, displayRouteTemplate, deprecated, deprecatedAt, sunsetAt, apiVersion and lastObservedAt.
PUT .../contract-source sets the address of the provider's OpenAPI 3 document in JSON. Seamward fetches it about once a day and raises an advisory when a new version changes an operation the connection calls. The address must be public HTTPS with no credentials, query string or fragment, because it is stored and shown as typed.
Code example
{ "url": "https://docs.provider.example/openapi.json" }Both GET and PUT return {"source": {...}} with id, url, status (pending, ok or failing), lastErrorCode, lastCheckedAt, nextCheckAt, lastChangedAt, documentVersion, operationCount and createdAt. GET returns {"source": null} when nothing is watched.
| Status | Error | Recovery |
|---|---|---|
| 400 | invalid_contract_source | Send only url, as a public HTTPS address with no query string. |
| 404 | integration_not_found | Refresh the connection list. |
| 404 | contract_source_not_found | Nothing was being watched. |
| 409 | contract_source_protocol_unsupported | Only HTTP API and HTTP webhook connections have an OpenAPI contract. |
Connection health and credential dates
GET .../health returns the expiry dates your team recorded, the tightest rate limit the provider advertised in the last 24 hours, and the webhook signature results your collector reported in the same period. quota and signatures are null when no traffic carried them.
Code example
{ "integrationId": "int_example", "credentials": [ { "id": "ccm_example", "kind": "signing-secret", "label": "Webhook signing secret", "expiresAt": "2026-11-01T00:00:00.000Z", "warnDays": 30, "createdAt": "2026-10-01T09:00:00.000Z", "updatedAt": "2026-10-01T09:00:00.000Z" } ], "quota": { "limit": 5000, "lowestRemaining": 600, "headroomPercent": 12, "samples": 240, "lastObservedAt": "2026-10-04T11:58:00.000Z" }, "signatures": { "checked": 180, "failed": 0 }}A credential entry is a kind, a name and a date. There is no field for the credential itself, and a name that looks like a token is refused. Seamward does not store credentials.
Code example
{ "kind": "signing-secret", "label": "Webhook signing secret", "expiresAt": "2026-11-01", "warnDays": 30}| Field | Required | Meaning |
|---|---|---|
kind | Yes | provider-token, signing-secret, certificate, api-key or other. |
label | Yes | A name of 1 to 80 characters, unique per kind on the connection. |
expiresAt | Yes | A calendar day (2026-11-01) or a UTC date-time. |
warnDays | No | How many days ahead to raise the advisory, 1 to 365. Defaults to 30. |
POST returns 201 with {"credential": {...}}. PATCH accepts any of label, expiresAt and warnDays; the kind cannot change. A connection holds at most 20 entries.
| Status | Error | Recovery |
|---|---|---|
| 400 | invalid_credential_metadata | Send only the fields above. Do not send the credential. |
| 404 | integration_not_found | Refresh the connection list. |
| 404 | credential_metadata_not_found | Refresh connection health. |
| 409 | credential_metadata_exists | That kind and name is already recorded. Change its date instead. |
| 409 | credential_metadata_limit_reached | Remove an entry that is no longer needed. |
Recovery
The three incident routes return the same documents as the CLI: affected-records is a seamward.affected-records/1 page, recovery-plan is a seamward.recovery-plan/1 document, and impact is the summary below. Records and accounts are identified by hash only. Recovery explains how to match the hashes to your own identifiers and run the plan in your infrastructure.
affected-records accepts limit (1 to 1000), cursor (the previous page's nextCursor) and status (missing or recovered, or affected or cleared for an incident about duplicated or out-of-order records, which has an empty recovery plan). Reading it or the plan writes an audit event.
Code example
{ "incidentId": "inc_example", "summary": "Applications API: expected records were not created after events that were accepted. First seen on 3 October 2026 at 08:10 UTC, last seen on 3 October 2026 at 09:40 UTC. 42 records are affected across 2 of your accounts. 30 have been recovered and 12 are still missing. The incident is open.", "facts": { "connectionName": "Applications API", "kind": "missing-business-outcome", "status": "open", "firstObservedAt": "2026-10-03T08:10:00.000Z", "lastObservedAt": "2026-10-03T09:40:00.000Z", "affectedRecords": 42, "recoveredRecords": 30, "affectedAccounts": 2 }, "accounts": [ { "accountRefHash": "sha256:6f1c2b0d9a4e7f3c8b5a1d2e0f9c4b7a6e3d8c1f5b2a9e0d7c4f1b8a5e2d9c6f", "affected": 30, "missing": 12 } ], "accountsTruncated": false}report returns {"report": {...}, "markdown": "..."}. report is a seamward.incident-report/1 document: the incident, its connection and rule, the scope counts, a timeline in time order (first evidence, the incident opening, each replay dataset and finished run, each repair proposal, its validation and decision, each delivery, the observed deployment, verification and resolution), then validation, approval, delivery and recovery. It is assembled from rows that already exist and adds no interpretation. A decision is recorded by role, never by name. markdown is the same report as a document your team can file.
A recovery-plan read is kept under the planId it returns, so that plan's progress can be read later. Reading the same set of missing events again returns the same planId; a plan with no items has planId: null and is not kept. recovery-plans lists an incident's kept plans with totals (items, recovered, pending), and recovery-plans/{planId} lists one plan's items with a status of pending, recovered or unknown (the evidence is no longer held). Status is not stored: it is what reconciliation has observed for each record. An unknown plan returns recovery_plan_not_found (404).
summary is built only from the counted facts. affectedAccounts is null when the collector sends no account reference.
| Status | Error | Recovery |
|---|---|---|
| 400 | invalid_affected_records_query | Use the listed query fields and an unmodified nextCursor. |
| 404 | incident_not_found | Refresh the incident list. |
Read one workflow
GET /v1/integrations/{workspaceIntegrationId} requires integrations:read. Pass the shared wint_ workflow ID. The response contains model: "canonical" and an integration with the same fields and independent Sandbox and Live slots as canonical inventory. A not_configured slot has no connection ID, credential state, or observations. It is available for separately reviewed connection setup.
Code example
curl 'https://api.seamward.com/v1/integrations/wint_example?include=sourceCode' \ -H "Authorization: Bearer $SEAMWARD_API_KEY"Code example
{ "model": "canonical", "integration": { "id": "wint_example", "name": "Candidate events", "provider": "ATS", "direction": "inbound", "protocol": "http-webhook", "createdAt": "2026-09-28T12:00:00.000Z", "revision": "1", "sourceCode": { "status": "not_connected" }, "connections": { "sandbox": { "status": "configured", "mode": "sandbox", "environmentId": "env_sandbox", "connectionId": "int_example", "revision": "1", "credentialStatus": "active", "activeContractVersionId": null, "latestObservationAt": null, "openIncidentCount": 0, "observationState": "waiting_for_observation" }, "live": { "status": "not_configured", "mode": "live", "environmentId": "env_live" } } }}The response is private and uncached. Revisions are opaque decimal strings, not edit counts. The endpoint returns invalid_inventory_query (400, unsupported query or include value), unauthenticated (401), insufficient_scope (403), integration_not_found (404, including foreign identities and runtime int_ IDs), or workspace_modes_unavailable (409). Legacy topology retains the compatibility inventory until migration.
Read one connection
GET /v1/connections/{connectionId} requires a workspace API key with
integrations:read. Use the runtime int_ connection ID returned by creation or
inventory, rather than the shared wint_ workflow ID.
Code example
curl 'https://api.seamward.com/v1/connections/int_example_live' \ -H "Authorization: Bearer $SEAMWARD_API_KEY"Code example
{ "id": "int_example_live", "workspaceIntegrationId": "wint_example", "name": "Candidate events", "provider": "ATS", "direction": "inbound", "protocol": "http-webhook", "environment": { "id": "env_live", "category": "live", "mode": "live" }, "revision": "12", "credentialStatus": "active", "activeContractVersionId": null}The server returns the exact stored environment identifier. Legacy identifiers
remain exact targets; their classified mode grants no additional access. The
monotonic revision changes with connection configuration. credentialStatus
is not_issued, active, or revoked; it describes credential lifecycle and
does not prove application traffic. The response contains no token, token hash,
or private key. It is never cached.
Errors: unauthenticated (401), insufficient_scope (403),
connection_not_found (404, including another workspace's connection), and
workspace_modes_unavailable (409). Refresh inventory to select a current
connection after a 404. Resolve unsupported topology before retrying a 409.
Local MCP setup using a raw workspace API key requires this discovery endpoint before recording remote setup evidence. A missing endpoint, mismatched connection ID, or unsupported mode fails closed. Upgrade the server and renew the setup review before retrying. A mode or connection-key change after review requires a new review.
Connection credential lifecycle
These workspace API endpoints manage only the selected canonical Sandbox or
Live connection. Every endpoint requires integrations:manage, including
status and verification. Use a runtime int_ connection ID. A shared wint_
workflow ID is not a connection. Existing legacy connections keep their console
credential flow until reviewed migration.
| Operation | Endpoint |
|---|---|
| Current status | GET /v1/connections/{connectionId}/credentials |
| Issue a token | POST /v1/connections/{connectionId}/credentials/issue |
| Rotate a token | POST /v1/connections/{connectionId}/credentials/rotate |
| Revoke a token | POST /v1/connections/{connectionId}/credentials/revoke |
| Verify credentials | POST /v1/connections/{connectionId}/credentials/verify |
| Recover a receipt | GET /v1/credential-operations/{operationId} |
Read current status before reviewing a change:
Code example
curl 'https://api.seamward.com/v1/connections/int_example/credentials' \ -H "Authorization: Bearer $SEAMWARD_API_KEY"Code example
{ "credential": { "connectionId": "int_example", "environmentId": "env_sandbox", "mode": "sandbox", "revision": "1", "status": "active", "issuedAt": "2026-09-28T12:00:00.000Z", "revokedAt": null }}status is not_issued, active, or revoked. The revision is an opaque
string covering connection configuration, including credential changes. A
status read contains no token or hash and does not prove application traffic.
Issue, rotate, and revoke require a JSON body containing only
expectedRevision and an Idempotency-Key with 8 to 128 printable non-space
ASCII characters. Retain both for recovery. Issue rejects an active credential;
rotation is explicit and rejects a connection that has never had a credential.
Revoking an already revoked or unissued connection records an unchanged receipt.
Code example
curl 'https://api.seamward.com/v1/connections/int_example/credentials/rotate' \ -H "Authorization: Bearer $SEAMWARD_API_KEY" \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: candidate-sandbox-rotate-001' \ -d '{"expectedRevision":"1"}'A first successful operation returns 201, operationId, action, the
committed credential state, connectionKey, replayed:false,
historical:false, and credentialRecoveryRequired:false. Issue and rotate
also return ingestToken exactly once. Store it securely. Revocation returns
no token. Rotation replaces the old token immediately; revocation stops writes
using that credential. The other mode's credentials and evidence are unaffected.
Retry the identical request with its original key and authorization. Success
returns 200, the original receipt, and replayed:true, without a token.
credentialRecoveryRequired:true on issue or rotation means the receipt cannot
recover the token. Read current status and review a separate rotation if the
token was lost. Never automatically retry a stale change with a newer revision.
Read a receipt through /v1/credential-operations/{operationId} under the same
workspace API key that performed the operation. It survives connection deletion
and subsequent configuration changes. historical:true means the connection no
longer has the receipt's recorded revision or has been deleted. A receipt is
evidence of the original operation, not a current health or credential verdict.
Replaying a receipt never recreates a connection or changes a token.
Verification accepts only connectionKey and ingestToken in its JSON body:
Code example
{ "connectionKey": "sw_conn_v1.K7mP4xQ9vT2nW6cR.example", "ingestToken": "sw_ing_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}These are synthetic placeholders. Supply your private token through a secret
store and avoid request-body logging. Verification returns 200,
status:"connection_verified", current credential, and
applicationVerified:false. It writes no observation, issues no token, and
does not start the application. A mismatched or revoked token returns 409.
For a collector's signed, token-only check, use
Check collector connection.
All responses use Cache-Control:no-store. Current workspace and key
authorization are checked inside the transaction and again after credential,
receipt, and audit writes before commit. A key expiring during the operation,
permission loss, or a changed authorization epoch rolls back all three writes.
An authorization change later restored still invalidates the operation.
Receipts and audit metadata never contain plaintext ingest tokens.
Canonical connections reject the legacy browser credential mutation routes
with 409 and canonical_credential_required. Use the reviewed connection
credential endpoints above. The compatibility status and Connection key reads
recheck current owner or admin access and return no token. These browser routes
do not accept a workspace API key. A request key already used by legacy
credentials, setup, deletion, or another canonical operation returns
idempotency_conflict; recovery must retain the original namespace and request.
| Status | Error | Recovery |
|---|---|---|
| 400 | invalid_credential_request | Correct the body. Unknown fields and caller-supplied tenant or mode are rejected. |
| 400 | idempotency_key_required | Retain a valid key for the mutation and its identical retries. |
| 401 | unauthenticated | Authorize with a current workspace key for an active workspace. |
| 403 | insufficient_scope | Use the original key with current integrations:manage authorization. A key revoked or expired before or during the transaction also returns this error. |
| 404 | connection_not_found | Refresh inventory. Foreign workspace IDs are also undisclosed. |
| 404 | operation_not_found | Use the original key and operation ID. Other keys cannot recover the receipt. |
| 409 | workspace_unavailable | Restore the workspace or resolve its organization binding before a new operation. |
| 409 | workspace_modes_unavailable | Resolve canonical migration before using these endpoints. |
| 409 | connection_configuration_changed | Configuration or authority changed. No token change commits. Read and review current state before creating a new operation. |
| 409 | credential_already_issued | Review rotation explicitly rather than issuing over an active credential. |
| 409 | credential_not_issued | Issue a credential before attempting rotation. |
| 409 | credential_verification_failed | Check the exact Connection key and current private token. |
| 409 | idempotency_conflict | Restore the original action, body, and authorization, or deliberately start a new reviewed operation. |
Create an Integration and first connection
Code example
POST /v1/integrationsCreate a new workflow with its Sandbox and Live connections atomically. Select
sandbox or live explicitly: that mode's connection is returned with its
one-time ingest token. The other mode's connection is created at the same time
with no credential of its own and appears in the
Integration inventory. A workspace API key needs
integrations:manage. Connections with the same name remain independent
workflows.
Code example
curl 'https://api.seamward.com/v1/integrations' \ -H "Authorization: Bearer $SEAMWARD_API_KEY" \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: candidate-events-create-001' \ -d '{"mode":"sandbox","name":"Candidate events","provider":"ATS","direction":"inbound","protocol":"http-webhook"}'| Field | Required | Meaning |
|---|---|---|
mode | Yes | sandbox or live; there is no default or legacy alias. |
name | Yes | Workflow name, trimmed to 1 to 120 characters. |
provider | Yes | Provider identity, trimmed to 1 to 120 characters. |
direction | Yes | inbound or outbound. |
protocol | Yes | http-api, http-webhook, scheduled-feed, or queue. |
Unknown fields are rejected. Workspace and organization come from the key. An environment identifier, tenant identifier, or existing Integration identifier cannot override the authenticated target.
Success returns 201, replayed:false, and credentialRecoveryRequired:false.
integration.id is the shared workflow ID; integration.connectionId is the
runtime connection ID. integration.mode and integration.environmentId identify
the selected mode. collectorConfiguration.connectionKey and
collectorConfiguration.ingestToken configure that connection. Store the token
securely when it is returned. No contract is activated and
connectionState:"waiting_for_observation" does not prove traffic has arrived.
Retry an identical request using the same key and the original authorization.
The result is 200, replayed:true, and credentialRecoveryRequired:true.
The response includes the original Integration, operation ID, and connection
key, but never the token. A lost token requires a separately approved credential
rotation through the connection credential API.
The console provides credential controls and receipt recovery. A retry does not recreate a deleted connection; the
receipt describes the original operation. Use a new idempotency key only when
intentionally creating another workflow.
Read a saved receipt without retrying creation:
Code example
GET /v1/integration-operations/{operationId}Code example
curl 'https://api.seamward.com/v1/integration-operations/connop_example' \ -H "Authorization: Bearer $SEAMWARD_API_KEY"This GET requires integrations:manage on the same key that created the
operation. It returns the credential-free replay shape above. A malformed,
missing, or differently authorized operation returns operation_not_found
(404), without disclosing the other workflow. Missing authorization returns
401 and missing management scope returns 403. Current authority is revalidated
inside the receipt transaction; archival or rebinding during an admitted request
returns workspace_unavailable (409). A key presented after archival fails
public authentication with 401. A saved receipt remains historical after the
connection is deleted.
Both responses use Cache-Control:no-store. Receipts and audit metadata contain
no plaintext ingest token. Revoked credentials, expired credentials, lost
management scope, and archived workspaces cannot execute the transaction.
Creation rechecks matching current authority after identity, connection, receipt
and audit writes before commit. Expiry or permission loss rolls everything
back. workspace_authorization_changed means the authority epoch changed during
creation, even if its permissions were later restored. Review current access
and deliberately start a new operation.
| Status | Code | Recovery |
|---|---|---|
| 400 | invalid_integration | Correct the body and use a canonical mode. |
| 400 | idempotency_key_required | Supply 8 to 128 printable non-space ASCII characters. |
| 401 | unauthenticated | Authorize with a valid, unexpired workspace API key. |
| 403 | insufficient_scope | Issue a key with integrations:manage; retry only with the original binding. |
| 409 | workspace_authorization_changed | Refresh and review current permissions before a new creation attempt. |
| 409 | workspace_modes_unavailable | Ask the workspace operator to complete migration or repair the canonical pair. |
| 409 | workspace_unavailable | Restore or reauthorize the workspace before creating a connection. |
| 409 | idempotency_conflict | Restore the identical request and original authorization, or deliberately create a new workflow with a new key. |
Existing legacy workspaces must continue their exact-target setup flow until
migration. This endpoint does not migrate their environments, credentials, or
evidence. Malformed JSON and requests larger than 1 MiB return the common
invalid_request and request_too_large errors.
Manage connection reconciliation rules
Available through the workspace API. Rules belong to one exact Sandbox or
Live int_ connection. Use a workspace API key with integrations:read to
discover definitions, and integrations:manage for mutations and retained
receipt recovery. Source names, object types and delays are configuration;
observations and incidents are never copied or returned by these endpoints.
Discover and review
Code example
GET /v1/connections/{connectionId}/reconciliation-rulesGET /v1/connections/{connectionId}/reconciliation-rules/{ruleId}Code example
curl 'https://api.seamward.com/v1/connections/int_sandbox/reconciliation-rules?limit=25' \ -H "Authorization: Bearer $SEAMWARD_API_KEY"The list returns exact connection context, the current revision, rules and
nextCursor. limit defaults to 25 and allows 1 to 100. Preserve the opaque
cursor on later pages of the same connection. A cursor used in another
connection is invalid; a changed configuration revision requires a fresh list.
Worker evaluation timestamps do not invalidate a configuration review.
Code example
{ "connectionId": "int_sandbox", "workspaceIntegrationId": "wint_example", "mode": "sandbox", "revision": "3", "rules": [ { "id": "rule_candidate", "sourceEventType": "candidate.created", "expectedObjectType": "candidate", "maxDelayMs": 10000, "kind": "expected-outcome", "config": {}, "enabled": false, "lastEvaluatedAt": null, "createdAt": "2026-09-29T12:00:00.000Z", "updatedAt": "2026-09-29T12:00:00.000Z" } ], "nextCursor": null}Single-rule reads return the same context with rule instead of rules and
nextCursor. Copy the exact revision you reviewed into expectedRevision for
any mutation. A revision from an example is not an approval of your connection.
Create or enable a rule
Code example
POST /v1/connections/{connectionId}/reconciliation-rulesPATCH /v1/connections/{connectionId}/reconciliation-rules/{ruleId}DELETE /v1/connections/{connectionId}/reconciliation-rules/{ruleId}| Field | Create | Update | Delete |
|---|---|---|---|
expectedRevision | Required reviewed decimal-string epoch | Required | Required |
sourceEventType | Required, 1 to 120 letters, digits, ., _, :, - | Optional | Rejected |
expectedObjectType | Same format, required | Optional | Rejected |
maxDelayMs | Required integer, 1,000 to 86,400,000 | Optional | Rejected |
kind | Optional, defaults to expected-outcome; see Rule kinds | Rejected: a rule's kind never changes | Rejected |
config | Optional object, validated for the kind; defaults are filled in the response | Optional; replaces the whole configuration | Rejected |
enabled | Defaults to false | Optional explicit boolean | Rejected |
Create example:
Code example
{ "expectedRevision": "2", "sourceEventType": "candidate.created", "expectedObjectType": "candidate", "maxDelayMs": 10000, "enabled": false}Rule kinds
A rule asks one question about whether accepted events produced the right records. The list is closed. Every kind uses sourceEventType, expectedObjectType and maxDelayMs; config holds what is specific to the kind, and a configuration that is not valid for the kind returns 400 invalid_rule_request.
kind | Question | config |
|---|---|---|
expected-outcome | Does each accepted event lead to a record within maxDelayMs? | {} |
no-duplicates | Does any event lead to more than one record? | windowMs |
count-match | Over the window, do events and records agree in number? | windowMs, tolerancePercent, minimumEvents |
amount-match | Over the window, do the amounts reported on both sides agree? | unit (required), windowMs, tolerancePercent, minimumEvents |
allowed-sequence | Do a record's reported states follow a declared order? | states (required), windowMs |
lateness-band | Do records arrive within a soft delay, well before the deadline? | softDelayMs (required), latePercent, windowMs, minimumEvents |
config field | Type | Default | Meaning |
|---|---|---|---|
windowMs | integer, 60,000 to 604,800,000 | 86,400,000 | How far back the rule looks. For count-match, amount-match and lateness-band the window ends maxDelayMs before now, so a record still on its way is not counted as absent |
tolerancePercent | number, 0 to 100 | 0 | The difference allowed, as a percentage of the event count or of the received total |
minimumEvents | integer, 1 to 100,000 | 20 | Below this many events in the window the rule raises nothing |
unit | 1 to 32 letters, digits, ., _, - | none | Only amounts reported with this unit are summed |
states | 2 to 12 distinct labels | none | The order a record's states must follow, earliest first. A backward step, a skipped step, or an undeclared state is a finding; repeating a state is not |
softDelayMs | integer, at least 1 and below maxDelayMs | none | A record later than this is late, though not yet missing |
latePercent | number, above 0 to 100 | 20 | The share of late records that raises a finding |
amount-match and allowed-sequence read outcome.amount and outcome.state, which need envelope 0.6. No collector release reports them yet, so these two kinds can be created but have nothing to evaluate until one does; with the REST API you can send them today. For allowed-sequence, sourceEventType labels the rule and does not filter events: states are read from every accepted observation that reports the record type.
Each kind raises its own incident kind: missing-business-outcome, outcome-duplicated, count-mismatch, amount-mismatch, illegal-transition or outcome-late. An incident from a windowed kind resolves on its own only after the rule has stayed healthy for a verification period of maxDelayMs or five minutes, whichever is longer.
Require at least one changed-definition field on PATCH; omitted fields stay
unchanged. To enable a copied disabled rule, review its current configuration,
then PATCH { "expectedRevision": "3", "enabled": true }. Setting enabled
to true affects analysis on this connection only. Test the definition in
Sandbox before explicitly enabling its separate Live copy. Copying never
enables a rule automatically.
For each mutation send JSON and a new retained Idempotency-Key of 8 to 128
printable non-space ASCII characters. Unknown fields and query parameters are
rejected. A matching source-event/object pair must be unique within the
connection. DELETE accepts only expectedRevision and refuses rules with
retained incident history. Disable such a rule with a separately reviewed PATCH
instead; incident history remains intact.
Mutation receipts and recovery
Code example
GET /v1/rule-operations/{operationId}Make a reviewed rule request
Set SEAMWARD_CONNECTION_ID to the exact connection returned by your inventory,
SEAMWARD_RULE_ID to a rule from its list, and SEAMWARD_REVIEWED_REVISION to
that list's current revision. Use a separate retained request key for each
reviewed change; keep the same key and body after a lost response.
Create a disabled definition:
Code example
curl -sS -X POST "https://api.seamward.com/v1/connections/$SEAMWARD_CONNECTION_ID/reconciliation-rules" \ -H "Authorization: Bearer $SEAMWARD_API_KEY" \ -H "Idempotency-Key: $SEAMWARD_RULE_CREATE_KEY" \ -H 'Content-Type: application/json' \ --data "{\"expectedRevision\":\"$SEAMWARD_REVIEWED_REVISION\",\"sourceEventType\":\"candidate.created\",\"expectedObjectType\":\"candidate\",\"maxDelayMs\":10000,\"enabled\":false}"Read current state and review its revision before enabling that exact rule:
Code example
curl -sS -X PATCH "https://api.seamward.com/v1/connections/$SEAMWARD_CONNECTION_ID/reconciliation-rules/$SEAMWARD_RULE_ID" \ -H "Authorization: Bearer $SEAMWARD_API_KEY" \ -H "Idempotency-Key: $SEAMWARD_RULE_UPDATE_KEY" \ -H 'Content-Type: application/json' \ --data "{\"expectedRevision\":\"$SEAMWARD_REVIEWED_REVISION\",\"enabled\":true}"To detach a rule with no incident history, review the latest revision and use its own deletion request key. Disabling is the supported alternative when history prevents deletion:
Code example
curl -sS -X DELETE "https://api.seamward.com/v1/connections/$SEAMWARD_CONNECTION_ID/reconciliation-rules/$SEAMWARD_RULE_ID" \ -H "Authorization: Bearer $SEAMWARD_API_KEY" \ -H "Idempotency-Key: $SEAMWARD_RULE_DELETE_KEY" \ -H 'Content-Type: application/json' \ --data "{\"expectedRevision\":\"$SEAMWARD_REVIEWED_REVISION\"}"Set SEAMWARD_RULE_OPERATION_ID to the returned operationId to recover that
original actor's receipt. After a response is lost before the ID is known,
resend the exact original body and key to recover the committed operation:
Code example
curl -sS "https://api.seamward.com/v1/rule-operations/$SEAMWARD_RULE_OPERATION_ID" \ -H "Authorization: Bearer $SEAMWARD_API_KEY"First mutation returns 201 with the committed definition, exact mode,
resulting revision and immutable token-free receipt:
Code example
{ "connectionId": "int_sandbox", "workspaceIntegrationId": "wint_example", "mode": "sandbox", "revision": "3", "operationId": "ruleop_example", "action": "rule.create", "status": "completed", "expectedRevision": "2", "requestFingerprint": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "completedAt": "2026-09-29T12:00:00.000Z", "rule": { "id": "rule_candidate", "sourceEventType": "candidate.created", "expectedObjectType": "candidate", "maxDelayMs": 10000, "kind": "expected-outcome", "config": {}, "enabled": false, "lastEvaluatedAt": null, "createdAt": "2026-09-29T12:00:00.000Z", "updatedAt": "2026-09-29T12:00:00.000Z" }, "replayed": false, "historical": false}Update and deletion use action:"rule.update" and action:"rule.delete".
Deletion retains the removed rule definition in its receipt. A new mutation,
receipt, redacted audit and analysis enqueue commit in the same transaction.
There is no partial commit after permission loss or a stale revision.
Keep the original operation ID, request and idempotency key. Identical successful
retry returns 200 and replayed:true. A lost response can also be recovered
with the original currently authorized actor through the status endpoint.
requestFingerprint binds the normalized exact request. historical:true
means the connection revision has since changed or the connection disappeared;
it does not assert current rule state. Proof and completion time do not change.
Responses are no-store. Another key cannot inherit a receipt.
| Status | Code | Recovery |
|---|---|---|
| 400 | invalid_rule_request, invalid_rule_cursor | Correct strict fields or restart pagination in the exact connection. |
| 400 | idempotency_key_required | Supply and retain a correctly formatted mutation key. |
| 401, 403 | unauthenticated, insufficient_scope | Use the required current workspace authority. |
| 404 | connection_not_found, expected_outcome_not_found, operation_not_found | Check exact IDs, workspace and original actor. |
| 409 | rule_cursor_stale, connection_configuration_changed | Read current state and review again. |
| 409 | expected_outcome_already_exists | Use the existing definition or review an explicit update. |
| 409 | expected_outcome_has_incident_history | Preserve history and review disabling the rule instead. |
| 409 | idempotency_conflict | Recover the original operation. Use a new key for a separately reviewed request. |
| 409 | workspace_unavailable, workspace_modes_unavailable | Restore workspace access or complete canonical mode migration. |
| 429 | rate_limited | Wait 60 seconds. The budget is 60 new mutations per workspace per minute; successful identical retries reuse their receipt. |
Console rule compatibility
The console's expected-outcome controls share the canonical rule service, current membership checks, reviewed revision, retained request key, receipt, audit and atomic analysis transaction. A stale form reports a conflict; reload and review current configuration before submitting a new request. Retrying the same uncertain submission retains its original request key.
Session reads at /workspaces/{workspaceSlug}/integrations/{connectionId}/expected-outcomes
return the displayed revision, mode, integration name, management permission
and expected outcomes. POST, PATCH and DELETE require expectedRevision and
Idempotency-Key; unreviewed requests are rejected with invalid_rule_request
(400), and a missing key returns idempotency_key_required (400). Creation returns
201, or 200 on a successful retry; updates and deletion return 200. The response
contains expectedOutcome and its completed operation.
Original-user receipts use /workspaces/{workspaceSlug}/rule-operations/{operationId}.
A current owner or admin who performed the operation can recover its completed
receipt in an archived workspace. Archived workspaces do not accept mutations.
Legacy environment records remain readable with their actual mode classification
and management controls disabled; canonical migration is required before a new
rule mutation. The deprecated POST /integrations/{connectionId}/reconciliation-rules
route rejects unreviewed mutation with canonical_lifecycle_required (409). Use
the public revision-bound lifecycle above. Existing unbound
mutation requests have no compatibility writer.
Use the public routes above for automation. Browser cookies are not workspace API keys, and a rule operation cannot be recovered by another actor.
List workspace environments
Code example
GET /v1/environmentsDiscover exact environment identifiers before selecting a target for automation.
Use an expiring workspace API key
with integrations:read. There is no request body or filter. The response is
ordered by environment ID and is not cached.
Code example
curl 'https://api.seamward.com/v1/environments' \ -H "Authorization: Bearer $SEAMWARD_API_KEY"For a newly created workspace, success returns 200 with:
Code example
{ "model": "canonical", "environments": [ { "id": "env_live", "name": "Live", "slug": "live", "category": "live", "mode": "live", "isDefault": false }, { "id": "env_sandbox", "name": "Sandbox", "slug": "sandbox", "category": "sandbox", "mode": "sandbox", "isDefault": true } ]}| Field | Type | Meaning |
|---|---|---|
model | canonical or legacy | Canonical workspaces have exactly Sandbox and Live. Existing workspaces retain their original environments until reviewed migration. |
environments | array, 1 to 100 items | Exact stored environment records in this workspace. |
id | string beginning env_ | Exact target identifier. Do not replace an identifier based on its display name or classification. |
name, slug | nonempty strings | Display name and navigation slug. |
category | sandbox, live, development, production, or staging | Stored environment category. |
mode | sandbox or live | Classification for presentation. Development and Staging classify as Sandbox; Production classifies as Live. |
isDefault | boolean | Sandbox is the default in canonical workspaces. |
Classification does not move connections, broaden grants, or change observation
allowances. A legacy response preserves every original environment identifier.
Use its exact identifiers with integration inventory.
Setup authorization selects an existing environment and cannot create Staging.
| Status | Body | Recovery |
|---|---|---|
| 401 | {"error":"unauthenticated"} | Use a valid, unexpired workspace API key. |
| 403 | {"error":"insufficient_scope"} | Ask an administrator for a key carrying integrations:read. |
| 409 | {"error":"workspace_modes_unavailable"} | The stored topology is missing, unsupported, or mixes legacy and canonical records. Ask the workspace owner to resolve the migration state; do not guess a target. |
List Integrations
For a canonical workspace, request both independent mode slots:
Code example
GET /v1/integrations?view=canonical&limit=50&include=sourceCodeCode example
curl 'https://api.seamward.com/v1/integrations?view=canonical&limit=50&include=sourceCode' \ -H "Authorization: Bearer $SEAMWARD_API_KEY"Each item retains its shared wint_ identity, name, provider, direction,
protocol, creation timestamp, and shared revision. connections.sandbox and
connections.live each report their exact mode and environment ID. A missing
slot contains only status: "not_configured", mode, and environmentId.
It has no fabricated runtime connection, credentials, evidence, or health.
A configured slot includes connectionId, configuration revision,
credentialStatus, activeContractVersionId, latestObservationAt,
openIncidentCount, and observationState. Observation states are
waiting_for_observation, observed, and incident_open. observed establishes
accepted traffic, rather than a complete health verdict. Credentials and token
hashes are omitted.
For a workflow with an issued Sandbox connection and a missing Live connection:
Code example
{ "model": "canonical", "items": [ { "id": "wint_example", "name": "Candidate events", "provider": "ATS", "direction": "inbound", "protocol": "http-webhook", "createdAt": "2026-09-28T12:00:00.000Z", "revision": "1", "sourceCode": { "status": "not_connected" }, "connections": { "sandbox": { "status": "configured", "mode": "sandbox", "environmentId": "env_sandbox", "connectionId": "int_example", "revision": "1", "credentialStatus": "active", "activeContractVersionId": null, "latestObservationAt": null, "openIncidentCount": 0, "observationState": "waiting_for_observation" }, "live": { "status": "not_configured", "mode": "live", "environmentId": "env_live" } } } ], "nextCursor": null}sourceCode is opt-in: add include=sourceCode to canonical list or detail requests.
The default response omits it to preserve existing strict clients, including published CLI versions.
The console requests source status through its workspace inventory. The projection is shared by the Integration and is also returned by
GET /v1/integrations/{workspaceIntegrationId}?include=sourceCode. It is independent of connection
slots and available before either mode has a connection:
| Status | Fields | Meaning |
|---|---|---|
not_connected | status | No repository mapping is saved. GitHub account access alone does not create a mapping. |
connected | status, repository, targetBranch | The saved mapping has active stored access. repository is owner/name; targetBranch is the saved branch, falling back to the repository default for older mappings, or null when unknown. |
unavailable | status, repository, targetBranch | The grant is revoked or its installation is suspended, disconnected, or uninstalled. Restore access in workspace GitHub settings. |
Older servers may omit sourceCode; clients must treat absence as unknown,
not as not_connected. This projection does not contact GitHub, verify branch
existence, or confirm repair readiness. Mapping mutations and branch discovery
use the repository mapping API. The legacy list
without view=canonical retains its response shape; use the mapping API to read
source configuration for those identities.
When nextCursor is non-null, pass it unchanged as cursor with
view=canonical and the same include=sourceCode option when used. limit defaults to 50 and accepts 1 through 100. Each page is
a consistent database snapshot ordered by shared ID. Separate pages may reflect
new changes; pagination does not freeze the workspace. Cursors are bound to one
workspace and cannot authorize another workspace's data. Restart without a
cursor after migration or an incompatible cursor error.
invalid_inventory_query (400) rejects an unknown view, unsupported include value, bad limit, malformed
cursor syntax, or an environmentId filter combined with canonical view.
invalid_inventory_cursor (400) rejects an unsupported or foreign workspace
cursor. workspace_modes_unavailable (409) rejects legacy, incomplete, or mixed
topology in canonical view. Resolve the migration state before retrying it.
Unauthenticated and insufficient-scope errors remain 401 and 403.
Omitting view retains the compatible inventory response:
Code example
GET /v1/integrations?environmentId={environmentId}Use a workspace API key carrying integrations:read. The optional
environmentId filter limits each stable Integration to its connection in one
workspace environment. Without the filter, each item includes every configured
connection in the workspace, including exact legacy identifiers where they
remain. This compatible response is capped at 100 items.
Code example
{ "items": [ { "id": "wint_candidate", "name": "Candidate ATS", "provider": "HireFlow", "direction": "inbound", "protocol": "http-webhook", "createdAt": "2026-08-30T12:00:00.000Z", "connections": [ { "id": "int_candidate_development", "environmentId": "env_development", "connectionState": "observed", "structuralAnalysis": { "status": "active", "code": null, "checkedAt": "2026-08-30T12:05:01.000Z", "lastSuccessAt": "2026-08-30T12:05:01.000Z", "pausedAt": null, "reason": null, "groups": 0, "bytes": 0 }, "behavioralAnalysis": { "status": "active", "code": null, "checkedAt": "2026-08-30T12:05:01.000Z", "lastSuccessAt": "2026-08-30T12:05:01.000Z", "pausedAt": null, "reason": null, "groups": 0, "bytes": 0 }, "declaredVersion": "candidate-ats-v1", "latestObservationAt": "2026-08-30T12:05:00.000Z", "openIncidentCount": 0, "createdAt": "2026-08-30T12:00:00.000Z" } ] } ]}Each connection includes structuralAnalysis. Older server versions may omit it.
active with a null lastSuccessAt means analysis has not completed yet.
paused_capacity means structural comparison could not finish within the MVP
capacity limits; it does not mean the integration is healthy. The stable code is
structural_analysis_capacity_exceeded. reason is groups, bytes, or
findings. Counts are bounded lower bounds when a limit is exceeded, and are
zero after success. Behavioral detection and expected-outcome checks continue.
See capacity troubleshooting.
Behavioral analysis capacity
Each connection also includes behavioralAnalysis, with the same fields and state
rules as structuralAnalysis. Older servers may omit either field; absence does
not establish a completed analysis. A behavioral pause uses the code
behavioral_analysis_capacity_exceeded and reason groups, bytes or findings.
The MVP limits are 1,000 distinct matching identities, 8 MiB of serialized identity
metadata and 5,000 findings per pass. Identity metadata includes operation key,
direction, protocol, status, route, method, payload location and event type.
Structural and behavioral states are independent. Check both lastSuccessAt
values before interpreting a lack of new findings as evidence of stability.
Errors
| Status | Body | Meaning |
|---|---|---|
400 | {"error":"invalid_environment_filter"} | environmentId is malformed |
401 | {"error":"unauthenticated"} | The workspace API key is missing or invalid |
403 | {"error":"insufficient_scope"} | The key does not carry integrations:read |
409 | {"error":"result_limit_exceeded"} | More than 100 matching Integrations would be returned |
Mode-scoped operational reads
Code example
GET /v1/incidentsGET /v1/incidents/{incidentId}GET /v1/repair-proposals/{proposalId}All three endpoints require a current workspace API key and an explicit
mode=sandbox or mode=live. Incident reads require incidents:read; proposal
reads require repairs:read. A workspace owner issues these explicit scopes;
existing keys keep their original permissions. An ingest token, setup token,
browser cookie, or an observation-only key cannot authenticate these reads.
Code example
curl 'https://api.seamward.com/v1/incidents?mode=sandbox&status=open&limit=25' \ -H "Authorization: Bearer $SEAMWARD_API_KEY"| Query | Required | Values and behavior |
|---|---|---|
mode | Yes | sandbox or live; there is no implicit default. |
status | List only, optional | open or resolved; omit to read both. |
limit | List only, optional | 1 to 100; defaults to 25. |
cursor | List only, optional | Use the returned opaque cursor with the original mode and status filter. |
The incident list returns mode, items, and nextCursor. Ordering is newest
creation time first, then ascending ID. A null cursor means the page has no
continuation. An empty result is a successful empty page. A cursor from a
foreign workspace, mode, or status filter is refused.
Code example
{ "mode": "sandbox", "items": [], "nextCursor": null}Read one incident using its ID from the list:
Code example
curl 'https://api.seamward.com/v1/incidents/inc_example?mode=sandbox' \ -H "Authorization: Bearer $SEAMWARD_API_KEY"incident contains its connection ID, mode and environment, kind, status,
summary, affected record count, resolution state, first/last observation times,
creation/update times, and nullable resolution time. Detail also includes up to
100 retained relational observationIds and finding references (id, kind,
path, and summary). evidenceTruncated marks omitted references. This is a
bounded evidence preview; it does not return raw evidence JSON or historical
references that have expired from the relational evidence store.
Read proposal provenance with its original mode:
Code example
curl 'https://api.seamward.com/v1/repair-proposals/rep_example?mode=sandbox' \ -H "Authorization: Bearer $SEAMWARD_API_KEY"Code example
{ "proposal": { "id": "rep_example", "incidentId": "inc_example", "connectionId": "int_example", "mode": "sandbox", "environmentId": "env_sandbox", "status": "queued", "createdAt": "2026-09-29T06:00:00.000Z", "updatedAt": "2026-09-29T06:00:00.000Z", "errorCode": null, "validation": null, "decision": null }}A recorded validation contains its ID, replay run ID, validity, replay result, and creation time. A recorded decision contains its ID, approved/rejected outcome, actor type, proposal fingerprint, validation report ID, and creation time. Candidate code, raw evidence, execution tokens, leases, and actor email addresses are omitted. A provenance read cannot approve a repair or prove that it was deployed. Review candidate artifacts and approval details in the console.
All responses use Cache-Control: no-store. A selected-mode record with a
foreign or wrong-mode ID is indistinguishable from a missing record. Reads
recheck current key authority within the transaction, including expiry before
returning the result.
| Status | Error | Recovery |
|---|---|---|
| 400 | invalid_incident_query | Supply the explicit mode and valid list fields. Unknown fields are refused. |
| 400 | invalid_evidence_query | Supply only the required mode for a detail read. |
| 400 | invalid_incident_cursor | Restart the list with its original workspace, mode and filter. |
| 401 | unauthenticated | Use a current workspace key for an active workspace. |
| 403 | insufficient_scope | Obtain the explicit incident or repair read permission from a workspace owner. |
| 404 | incident_not_found or repair_proposal_not_found | Check the original workspace, mode, and ID. |
| 409 | workspace_unavailable or workspace_modes_unavailable | Restore access or complete the reviewed workspace mode conversion. |
Planned endpoints
Public incident resolution and repair approval APIs are planned, not available. Use the console for those mutations. These operational reads do not grant permission to approve, deliver, or deploy a repair.
Authentication model
- Workspace-scoped API keys are created by an owner, shown once, stored only as a hash, and expire within one year.
- Integration, observation, incident, repair, and contract scopes separate inventory, evidence reads, proposal provenance, draft registration, and activation.
- Bearer authentication uses
Authorization: Bearer sw_api_...; contract requests do not use HMAC signing.
Repair approval and delivery remain explicit console decisions and are not exposed as write endpoints.
Next steps
- Ingest observations: the endpoint that is available today.
- Observation API: list evidence and read usage.
- Contract API: the available workspace automation surface.
- MCP overview: programmatic read access that works now, through OAuth.
Delete one connection
Connection-only deletion is available for canonical Sandbox/Live workspaces. It permanently removes the selected connection and its owned operational data, then creates a new, empty connection for that mode, because an Integration always has both. The new connection has a new int_ ID, no credential, no contracts, no rules and no evidence. The shared Integration remains. Other connections, accepted usage totals, audit history, and canonical creation, setup, credential, and deletion receipts remain. Completed legacy credential and setup receipts also remain, including their original request, result and item lineage. Pending setup reviews that reference the removed connection expire; active OAuth bindings lose that connection reference.
An ingest token cannot perform this operation. Use a workspace API key with integrations:manage in Authorization: Bearer <key>. Keep the same key for the review, apply, status, and retries. An inactive workspace is unauthenticated. A legacy workspace returns workspace_modes_unavailable; its owner must complete the reviewed environment migration first.
Preview the exact mode
POST /v1/connections/{connectionId}/deletion-preview
Use the int_ connection ID from the Integration inventory, not its shared wint_ identity. The body is the empty JSON object {}. Mode overrides and unknown options are rejected.
Code example
curl -X POST 'https://api.seamward.com/v1/connections/int_sandbox_example/deletion-preview' \ -H "Authorization: Bearer $SEAMWARD_WORKSPACE_API_KEY" \ -H 'Content-Type: application/json' \ --data '{}'Replace the connection ID and set SEAMWARD_WORKSPACE_API_KEY to your workspace key. A 201 response describes the permanent impact:
Code example
{ "operationId": "conndel_example", "approvalFingerprint": "sha256:8ef4c6194a5cff12e4df19d13bb26b352bc36d3097a8a4fd086e4d6047e60f7c", "confirmationChallenge": "Candidate webhook (Sandbox)", "expiresAt": "2026-09-28T12:10:00.000Z", "connection": { "id": "int_sandbox_example", "workspaceIntegrationId": "wint_example", "environmentId": "env_sandbox", "mode": "sandbox", "name": "Candidate webhook", "provider": "ATS", "direction": "inbound", "protocol": "http-webhook" }, "revisions": { "identity": "1", "configuration": "2", "impact": "3" }, "counts": { "approvalRecords": 0, "contractActivations": 1, "contracts": 1, "credentialOperations": 1, "expectedOutcomes": 1, "findings": 3, "incidents": 2, "observations": 15, "oauthBindings": 1, "repairArtifacts": 0, "repairDeliveries": 0, "repairProposals": 0, "repositoryMappings": 0, "replayDatasets": 0, "replayRuns": 0, "setupProvisioningItems": 1, "validationReports": 0, "webhookDeliveries": 0, "contractOperations": 1, "shapeSummaries": 2, "observationFindings": 3, "incidentObservations": 2 }, "hazards": { "backgroundJobs": 0, "activeReplayRuns": 0, "activeRepairProposals": 0, "activeDeliveries": 0, "crossConnectionReferences": 0 }, "preservedConnectionIds": ["int_live_example"]}| Field | Meaning |
|---|---|
operationId | Server-issued conndel_ review identifier. Retain it for apply and status. |
connection | Exact connection ID, shared Integration ID, Sandbox or Live mode, canonical environment ID, name, provider, direction, and protocol. |
revisions | Server-owned identity, configuration, and destructive-impact revisions. Review changes require a fresh preview. |
counts | Records removed or bindings scrubbed: contracts, contract operations, activations, observations, shape summaries, observation-finding links, incident-observation links, findings, incidents, expected outcomes, replay datasets and runs, repair proposals and artifacts, deliveries, approval records, validation reports, webhook receipts, repository mappings, and OAuth bindings. credentialOperations and setupProvisioningItems count retained historical references. Their completed receipts are preserved; they are not removal counts. |
hazards | Pending or claimed background jobs, active replay runs, active repair proposals, queued external deliveries, and references shared with another connection. Any nonzero value blocks apply. |
preservedConnectionIds | Other connections present during review. Their operational data and credentials are preserved. |
confirmationChallenge | Exact case-sensitive name and mode the reviewer must confirm. |
approvalFingerprint | Server-issued binding to this immutable review. Submit it unchanged. |
expiresAt | Ten minutes after preview creation. At expiry, apply is refused. |
Preview creation is limited to 30 per workspace per minute. 429 rate_limited includes Retry-After: 60. Responses are private and not cacheable. A preview makes no deletion and does not cancel work.
Apply the reviewed deletion
POST /v1/connection-deletions/{operationId}/apply
After reviewing the exact mode, counts, and preservation guarantees, submit the returned fingerprint and confirmation challenge. This permanently removes data and has no undo operation. There is no force option.
| Request | Required constraint |
|---|---|
operationId path parameter | The original server-issued conndel_ identifier. |
Idempotency-Key header | 8 to 128 printable non-space ASCII characters. Retain the original value for retries. |
approvalFingerprint body field | Exact sha256: fingerprint followed by 64 lowercase hexadecimal characters from the preview. |
confirmationName body field | Exact returned challenge, including (Sandbox) or (Live), 1 to 160 characters. |
Code example
curl -X POST 'https://api.seamward.com/v1/connection-deletions/conndel_example/apply' \ -H "Authorization: Bearer $SEAMWARD_WORKSPACE_API_KEY" \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: remove-sandbox-20260929-01' \ --data '{"approvalFingerprint":"sha256:8ef4c6194a5cff12e4df19d13bb26b352bc36d3097a8a4fd086e4d6047e60f7c","confirmationName":"Candidate webhook (Sandbox)"}'Replace all review values with your response. The first completion returns 201 with replayed: false. The exact original-key retry returns 200 with replayed: true. The key shares a workspace-wide namespace with canonical creation and credential operations; reuse for a different request or action is refused.
Authorization is checked again in the deletion transaction. Changes to authorization, identity, configuration, or owned operational data invalidate a review, including changes later restored to their earlier values. A serialization conflict is retried internally at most twice with fresh authorization and a fresh snapshot; persistent contention returns deletion_preview_stale. Deletion, retained receipt, and audit commit together. No external GitHub issue or pull request is deleted.
Recover a lost response
GET /v1/connection-deletions/{operationId}
Code example
curl 'https://api.seamward.com/v1/connection-deletions/conndel_example' \ -H "Authorization: Bearer $SEAMWARD_WORKSPACE_API_KEY"Status returns 200. Before apply, it contains the preview plus status: "previewed"; after ten minutes, status: "expired". After completion, it returns the retained historical receipt even though the connection no longer exists:
Code example
{ "operationId": "conndel_example", "status": "completed", "connectionId": "int_sandbox_example", "workspaceIntegrationId": "wint_example", "mode": "sandbox", "name": "Candidate webhook", "counts": { "approvalRecords": 0, "contractActivations": 1, "contracts": 1, "credentialOperations": 1, "expectedOutcomes": 1, "findings": 3, "incidents": 2, "observations": 15, "oauthBindings": 1, "repairArtifacts": 0, "repairDeliveries": 0, "repairProposals": 0, "repositoryMappings": 0, "replayDatasets": 0, "replayRuns": 0, "setupProvisioningItems": 1, "validationReports": 0, "webhookDeliveries": 0, "contractOperations": 1, "shapeSummaries": 2, "observationFindings": 3, "incidentObservations": 2 }, "preservedConnectionIds": ["int_live_example"], "replayed": true}A receipt does not describe a subsequently recreated connection with the same ID. Replaying it cannot remove the replacement. Status and retries remain restricted to the original active key; deleting or revoking that key prevents recovery through this endpoint. No token or token hash is returned.
| Status | Error | Recovery |
|---|---|---|
| 400 | invalid_connection_deletion | Submit the strict empty preview body or the two apply fields. |
| 400 | idempotency_key_required | Supply a valid original key for apply. |
| 401 | unauthenticated | Use the original active workspace key and active workspace. |
| 403 | insufficient_scope | Obtain current integrations:manage permission. |
| 404 | connection_not_found or operation_not_found | Check the workspace, exact connection, operation ID, and original key. Other tenants and keys are not disclosed. |
| 409 | workspace_unavailable or workspace_modes_unavailable | Restore workspace availability or complete the reviewed environment migration. |
| 409 | deletion_preview_expired or deletion_preview_stale | Create a new preview and review its current impact. |
| 409 | confirmation_mismatch | Use the exact challenge from the reviewed preview. |
| 409 | idempotency_conflict | For recovery, resend the exact original request and key. For a separate operation, review it and use a new key. |
| 409 | connection_work_in_progress | Let background work and external delivery finish, then preview again. Do not remove delivery evidence to bypass this check. |
| 409 | connection_references_in_use | Resolve the cross-connection reference with its owner, then preview again. |
| 429 | rate_limited | Wait 60 seconds before another preview. |
Whole-Integration compatibility
The CLI integrations delete command and setup MCP delete_integration tool delegate to the canonical whole-Integration service. They refuse deletion if any configured connection falls outside the selected one-mode setup grant. A Sandbox grant cannot remove Live. Use the workspace-key whole deletion API when both modes are configured. Neither command provides connection-only deletion.
Canonical browser connection detachment uses the same immutable service. Previews created by the historical detachment service cannot apply after a workspace enters canonical mode; create a fresh preview. Historical legacy records remain under their original contract and are not converted into canonical receipts.
For safe setup replacement and permanent-removal steps, see Automatic setup. If apply is refused, follow Deletion troubleshooting.
Legacy setup deletion retains its whole-Integration meaning through the canonical service. It requires every configured connection to be in the current client’s exact one-mode grant. A workflow configured in both modes requires the workspace-key API below.
Delete a whole Integration
Whole Integration deletion is available for canonical Sandbox and Live workspaces. It permanently removes the shared workflow and every configured connection. An empty workflow can also be removed. Use connection deletion when you intend to remove only one mode.
All three endpoints require a workspace API key with integrations:manage. This is a destructive operation with no undo. Accepted usage, audit history and immutable operation receipts survive. External GitHub issues and pull requests are not deleted.
Review both connections
POST /v1/integrations/{workspaceIntegrationId}/deletion-preview
Pass the shared wint_ ID and an empty JSON object. The response describes the shared identity revision and each configured connection's mode, revision, counts and hazards. Zero, one or two connections can appear. Both modes are covered by the whole Integration operation. The oauthBindings count includes setup bindings and remote MCP grants affected by each removal. Removing one selected connection invalidates the entire remote grant, including its remaining selections. A changed grant requires a fresh deletion review. Reauthorize the surviving connections after deletion.
Code example
curl -X POST 'https://api.seamward.com/v1/integrations/wint_example/deletion-preview' \ -H "Authorization: Bearer $SEAMWARD_API_KEY" \ -H 'Content-Type: application/json' \ --data '{}'Example 201 response:
Code example
{ "operationId": "wintdel_example", "approvalFingerprint": "sha256:8ef4c6194a5cff12e4df19d13bb26b352bc36d3097a8a4fd086e4d6047e60f7c", "expiresAt": "2026-09-28T12:10:00.000Z", "integration": { "id": "wint_example", "name": "Candidate webhook", "provider": "ATS", "direction": "inbound", "protocol": "http-webhook", "revision": "1" }, "confirmationChallenge": "Candidate webhook (all connections)", "connections": [ { "connection": { "id": "int_sandbox_example", "workspaceIntegrationId": "wint_example", "environmentId": "env_sandbox", "mode": "sandbox", "name": "Candidate webhook", "provider": "ATS", "direction": "inbound", "protocol": "http-webhook" }, "revisions": { "identity": "1", "configuration": "2", "impact": "3" }, "confirmationChallenge": "Candidate webhook (Sandbox)", "counts": { "approvalRecords": 0, "contractActivations": 1, "contracts": 1, "credentialOperations": 1, "expectedOutcomes": 1, "findings": 3, "incidents": 2, "observations": 15, "oauthBindings": 1, "repairArtifacts": 0, "repairDeliveries": 0, "repairProposals": 0, "repositoryMappings": 0, "replayDatasets": 0, "replayRuns": 0, "setupProvisioningItems": 1, "validationReports": 0, "webhookDeliveries": 0, "contractOperations": 1, "shapeSummaries": 2, "observationFindings": 3, "incidentObservations": 2 }, "hazards": { "backgroundJobs": 0, "activeReplayRuns": 0, "activeRepairProposals": 0, "activeDeliveries": 0, "crossConnectionReferences": 0 }, "preservedConnectionIds": [] } ]}Every configured connection will be removed. preservedConnectionIds is empty in this whole Integration review. The review expires after 10 minutes. Shared with connection deletion, preview creation is limited to 30 per workspace per minute; 429 rate_limited includes Retry-After: 60.
Apply exactly that review
POST /v1/integration-deletions/{operationId}/apply
Use the wintdel_ operation ID, exact approval fingerprint and the complete confirmation challenge, including (all connections). Preserve an Idempotency-Key of 8 to 128 printable non-space ASCII characters. The body has only these two fields:
Code example
{ "approvalFingerprint": "sha256:8ef4c6194a5cff12e4df19d13bb26b352bc36d3097a8a4fd086e4d6047e60f7c", "confirmationName": "Candidate webhook (all connections)"}Code example
curl -X POST 'https://api.seamward.com/v1/integration-deletions/wintdel_example/apply' \ -H "Authorization: Bearer $SEAMWARD_API_KEY" \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: delete-candidate-workflow-001' \ --data '{"approvalFingerprint":"sha256:8ef4c6194a5cff12e4df19d13bb26b352bc36d3097a8a4fd086e4d6047e60f7c","confirmationName":"Candidate webhook (all connections)"}'201 confirms atomic completion. An exact original-key retry returns 200 with replayed: true:
Code example
{ "operationId": "wintdel_example", "status": "completed", "workspaceIntegrationId": "wint_example", "name": "Candidate webhook", "connections": [ { "connectionId": "int_sandbox_example", "mode": "sandbox", "counts": { "approvalRecords": 0, "contractActivations": 1, "contracts": 1, "credentialOperations": 1, "expectedOutcomes": 1, "findings": 3, "incidents": 2, "observations": 15, "oauthBindings": 1, "repairArtifacts": 0, "repairDeliveries": 0, "repairProposals": 0, "repositoryMappings": 0, "replayDatasets": 0, "replayRuns": 0, "setupProvisioningItems": 1, "validationReports": 0, "webhookDeliveries": 0, "contractOperations": 1, "shapeSummaries": 2, "observationFindings": 3, "incidentObservations": 2 } } ], "replayed": false}The server checks authorization, the shared identity revision, every connection revision, data impact and hazards before deleting any record. An active replay, repair, delivery or queued background job returns connection_work_in_progress. Cross-connection evidence references return connection_references_in_use. Both failures preserve every connection. Changed authorization, configuration or impact returns deletion_preview_stale; create a new review after resolving the change.
Recover a lost response
GET /v1/integration-deletions/{operationId}
Use the original credential to read previewed, expired or completed status. After completion, the immutable receipt is returned with replayed: true without looking up the deleted identity. A different credential cannot claim the operation. Retry apply only with its original body and request key.
A current owner or administrator who originally approved the browser operation can read its token-free receipt at GET /workspaces/{workspaceSlug}/integration-deletions/{operationId}, including an archived workspace. This browser session route does not accept workspace API keys.
Resolve a refused request
| Status | Error | Recovery |
|---|---|---|
| 400 | invalid_integration_deletion | Use an empty preview body or exactly the two apply fields. |
| 400 | idempotency_key_required | Supply the original valid request key. |
| 401 | unauthenticated | Use a current workspace API key. |
| 403 | insufficient_scope | Obtain integrations:manage for this workspace. |
| 404 | integration_not_found or operation_not_found | Check the shared ID, original actor and workspace. Foreign IDs are not disclosed. |
| 409 | workspace_modes_unavailable or workspace_unavailable | Resolve workspace lifecycle or canonical mode availability before creating another review. |
| 409 | confirmation_mismatch | Submit the exact server-issued challenge. |
| 409 | deletion_preview_stale or deletion_preview_expired | Create and review a new preview. |
| 409 | idempotency_conflict | Reuse the original body and credential for a completed operation. Use a new key for a separate operation. |
| 409 | connection_work_in_progress or connection_references_in_use | Resolve the reviewed hazards; then review the complete Integration again. |
| 429 | rate_limited | Wait for Retry-After. |
| 503 | mode_cutover_write_paused | Retry the same request and key after Retry-After. |
Read account mode usage
GET /v1/accounts/{accountId}/usage returns pooled accepted observations for the
current UTC calendar month. Use an account API key carrying account:usage:read.
Workspace keys, setup tokens, and browser cookies cannot authorize this read.
Obtain the account ID and credential through the verified account management
requests below. There are no query parameters or request body.
Quota enforcement is scoped to Sandbox. enforcement.scope is always
sandbox; status and startsAt describe that Sandbox policy. The Live
allowance is enforced separately by plan: Developer stops recording Live
observations at it, and Pro and Enterprise keep recording. Activating this
Sandbox policy does not change Live admission.
Code example
curl -sS "https://api.seamward.com/v1/accounts/$SEAMWARD_ACCOUNT_ID/usage" \ -H "Authorization: Bearer $SEAMWARD_ACCOUNT_API_KEY"Code example
{ "accountId": "ba_example", "period": "utc_calendar_month", "periodStart": "2026-09-01T00:00:00.000Z", "periodEnd": "2026-10-01T00:00:00.000Z", "enforcement": { "scope": "sandbox", "status": "off", "publishedAt": null, "cutoverAt": null, "startsAt": null }, "sandbox": { "recordedCount": 3, "monthlyLimit": 10000, "remainingCount": 9997, "retentionDays": 7, "retentionCutoverAt": null, "work": { "enforcement": "active", "repairValidations": { "monthlyLimit": 3, "reservedCount": 0, "recordedCount": 0, "remainingCount": 3 }, "aiInvestigations": { "monthlyLimit": 1, "recordedCount": 0, "remainingCount": 1 }, "activeExecutions": 0, "concurrentLimit": 1, "submittedObservationsPerMinute": 1000, "ingestRequestsPerMinute": 100 } }, "live": { "recordedCount": 7, "monthlyLimit": 50000, "remainingCount": 49993, "unclassifiedCount": 2 }}| Field | Meaning |
|---|---|
periodStart, periodEnd | Inclusive start and exclusive end of one UTC calendar month. |
sandbox.recordedCount | Accepted Sandbox observations across the account's workspaces. |
live.recordedCount | Accepted Live observations plus historical usage whose mode cannot be established. |
live.unclassifiedCount | The historical portion already included in Live, never an extra charge. |
monthlyLimit, remainingCount | Each mode's nonnegative remaining allowance. Sandbox defaults to 10,000. Live remaining is enforced on Developer only. |
enforcement.scope | Always sandbox. This schedule cannot activate Live rejection. |
enforcement.status | off before scheduling, grace before its effective date, or active once Sandbox admission checks apply. |
publishedAt, cutoverAt, startsAt | Policy publication, reviewed cutover, and enforcement dates, or null. |
sandbox.retentionDays, retentionCutoverAt | Prospective retention duration and eligibility date. A null date means this new policy has not begun. |
Moving or removing a workspace, connection, or retained observation does not remove charges already attributed to its admission account. No workspace names, identifiers, raw observations, or credential material appear in this response. The response is never cached.
| Status | Error | Recovery |
|---|---|---|
400 | invalid_account_usage_query | Remove all query fields. |
401 | unauthenticated | Use a current account key. Its creator must still have a verified email and account owner or billing administrator access, and the account must be active. |
404 | account_not_found | Use the exact account bound to the credential. Foreign accounts are indistinguishable from missing accounts. |
409 | account_access_changed | Recheck access and retry the read with current authorization. |
503 | usage_unavailable | Retry later or contact support. Do not treat missing usage as zero. |
Sandbox work usage
sandbox.work reports active resource safeguards independently of observation
quota scheduling. Repair validations are reserved on admission and charged once
when execution starts. reservedCount plus recordedCount consumes the monthly
allowance. Never-started cancellation releases its reservation; failed validation
counts. AI dispatch is counted before the provider is called, including uncertain
responses. Recovery does not repeat an uncertain call. activeExecutions covers
the whole account, including prior-month work, and is at most one.
Manage account usage keys
Account owners and billing administrators use a verified browser session for
these requests. List accessible accounts with GET /billing-accounts. Obtain the
same mode usage response with GET /billing-accounts/{accountId}/mode-usage,
including when the account has no active workspace. Key creation and revocation
also require an Origin header matching the console origin. These management
routes do not accept workspace keys, setup tokens, or account usage keys.
Code example
POST /billing-accounts/{accountId}/api-keysOrigin: https://seamward.comContent-Type: application/json
{ "name": "Account usage reporting", "scopes": ["account:usage:read"], "expiresAt": "2027-01-01T00:00:00.000Z"}The 201 response contains apiKey metadata and secret. The secret has the
sw_acct_ prefix and 43 URL-safe characters; it appears once and is stored only
as a SHA-256 hash. Metadata contains id, accountId, name, tokenPrefix,
scopes, createdAt, expiresAt, lastUsedAt, and revokedAt. Dates are UTC
ISO strings; the last two may be null. Save the secret directly to your secret
store. If creation succeeded but its response was lost, list metadata, revoke
the orphaned key, and issue a replacement. Creation does not replay a secret.
The name must contain 1 to 80 characters after trimming. The sole scope is
account:usage:read. Expiry must be in the future and within 365 days. Extra
body fields are rejected. There may be at most 50 unexpired, unrevoked keys per
account. Creation is limited to 10 successful requests per actor and account
per hour; 429 rate_limited carries Retry-After: 3600.
GET /billing-accounts/{accountId}/api-keys returns { "apiKeys": [], "nextCursor": null } when empty. limit defaults to 25 and accepts 1 to 100.
Pass the returned opaque cursor to continue; it is bound to the account.
Keys are ordered by descending creation time and then ID. Lists include
expired and revoked keys, without secrets or hashes.
POST /billing-accounts/{accountId}/api-keys/{apiKeyId}/revoke accepts exactly
{} and returns 200 with { "apiKey": <metadata> }. Repeating revocation is
safe and does not add another audit event. Revoking a key stops future usage
reads. Scopes and expiry are immutable: issue and verify a replacement before
revoking a working key.
All routes check fresh account access, independent of workspace membership.
401 unauthenticated requires signing in. 403 email_verification_required
requires email verification; 403 untrusted_origin requires the correct
console origin. 404 account_not_found or account_api_key_not_found conceals
foreign targets. Invalid create/revoke bodies return 400 invalid_account_api_key;
invalid list filters or cursors return 400 invalid_account_key_query or
invalid_account_key_cursor. 409 account_key_limit_reached requires revoking
unused keys; 409 account_access_changed requires a fresh authorization check.
Account audit records never contain secret material.
See monthly admission behavior before implementing delivery retries.
Historical session receipt access
Canonical browser credential controls use reviewed operations and show new tokens once. Reloading or reading a receipt never recovers a plaintext token. The following session reads use the original user's current workspace membership, independent of which workspace is active in the browser:
| Receipt | Session endpoint |
|---|---|
| Creation | GET /workspaces/{workspaceSlug}/integration-operations/{operationId} |
| Credential change | GET /workspaces/{workspaceSlug}/credential-operations/{operationId} |
| Completed connection removal | GET /workspaces/{workspaceSlug}/connection-deletions/{operationId} |
| Completed rule mutation | GET /workspaces/{workspaceSlug}/rule-operations/{operationId} |
Only the original actor who remains an owner or admin can read these completed,
token-free receipts in an archived workspace. Another owner cannot inherit the
receipt. A current member without owner/admin access receives 404 at the
session boundary. Pending previews and all mutations remain unavailable while
archived. A historical credential receipt returns historical:true; completed
setup or creation returns its retained replay shape with
credentialRecoveryRequired:true. No receipt asserts current connection health.
This exception grants no archived access to workspace API keys. Keys require an
active workspace, so archive generally returns 401 at public authentication.
If archive or rebinding happens after authorization but before the transaction,
the service returns 409 workspace_unavailable. Restore the workspace before
reviewing new operations. Repeating a mutation is not a historical receipt read.
GitHub repository mappings
Available for workspaces with a connected GitHub App installation. Connect the account in Workspace settings → GitHub first. This API selects where an integration's source lives and where repair pull requests should target. Saving a mapping does not write to GitHub or change a deployed application.
Removing one mode connection preserves the mapping. Whole Integration deletion removes it and reports sharedRepositoryMappings once in the preview and result, separately from per-connection counts. Older completed receipts can omit this field.
Repository, service path, and target branch belong to the Integration and apply in both Test and Live. You can configure source code before either mode receives traffic. Credentials, observations, incidents, approvals, and repair execution remain specific to each mode.
Use a workspace API key with integrations:read for discovery and reads, or
integrations:manage for saves and removal. Browser cookies and CLI OAuth tokens
are not accepted by these public endpoints. Every repository grant and integration
must belong to the key's workspace. Responses use Cache-Control: private, no-store.
| Endpoint | Result |
|---|---|
GET /v1/github/repositories | Active repository grants, up to 100 per page. |
GET /v1/github/repositories/:repositoryGrantId/branches | Up to 100 branch names, nextPage, and selectedBranchAvailable. |
GET /v1/integrations/:workspaceIntegrationId/github-mapping | Integration identity, canManage, and mapping or null. |
POST /v1/integrations/:workspaceIntegrationId/github-mapping | Validate and save; returns { "mapping": ... }. |
DELETE /v1/integrations/:workspaceIntegrationId/github-mapping | Remove an unused mapping; returns 204, including repeated removal. |
These endpoints remain supported compatibility aliases:
| Endpoint | Result |
|---|---|
GET /v1/connections/:connectionId/github-mapping | Read the shared Integration mapping through either mode connection. |
POST /v1/connections/:connectionId/github-mapping | Save the shared mapping with the same validation and version check. |
DELETE /v1/connections/:connectionId/github-mapping | Remove the shared mapping, subject to retained-history checks. |
Either mode connection resolves to the same mapping, and reads return its canonical Integration identity. No removal date is scheduled. Prefer the Integration URL for new clients; it also works when neither mode has a connection.
Repository discovery returns { "repositories": [], "nextCursor": null } when
empty. Entries contain id, owner, name, and defaultBranch. Pass after
with the last returned nextCursor to continue. Branch discovery accepts page
(default 1, maximum 10000) and optional selectedBranch. The latter checks the
chosen branch directly, independently of pagination. selectedBranchAvailable
is true or false when requested, or null otherwise. Provider failures are errors,
not empty lists or evidence that a branch was deleted. Pass nextPage to continue;
a null continuation ends the list. Search in the console covers loaded branches.
Code example
curl --fail-with-body 'https://api.seamward.com/v1/github/repositories' \ -H "Authorization: Bearer $SEAMWARD_API_KEY"
curl --fail-with-body \ 'https://api.seamward.com/v1/github/repositories/ghrepo_backend/branches?selectedBranch=release%2Fstable' \ -H "Authorization: Bearer $SEAMWARD_API_KEY"
curl --fail-with-body -X POST \ 'https://api.seamward.com/v1/integrations/wint_payments/github-mapping' \ -H "Authorization: Bearer $SEAMWARD_API_KEY" \ -H 'Content-Type: application/json' \ --data '{"repositoryGrantId":"ghrepo_backend","targetBranch":"release/stable","servicePath":"services/payments","expectedUpdatedAt":null}'Replace the example IDs with your Integration ID (wint_...) and a returned repository grant ID. Use your deployment's API base URL. expectedUpdatedAt: null means no mapping
is expected. For edits, send the updatedAt timestamp from the mapping read.
A stale edit returns 409 github_mapping_changed; read the latest mapping before
trying again. Exact repeats of the saved repository, branch, and path return the
existing mapping without another audit event. Legacy clients may omit the version;
simultaneous writes are still checked against the mapping read at request start.
Use the version field to protect against edits made before your request starts.
| Save field | Meaning |
|---|---|
repositoryGrantId | Required workspace grant ID, 1 to 128 letters, digits, underscores, or hyphens. |
targetBranch | Optional short Git branch name, up to 200 characters. Omission preserves the saved branch for the same repository; a new repository uses its default. |
servicePath | Optional repository-relative directory, up to 240 characters. Empty or null means repository root. No leading slash, . or .. segments. |
expectedUpdatedAt | Optional ISO timestamp from a mapping read, or null for a new mapping. |
The directory must exist on the chosen branch. Validation resolves that branch to
an exact commit before checking its directory. services/payments refers to a
folder in your repository, not a server path or URL. The response retains the
existing field name deploymentBranch for the saved target branch. It also
contains id, servicePath, createdAt, updatedAt, and repository and
installation metadata. Older mappings with a null deploymentBranch resolve to
the repository's defaultBranch until saved explicitly. Saving pins the selected branch explicitly.
For removal, send If-Match: "<updatedAt>" with the timestamp from the mapping
read. Stale removal confirmations return 409 github_mapping_changed. Omitting
the header retains legacy behavior with concurrent-request protection. Invalid
header syntax returns 400 invalid_github_mapping.
The observed deployment commit remains separate from the target branch. Repair preparation checks reported commits and target source; delivery still verifies the branch head, source content, reviewed fingerprint, and approval. Changing the target branch requires preparing and reviewing the patch again. Repository protections and merge/deployment decisions remain yours.
The console's existing session mapping routes use this same validation,
transaction, and audit lifecycle. Writes recheck workspace authority and active
repository access in the transaction. Audit records include the previous and
new mapping choices and API-key attribution when used. Removing a mapping with
retained patch or delivery history returns 409 github_mapping_in_use; edit its
settings for future repairs instead. No retained history is deleted.
| Error | Recovery |
|---|---|
401 unauthenticated | Supply a valid workspace API key. |
403 insufficient_scope | Use a key with the required scope. |
409 workspace_unavailable | Restore access to an active workspace. |
404 integration_not_found | Use an integration in this workspace. |
404 github_repository_unavailable | Restore installation/repository access; this may return 409 if access changes during a save. |
400 invalid_github_repository_cursor | Use the returned repository cursor. |
400 invalid_github_branch_page | Use a page from 1 to 10000 and a valid optional selected branch. |
400 invalid_github_mapping | Correct the fields and repository-relative service path. Unknown fields are rejected. |
400 invalid_github_target_branch | Use a short branch name such as main or release/stable, without refs/heads/. |
422 github_target_branch_unavailable | Refresh branches and choose an accessible existing branch. |
422 github_service_path_not_found | Choose a directory on the target branch or leave the service path empty. |
409 github_mapping_changed | Read the mapping again and review the intervening change. |
409 github_mapping_in_use | Keep the retained history and edit the mapping instead of removing it. |
503 github_not_configured | Contact the deployment operator. |
401 github_unauthorized | Reconnect GitHub access. |
403 github_forbidden | Check the GitHub App permissions and selected repositories. |
404 github_not_found | Refresh repository access. |
422 github_invalid_request | Check the selected repository and branch. |
429 github_rate_limited | Wait for the returned Retry-After seconds (bounded to 1 through 3600), then retry. |
502 github_unavailable or github_invalid_response | Retry later; the existing mapping remains unchanged. |
See Map a repository for the console flow and Open a repair pull request for review delivery.
Read and remove a mapping
Read the saved mapping before editing or removing it:
Code example
curl --fail-with-body \ 'https://api.seamward.com/v1/integrations/wint_payments/github-mapping' \ -H "Authorization: Bearer $SEAMWARD_API_KEY"
# Replace the timestamp with updatedAt from the mapping read.curl --fail-with-body -X DELETE \ 'https://api.seamward.com/v1/integrations/wint_payments/github-mapping' \ -H "Authorization: Bearer $SEAMWARD_API_KEY" \ -H 'If-Match: "2026-10-11T12:00:00.000Z"'Successful removal returns 204 No Content with no response body. Retained
repair history returns 409 github_mapping_in_use instead.
Successful response examples
The following responses use the same repository grant and Integration IDs as the
requests above. Empty repository lists use an empty array. An unmapped Integration
returns mapping: null from the mapping read.
List available GitHub repositories returns 200:
Code example
{ "repositories": [ { "id": "ghrepo_backend", "owner": "acme", "name": "backend", "defaultBranch": "main" } ], "nextCursor": null}List GitHub branches returns 200:
Code example
{ "branches": ["main", "release/stable"], "nextPage": null, "selectedBranchAvailable": null}Read an integration repository mapping returns 200:
Code example
{ "integration": { "id": "wint_payments", "name": "Payments" }, "canManage": true, "mapping": { "id": "ghmap_payments", "servicePath": "services/payments", "deploymentBranch": "release/stable", "createdAt": "2026-10-11T12:00:00.000Z", "updatedAt": "2026-10-11T12:00:00.000Z", "repository": { "id": "ghrepo_backend", "githubRepositoryId": "123456789", "owner": "acme", "name": "backend", "defaultBranch": "main", "visibility": "private", "revokedAt": null, "installation": { "id": "ghi_acme", "accountLogin": "acme", "suspendedAt": null, "disconnectedAt": null, "uninstalledAt": null } } }}Save an integration repository mapping returns 200:
Code example
{ "mapping": { "id": "ghmap_payments", "servicePath": "services/payments", "deploymentBranch": "release/stable", "createdAt": "2026-10-11T12:00:00.000Z", "updatedAt": "2026-10-11T12:00:00.000Z", "repository": { "id": "ghrepo_backend", "githubRepositoryId": "123456789", "owner": "acme", "name": "backend", "defaultBranch": "main", "visibility": "private", "revokedAt": null, "installation": { "id": "ghi_acme", "accountLogin": "acme", "suspendedAt": null, "disconnectedAt": null, "uninstalledAt": null } } }}