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 pathResult
GET /workspacesLists 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 /workspacesCreates 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-accountsLists accounts the user owns or administers, with the active workspace count.
GET /billing-accounts/{accountId}/usageReturns the account's UTC calendar-month observation total, limit, remaining count, and per-workspace contributions.
POST /workspaces/{workspaceSlug}/archiveArchives a workspace owned by the account. New ingestion and customer writes stop.
POST /workspaces/{workspaceSlug}/restoreRestores 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 pathPermissionResultSuccess
GET /workspaces/{workspaceSlug}/advisoriesincidents:readLists up to 100 advisories, most urgent first, with a count per status.200
POST /workspaces/{workspaceSlug}/advisories/{advisoryId}/statusincidents:manageAcknowledges, dismisses or reopens an advisory, with an optional note. Audited.200
GET /workspaces/{workspaceSlug}/integrations/{integrationId}/provider-noticesintegrations:readReturns the API versions a connection is served under and each operation's deprecation notice.200
GET /workspaces/{workspaceSlug}/integrations/{integrationId}/contract-sourceintegrations:readReturns the published contract watched for a connection, or null.200
PUT /workspaces/{workspaceSlug}/integrations/{integrationId}/contract-sourcecontracts:writeSets the address of the provider's OpenAPI document and queues its first check. Audited.200
DELETE /workspaces/{workspaceSlug}/integrations/{integrationId}/contract-sourcecontracts:writeStops watching the published contract. Audited.204
GET /workspaces/{workspaceSlug}/integrations/{integrationId}/healthintegrations:readReturns declared credential expiry dates, the tightest rate limit of the last 24 hours, and webhook signature results.200
POST /workspaces/{workspaceSlug}/integrations/{integrationId}/provider-credentialsintegrations:manageRecords when a credential expires: a kind, a name and a date. Audited.201
PATCH /workspaces/{workspaceSlug}/integrations/{integrationId}/provider-credentials/{credentialId}integrations:manageChanges the name, date or warning period. Audited.200
DELETE /workspaces/{workspaceSlug}/integrations/{integrationId}/provider-credentials/{credentialId}integrations:manageRemoves a recorded expiry date. Audited.204
GET /workspaces/{workspaceSlug}/incidents/{incidentId}/affected-recordsincidents:readPages through an incident's affected records by hashed identifier, with each one's recovery status. Audited.200
GET /workspaces/{workspaceSlug}/incidents/{incidentId}/recovery-planincidents:readReturns the records still missing, in the order to request them again. Audited.200
GET /workspaces/{workspaceSlug}/incidents/{incidentId}/impactincidents:readReturns a plain summary built only from counted facts, and affected accounts by hash.200
GET /workspaces/{workspaceSlug}/incidents/{incidentId}/recovery-plansincidents:readLists 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:readReturns one kept recovery plan with each event's status: pending, recovered or unknown.200
GET /workspaces/{workspaceSlug}/incidents/{incidentId}/reportincidents:readReturns 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:

QueryValuesDefault
statusactive (open and acknowledged), open, acknowledged, dismissed, withdrawn, allactive
environmentIdOne workspace environment, such as env_liveAll
integrationIdOne connectionAll

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.

StatusErrorRecovery
400invalid_advisory_filterUse a listed status and a connection ID that starts with int_.
400invalid_environment_filterUse an environment ID of this workspace.
400invalid_advisory_statusSend open, acknowledged or dismissed, and a note of 500 characters or fewer.
404advisory_not_foundRefresh the list.
409advisory_withdrawnNothing 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.

StatusErrorRecovery
400invalid_contract_sourceSend only url, as a public HTTPS address with no query string.
404integration_not_foundRefresh the connection list.
404contract_source_not_foundNothing was being watched.
409contract_source_protocol_unsupportedOnly 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}
FieldRequiredMeaning
kindYesprovider-token, signing-secret, certificate, api-key or other.
labelYesA name of 1 to 80 characters, unique per kind on the connection.
expiresAtYesA calendar day (2026-11-01) or a UTC date-time.
warnDaysNoHow 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.

StatusErrorRecovery
400invalid_credential_metadataSend only the fields above. Do not send the credential.
404integration_not_foundRefresh the connection list.
404credential_metadata_not_foundRefresh connection health.
409credential_metadata_existsThat kind and name is already recorded. Change its date instead.
409credential_metadata_limit_reachedRemove 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.

StatusErrorRecovery
400invalid_affected_records_queryUse the listed query fields and an unmodified nextCursor.
404incident_not_foundRefresh 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.

OperationEndpoint
Current statusGET /v1/connections/{connectionId}/credentials
Issue a tokenPOST /v1/connections/{connectionId}/credentials/issue
Rotate a tokenPOST /v1/connections/{connectionId}/credentials/rotate
Revoke a tokenPOST /v1/connections/{connectionId}/credentials/revoke
Verify credentialsPOST /v1/connections/{connectionId}/credentials/verify
Recover a receiptGET /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.

StatusErrorRecovery
400invalid_credential_requestCorrect the body. Unknown fields and caller-supplied tenant or mode are rejected.
400idempotency_key_requiredRetain a valid key for the mutation and its identical retries.
401unauthenticatedAuthorize with a current workspace key for an active workspace.
403insufficient_scopeUse the original key with current integrations:manage authorization. A key revoked or expired before or during the transaction also returns this error.
404connection_not_foundRefresh inventory. Foreign workspace IDs are also undisclosed.
404operation_not_foundUse the original key and operation ID. Other keys cannot recover the receipt.
409workspace_unavailableRestore the workspace or resolve its organization binding before a new operation.
409workspace_modes_unavailableResolve canonical migration before using these endpoints.
409connection_configuration_changedConfiguration or authority changed. No token change commits. Read and review current state before creating a new operation.
409credential_already_issuedReview rotation explicitly rather than issuing over an active credential.
409credential_not_issuedIssue a credential before attempting rotation.
409credential_verification_failedCheck the exact Connection key and current private token.
409idempotency_conflictRestore the original action, body, and authorization, or deliberately start a new reviewed operation.

Create an Integration and first connection

Code example

POST /v1/integrations

Create 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"}'
FieldRequiredMeaning
modeYessandbox or live; there is no default or legacy alias.
nameYesWorkflow name, trimmed to 1 to 120 characters.
providerYesProvider identity, trimmed to 1 to 120 characters.
directionYesinbound or outbound.
protocolYeshttp-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.

StatusCodeRecovery
400invalid_integrationCorrect the body and use a canonical mode.
400idempotency_key_requiredSupply 8 to 128 printable non-space ASCII characters.
401unauthenticatedAuthorize with a valid, unexpired workspace API key.
403insufficient_scopeIssue a key with integrations:manage; retry only with the original binding.
409workspace_authorization_changedRefresh and review current permissions before a new creation attempt.
409workspace_modes_unavailableAsk the workspace operator to complete migration or repair the canonical pair.
409workspace_unavailableRestore or reauthorize the workspace before creating a connection.
409idempotency_conflictRestore 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}
FieldCreateUpdateDelete
expectedRevisionRequired reviewed decimal-string epochRequiredRequired
sourceEventTypeRequired, 1 to 120 letters, digits, ., _, :, -OptionalRejected
expectedObjectTypeSame format, requiredOptionalRejected
maxDelayMsRequired integer, 1,000 to 86,400,000OptionalRejected
kindOptional, defaults to expected-outcome; see Rule kindsRejected: a rule's kind never changesRejected
configOptional object, validated for the kind; defaults are filled in the responseOptional; replaces the whole configurationRejected
enabledDefaults to falseOptional explicit booleanRejected

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.

kindQuestionconfig
expected-outcomeDoes each accepted event lead to a record within maxDelayMs?{}
no-duplicatesDoes any event lead to more than one record?windowMs
count-matchOver the window, do events and records agree in number?windowMs, tolerancePercent, minimumEvents
amount-matchOver the window, do the amounts reported on both sides agree?unit (required), windowMs, tolerancePercent, minimumEvents
allowed-sequenceDo a record's reported states follow a declared order?states (required), windowMs
lateness-bandDo records arrive within a soft delay, well before the deadline?softDelayMs (required), latePercent, windowMs, minimumEvents
config fieldTypeDefaultMeaning
windowMsinteger, 60,000 to 604,800,00086,400,000How 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
tolerancePercentnumber, 0 to 1000The difference allowed, as a percentage of the event count or of the received total
minimumEventsinteger, 1 to 100,00020Below this many events in the window the rule raises nothing
unit1 to 32 letters, digits, ., _, -noneOnly amounts reported with this unit are summed
states2 to 12 distinct labelsnoneThe 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
softDelayMsinteger, at least 1 and below maxDelayMsnoneA record later than this is late, though not yet missing
latePercentnumber, above 0 to 10020The 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.

StatusCodeRecovery
400invalid_rule_request, invalid_rule_cursorCorrect strict fields or restart pagination in the exact connection.
400idempotency_key_requiredSupply and retain a correctly formatted mutation key.
401, 403unauthenticated, insufficient_scopeUse the required current workspace authority.
404connection_not_found, expected_outcome_not_found, operation_not_foundCheck exact IDs, workspace and original actor.
409rule_cursor_stale, connection_configuration_changedRead current state and review again.
409expected_outcome_already_existsUse the existing definition or review an explicit update.
409expected_outcome_has_incident_historyPreserve history and review disabling the rule instead.
409idempotency_conflictRecover the original operation. Use a new key for a separately reviewed request.
409workspace_unavailable, workspace_modes_unavailableRestore workspace access or complete canonical mode migration.
429rate_limitedWait 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/environments

Discover 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    }  ]}
FieldTypeMeaning
modelcanonical or legacyCanonical workspaces have exactly Sandbox and Live. Existing workspaces retain their original environments until reviewed migration.
environmentsarray, 1 to 100 itemsExact stored environment records in this workspace.
idstring beginning env_Exact target identifier. Do not replace an identifier based on its display name or classification.
name, slugnonempty stringsDisplay name and navigation slug.
categorysandbox, live, development, production, or stagingStored environment category.
modesandbox or liveClassification for presentation. Development and Staging classify as Sandbox; Production classifies as Live.
isDefaultbooleanSandbox 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.

StatusBodyRecovery
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=sourceCode

Code 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:

StatusFieldsMeaning
not_connectedstatusNo repository mapping is saved. GitHub account access alone does not create a mapping.
connectedstatus, repository, targetBranchThe 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.
unavailablestatus, repository, targetBranchThe 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

StatusBodyMeaning
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"
QueryRequiredValues and behavior
modeYessandbox or live; there is no implicit default.
statusList only, optionalopen or resolved; omit to read both.
limitList only, optional1 to 100; defaults to 25.
cursorList only, optionalUse 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.

StatusErrorRecovery
400invalid_incident_querySupply the explicit mode and valid list fields. Unknown fields are refused.
400invalid_evidence_querySupply only the required mode for a detail read.
400invalid_incident_cursorRestart the list with its original workspace, mode and filter.
401unauthenticatedUse a current workspace key for an active workspace.
403insufficient_scopeObtain the explicit incident or repair read permission from a workspace owner.
404incident_not_found or repair_proposal_not_foundCheck the original workspace, mode, and ID.
409workspace_unavailable or workspace_modes_unavailableRestore 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

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"]}
FieldMeaning
operationIdServer-issued conndel_ review identifier. Retain it for apply and status.
connectionExact connection ID, shared Integration ID, Sandbox or Live mode, canonical environment ID, name, provider, direction, and protocol.
revisionsServer-owned identity, configuration, and destructive-impact revisions. Review changes require a fresh preview.
countsRecords 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.
hazardsPending or claimed background jobs, active replay runs, active repair proposals, queued external deliveries, and references shared with another connection. Any nonzero value blocks apply.
preservedConnectionIdsOther connections present during review. Their operational data and credentials are preserved.
confirmationChallengeExact case-sensitive name and mode the reviewer must confirm.
approvalFingerprintServer-issued binding to this immutable review. Submit it unchanged.
expiresAtTen 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.

RequestRequired constraint
operationId path parameterThe original server-issued conndel_ identifier.
Idempotency-Key header8 to 128 printable non-space ASCII characters. Retain the original value for retries.
approvalFingerprint body fieldExact sha256: fingerprint followed by 64 lowercase hexadecimal characters from the preview.
confirmationName body fieldExact 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.

StatusErrorRecovery
400invalid_connection_deletionSubmit the strict empty preview body or the two apply fields.
400idempotency_key_requiredSupply a valid original key for apply.
401unauthenticatedUse the original active workspace key and active workspace.
403insufficient_scopeObtain current integrations:manage permission.
404connection_not_found or operation_not_foundCheck the workspace, exact connection, operation ID, and original key. Other tenants and keys are not disclosed.
409workspace_unavailable or workspace_modes_unavailableRestore workspace availability or complete the reviewed environment migration.
409deletion_preview_expired or deletion_preview_staleCreate a new preview and review its current impact.
409confirmation_mismatchUse the exact challenge from the reviewed preview.
409idempotency_conflictFor recovery, resend the exact original request and key. For a separate operation, review it and use a new key.
409connection_work_in_progressLet background work and external delivery finish, then preview again. Do not remove delivery evidence to bypass this check.
409connection_references_in_useResolve the cross-connection reference with its owner, then preview again.
429rate_limitedWait 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

StatusErrorRecovery
400invalid_integration_deletionUse an empty preview body or exactly the two apply fields.
400idempotency_key_requiredSupply the original valid request key.
401unauthenticatedUse a current workspace API key.
403insufficient_scopeObtain integrations:manage for this workspace.
404integration_not_found or operation_not_foundCheck the shared ID, original actor and workspace. Foreign IDs are not disclosed.
409workspace_modes_unavailable or workspace_unavailableResolve workspace lifecycle or canonical mode availability before creating another review.
409confirmation_mismatchSubmit the exact server-issued challenge.
409deletion_preview_stale or deletion_preview_expiredCreate and review a new preview.
409idempotency_conflictReuse the original body and credential for a completed operation. Use a new key for a separate operation.
409connection_work_in_progress or connection_references_in_useResolve the reviewed hazards; then review the complete Integration again.
429rate_limitedWait for Retry-After.
503mode_cutover_write_pausedRetry 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  }}
FieldMeaning
periodStart, periodEndInclusive start and exclusive end of one UTC calendar month.
sandbox.recordedCountAccepted Sandbox observations across the account's workspaces.
live.recordedCountAccepted Live observations plus historical usage whose mode cannot be established.
live.unclassifiedCountThe historical portion already included in Live, never an extra charge.
monthlyLimit, remainingCountEach mode's nonnegative remaining allowance. Sandbox defaults to 10,000. Live remaining is enforced on Developer only.
enforcement.scopeAlways sandbox. This schedule cannot activate Live rejection.
enforcement.statusoff before scheduling, grace before its effective date, or active once Sandbox admission checks apply.
publishedAt, cutoverAt, startsAtPolicy publication, reviewed cutover, and enforcement dates, or null.
sandbox.retentionDays, retentionCutoverAtProspective 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.

StatusErrorRecovery
400invalid_account_usage_queryRemove all query fields.
401unauthenticatedUse 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.
404account_not_foundUse the exact account bound to the credential. Foreign accounts are indistinguishable from missing accounts.
409account_access_changedRecheck access and retry the read with current authorization.
503usage_unavailableRetry 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:

ReceiptSession endpoint
CreationGET /workspaces/{workspaceSlug}/integration-operations/{operationId}
Credential changeGET /workspaces/{workspaceSlug}/credential-operations/{operationId}
Completed connection removalGET /workspaces/{workspaceSlug}/connection-deletions/{operationId}
Completed rule mutationGET /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.

EndpointResult
GET /v1/github/repositoriesActive repository grants, up to 100 per page.
GET /v1/github/repositories/:repositoryGrantId/branchesUp to 100 branch names, nextPage, and selectedBranchAvailable.
GET /v1/integrations/:workspaceIntegrationId/github-mappingIntegration identity, canManage, and mapping or null.
POST /v1/integrations/:workspaceIntegrationId/github-mappingValidate and save; returns { "mapping": ... }.
DELETE /v1/integrations/:workspaceIntegrationId/github-mappingRemove an unused mapping; returns 204, including repeated removal.

These endpoints remain supported compatibility aliases:

EndpointResult
GET /v1/connections/:connectionId/github-mappingRead the shared Integration mapping through either mode connection.
POST /v1/connections/:connectionId/github-mappingSave the shared mapping with the same validation and version check.
DELETE /v1/connections/:connectionId/github-mappingRemove 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 fieldMeaning
repositoryGrantIdRequired workspace grant ID, 1 to 128 letters, digits, underscores, or hyphens.
targetBranchOptional short Git branch name, up to 200 characters. Omission preserves the saved branch for the same repository; a new repository uses its default.
servicePathOptional repository-relative directory, up to 240 characters. Empty or null means repository root. No leading slash, . or .. segments.
expectedUpdatedAtOptional 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.

ErrorRecovery
401 unauthenticatedSupply a valid workspace API key.
403 insufficient_scopeUse a key with the required scope.
409 workspace_unavailableRestore access to an active workspace.
404 integration_not_foundUse an integration in this workspace.
404 github_repository_unavailableRestore installation/repository access; this may return 409 if access changes during a save.
400 invalid_github_repository_cursorUse the returned repository cursor.
400 invalid_github_branch_pageUse a page from 1 to 10000 and a valid optional selected branch.
400 invalid_github_mappingCorrect the fields and repository-relative service path. Unknown fields are rejected.
400 invalid_github_target_branchUse a short branch name such as main or release/stable, without refs/heads/.
422 github_target_branch_unavailableRefresh branches and choose an accessible existing branch.
422 github_service_path_not_foundChoose a directory on the target branch or leave the service path empty.
409 github_mapping_changedRead the mapping again and review the intervening change.
409 github_mapping_in_useKeep the retained history and edit the mapping instead of removing it.
503 github_not_configuredContact the deployment operator.
401 github_unauthorizedReconnect GitHub access.
403 github_forbiddenCheck the GitHub App permissions and selected repositories.
404 github_not_foundRefresh repository access.
422 github_invalid_requestCheck the selected repository and branch.
429 github_rate_limitedWait for the returned Retry-After seconds (bounded to 1 through 3600), then retry.
502 github_unavailable or github_invalid_responseRetry 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      }    }  }}