Contracts

The Contract API imports operation-scoped schemas, preserves every version as immutable evidence, and changes analysis only after explicit activation. For an HTTP API Integration, OpenAPI imports supported JSON request and response operations from paths. For an HTTP webhook Integration, it imports supported message operations from webhooks. A standalone JSON Schema must be bound to one explicit operation selector.

The legacy browser-session POST /contracts writer is retired. Every otherwise valid, owned request returns 409 canonical_lifecycle_required without changing contracts, activation history or audit. It retains deprecation headers until removal after 30 November 2026. Use the public reviewed draft-registration and activation routes documented below. Existing collector-authenticated baseline reads remain available for historical contracts.

For a canonical Sandbox or Live connection, the legacy route returns 409 with canonical_lifecycle_required and writes nothing. Register a draft through the public API, review it, and activate it explicitly. The legacy route cannot activate a contract in either canonical mode.

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

Authentication and scopes

Workspace automation sends a workspace API key:

Code example

Authorization: Bearer $SEAMWARD_API_KEY

Use contracts:read for listing, detail and drafting from traffic, contracts:write for draft registration, and contracts:activate for activation or rollback. One key may carry multiple scopes. An ingest token cannot call these routes.

The seamward CLI may also list contracts with its browser-approved setup OAuth token carrying seamward:setup:contracts:read. That token is bound to one environment and an approved Integration. It cannot register or activate a contract through these public routes. Requesting another Integration returns integration_scope_mismatch.

Copy {integrationId} from the integration's Overview tab under Connection status, using the Integration ID code block. It is a non-secret identifier. Sandbox tooling can derive the same ID from the public Connection key, so it does not need a second configuration value.

List contracts

Code example

GET /v1/integrations/{integrationId}/contracts?limit=25&cursor={opaqueCursor}

The response identifies the active version and returns immutable versions in descending createdAt, then descending id, order. limit defaults to 25 and accepts 1 through 100. When nextCursor is not null, pass it unchanged as cursor to read the next page. A cursor is scoped to one integration.

Code example

{  "integration": { "id": "int_orders", "name": "Orders" },  "activeContractVersionId": "contract_v1",  "contracts": [    {      "id": "contract_v1",      "declaredVersion": "2026-08",      "fingerprint": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",      "importedFrom": "json-schema",      "lifecycleStatus": "active",      "activatedAt": "2026-08-20T12:00:00.000Z",      "supersededAt": null,      "createdAt": "2026-08-20T11:55:00.000Z",      "operations": [        {          "id": "operation_create_order_201",          "operationKey": "createOrder:response:201",          "direction": "outbound",          "protocol": "http-api",          "method": "POST",          "routeTemplate": "/orders",          "eventType": null,          "payloadLocation": "response",          "statusSelector": "201",          "fingerprint": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",          "schema": { "type": "object", "required": ["id"] }        }      ]    }  ],  "nextCursor": null}

Register a draft

A reviewed automation may include expectedConnectionRevision, the opaque string from connection metadata. The API checks this epoch while holding the connection row lock and returns connectionRevision from the committed draft transaction. Any intervening configuration change returns connection_configuration_changed (409) without writing a draft. Keep the returned epoch for the next reviewed mutation. If a response is lost or the epoch no longer matches, inspect the current state and review again. Do not replace the expected epoch with an arbitrary fresh read to revive an old approval.

Code example

POST /v1/integrations/{integrationId}/contractsContent-Type: application/json

OpenAPI example:

Code example

{  "declaredVersion": "2026-08",  "expectedScope": { "direction": "outbound", "protocol": "http-api" },  "format": "openapi",  "document": {    "openapi": "3.1.0",    "info": { "title": "Orders", "version": "2026-08" },    "paths": {      "/orders": {        "post": {          "operationId": "createOrder",          "responses": {            "201": {              "content": {                "application/json": {                  "schema": { "type": "object", "required": ["id"] }                }              }            }          }        }      }    }  }}

JSON Schema example:

Code example

{  "declaredVersion": "2026-08",  "expectedScope": { "direction": "outbound", "protocol": "http-api" },  "format": "json-schema",  "document": { "type": "object", "required": ["id"] },  "operation": {    "method": "POST",    "routeTemplate": "/orders",    "payloadLocation": "response",    "statusSelector": "201"  }}

Registration returns 201 with lifecycleStatus: "draft". It does not enqueue analysis or replace the active version.

expectedScope is an optimistic safety guard. Seamward returns 409 integration_scope_mismatch before writing a version or audit event when the target Integration's direction or protocol differs. The current CLI and console always send it. The field remains optional only so existing Contract API clients can migrate without a breaking change; new automation should always include it.

Code example

{  "contract": {    "id": "contract_v2",    "declaredVersion": "2026-09",    "fingerprint": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",    "importedFrom": "json-schema",    "lifecycleStatus": "draft",    "operationCount": 1,    "createdAt": "2026-08-21T09:30:00.000Z"  }}

Registering on one connection also gives the Integration's other mode connection the same version as a draft, in the same transaction. Activation stays separate per connection. A version label means one thing across both modes: if the other connection already holds the declaredVersion with different content, registration returns 409 contract_version_already_exists and writes nothing.

Registration is safe to retry only while the existing version is still a draft and the retry is identical. Registering the same document from the other mode's connection is such a retry: it returns the draft that connection already holds. If the same integration and declaredVersion already have a draft with the same document fingerprint, import format, operation count, and every operation selector and schema fingerprint, the API returns 201 with that existing draft. It does not create another version or audit event. A changed document, format, selector, or schema returns 409 contract_version_already_exists. An already active or superseded version is not treated as an identical draft retry.

Supported OpenAPI methods are GET, POST, PUT, PATCH, and DELETE. OpenAPI can be registered only against an HTTP API or HTTP webhook Integration. For an HTTP API Integration, Seamward imports paths operations. For an HTTP webhook Integration, it imports webhooks operations. Queue and scheduled-feed Integrations use an operation-bound JSON Schema. Imports read JSON and application/*+json schemas, local references, and the supported JSON Schema subset: type, properties, required, items, enum, and additionalProperties. Remote references, composition keywords such as allOf, oneOf, and anyOf, and non-JSON media types are not imported.

Get one version

Code example

GET /v1/integrations/{integrationId}/contracts/{contractVersionId}

The response contains contract with the same immutable version shape returned by the list endpoint. A version id from another integration or workspace returns contract_not_found or integration_not_found without revealing foreign records.

Code example

{  "contract": {    "id": "contract_v1",    "declaredVersion": "2026-08",    "fingerprint": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",    "importedFrom": "json-schema",    "lifecycleStatus": "active",    "activatedAt": "2026-08-20T12:00:00.000Z",    "supersededAt": null,    "createdAt": "2026-08-20T11:55:00.000Z",    "operations": [      {        "id": "operation_create_order_201",        "operationKey": "createOrder:response:201",        "direction": "outbound",        "protocol": "http-api",        "method": "POST",        "routeTemplate": "/orders",        "eventType": null,        "payloadLocation": "response",        "statusSelector": "201",        "fingerprint": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",        "schema": { "type": "object", "required": ["id"] }      }    ]  }}

Activate a version

Code example

POST /v1/integrations/{integrationId}/contracts/{contractVersionId}/activateContent-Type: application/json

Code example

{  "expectedActiveContractVersionId": "contract_v1",  "reason": "promote"}

Reviewed automation may also include expectedConnectionRevision. It checks all connection configuration alongside expectedActiveContractVersionId and returns the committed connectionRevision. Stale configuration returns connection_configuration_changed (409), with no activation, audit or enqueue. This opaque connection epoch is separate from the integer analysis revision.

Use null when the integration has no active version. Use reason: "rollback" when reactivating a prior immutable version. Activation supersedes the previous version, records a new revision and audit event, and enqueues reanalysis in one database transaction.

Code example

{  "activationId": "activation_contract_v2_revision_8",  "activeContractVersionId": "contract_v2",  "previousActiveContractVersionId": "contract_v1",  "revision": 8}

If another deployment changed the active version first, the request returns 409:

Code example

{  "error": "active_contract_changed",  "activeContractVersionId": "contract_v3"}

Read the current version, review it, and retry with a new explicit precondition. Do not retry blindly.

Draft from traffic

Code example

POST /v1/integrations/{integrationId}/contracts/draft-from-trafficContent-Type: application/json

Code example

{ "windowDays": 7 }

Requires contracts:read. The route drafts a contract for one connection from the payload shapes already observed on it and compares the draft with the active version. It saves nothing: to keep a draft, send one of the returned documents to Register a draft.

windowDays is optional, from 1 to 90, and defaults to 7. Shapes last seen inside the window are used. An empty body is valid.

Code example

{  "connection": {    "id": "int_orders",    "name": "Paystack webhook",    "mode": "sandbox"  },  "windowStart": "2026-09-30T12:00:00.000Z",  "windowDays": 7,  "payloads": 2104,  "operations": [    {      "method": "POST",      "routeTemplate": "/webhooks/paystack",      "eventType": "charge.success",      "payloadLocation": "message",      "statusSelector": null,      "schema": {        "type": "object",        "properties": {          "email": { "type": "string" },          "reference": { "type": "string" }        },        "required": ["reference"]      },      "evidence": {        "payloads": 2104,        "distinctShapes": 2,        "firstObservedAt": "2026-10-01T08:00:00.000Z",        "lastObservedAt": "2026-10-07T11:45:00.000Z",        "requiredDecided": true,        "fields": [          {            "path": "$.email",            "types": ["string"],            "presentIn": 1980,            "outOf": 2104,            "required": false,            "mixedTypes": false,            "insideArray": false          },          {            "path": "$.reference",            "types": ["string"],            "presentIn": 2104,            "outOf": 2104,            "required": true,            "mixedTypes": false,            "insideArray": false          }        ]      },      "comparison": {        "changes": [          { "path": "$.email", "kind": "field-added", "class": "added" }        ],        "highest": "added",        "newOperation": false      }    }  ],  "carriedOperations": [],  "documents": [    {      "format": "openapi",      "document": {        "openapi": "3.1.0",        "info": { "title": "Paystack webhook", "version": "1.1.0" },        "webhooks": {          "charge.success": {            "post": {              "x-seamward-event-type": "charge.success",              "x-seamward-route-template": "/webhooks/paystack",              "requestBody": {                "content": {                  "application/json": {                    "schema": {                      "type": "object",                      "properties": {                        "email": { "type": "string" },                        "reference": { "type": "string" }                      },                      "required": ["reference"]                    }                  }                }              }            }          }        }      }    }  ],  "activeVersion": { "id": "contract_v1", "declaredVersion": "1.0.0" },  "highest": "added",  "proposedVersion": "1.1.0"}
FieldMeaning
payloadsPayloads the draft rests on, across all operations.
operationsOne entry per operation that had traffic in the window: the drafted schema, the evidence for it, and the comparison with the active version.
evidence.fieldsEach field with its types and how many payloads had it. requiredDecided is false below 50 payloads, when nothing is newly marked required.
comparisonnull when no version is active. Each change has a class: added, loosened, tightened or retyped. newOperation is true when the active version does not describe the operation.
openFindingPresent on a change that matches a change Seamward detected inside the window. Registering and activating that change makes the contract agree with the traffic that raised it.
carriedOperationsOperations of the active version that had no traffic in the window. A single OpenAPI document keeps them exactly as they are, so saving it never drops a quiet operation. JSON Schema documents hold one operation each and cannot keep them: activating one replaces the version that checks these.
documentsThe drafted and carried operations in a form the registration route accepts. HTTP API operations, and webhook events that each have an event type, fit one OpenAPI document. Anything else is one JSON Schema document each.
highestThe most significant change class across all operations, or null when the draft matches the active version.
proposedVersionThe next label when the active version is a version number: a minor step for added and loosened, a major step for tightened and retyped. null when nothing changed, no version is active, or the label is not numeric.

A draft describes field names and types only. Seamward reads the shape of a payload, never its values, so a draft has no enumerations and no formats.

Errors

StatusBodyMeaning
400{"error":"invalid_contract"}The document, selector, or supported schema subset is invalid
400{"error":"invalid_contract_query"}A list limit or query parameter is invalid
400{"error":"invalid_contract_cursor"}The cursor is malformed or belongs to another integration
400{"error":"invalid_contract_activation"}The activation body or reason is invalid
400{"error":"invalid_contract_draft_request"}The draft body has an unknown field or a window outside 1 to 90
401{"error":"unauthenticated"}The key is missing, malformed, expired, or revoked
403{"error":"insufficient_scope"}The key lacks the endpoint scope
403{"error":"integration_scope_mismatch"}Setup OAuth is missing the requested Integration
404{"error":"integration_not_found"}The integration is absent from this workspace
404{"error":"contract_not_found"}The version is absent from this integration
409{"error":"contract_version_already_exists"}The declared version exists with different content or selectors
409{"error":"integration_scope_mismatch"}The expected direction or protocol differs from the Integration
409{"error":"active_contract_changed"}The optimistic concurrency precondition is stale
409{"error":"contract_not_activatable"}The lifecycle transition is invalid
409{"error":"contract_draft_traffic_too_large"}More than 1,000 distinct payload shapes are in the draft window
503{"error":"service_unavailable"}Reanalysis is unavailable, so activation was not attempted

Next steps