Authentication

Check a connection uses the same collector headers and signs an empty body. Its response identifies the credential's Integration and environment without recording traffic. It does not accept CLI login tokens.

Seamward has separate credentials for ingestion, workspace automation, account usage, and setup authorization. Ingestion uses an Integration-owned HMAC-signed ingest token. Headless integration and contract automation uses a scoped workspace API key. Automatic coding-agent setup uses a short-lived OAuth token bound to one exact workspace environment. Account usage uses a separate sw_acct_ key with account:usage:read, issued by a current account owner or billing administrator. Manage account keys. Browser cookies are never accepted by public API routes.

Authorize automatic setup

Run seamward login. It shows the authorization URL, waits for an Enter press before opening the hosted Seamward approval flow, uses the OAuth device authorization grant, and stores its short-lived credential outside the repository. The browser remains on Seamward and shows a completion screen while the CLI polls securely for the approved grant. Then run seamward setup from the service directory. Setup and every other supported command remain in the terminal. Users do not create or copy a workspace API key for this flow.

The resulting bearer token is accepted only by the reviewed setup and Integration-management routes covered by its setup scopes for that workspace environment. See Setup authorization for the target claims, context response, errors, and revocation behavior.

Get ingestion credentials

  1. Open the Integration's Collector setup tab.
  2. Copy its public Connection key (sw_conn_v1...).
  3. Copy its ingest token (sw_ing_...) when it is issued. It is shown once and stored only as a hash; put it straight into your backend secret store.
  4. If a token was not saved or may be exposed, rotate it from the same Integration. Rotation invalidates the prior token immediately and shows the replacement once.

The ingest token is the bearer credential and the signing secret. It can deliver observations and check its own connection. It cannot read observation evidence or manage contracts.

Get a workspace API key

Workspace owners open Workspace settings → API keys and create a scoped key. The sw_api_... secret is shown once. Store it immediately; Seamward stores only its SHA-256 hash. The authenticated management endpoints below provide the same lifecycle for automation and administrative tooling.

Each key requires an expiry within one year and one or more scopes:

ScopeGrants
observations:readList redacted observations, evidence links, and usage
incidents:readRead explicit-mode incident lists and bounded evidence references.
repairs:readRead explicit-mode proposal state and validation/decision provenance.
contracts:readList and inspect immutable versions and operation schemas
contracts:writeRegister a new draft version
integrations:readList workspace Integrations and their environment connections
integrations:manageCreate canonical workflows, review/apply a missing mode connection, and read the original key’s operation receipts
contracts:activateActivate or roll back a version and enqueue reanalysis

Scopes are fixed when the key is created. To add or remove access, create a replacement key with the required scopes, update the consuming automation, and revoke the previous key only after the replacement works. The local assisted setup lifecycle uses OAuth instead of a workspace API key. For advanced headless contract automation, create a key with the five scopes listed above; choose Manage contracts and read observations in the console.

The authenticated management request is:

Code example

POST /workspaces/{workspaceSlug}/api-keysContent-Type: application/json
{  "name": "Reliability automation",  "scopes": ["integrations:read", "observations:read", "contracts:read", "contracts:write", "contracts:activate"],  "expiresAt": "2026-11-18T12:00:00.000Z"}

List key metadata with GET /workspaces/{workspaceSlug}/api-keys. Revoke a key with POST /workspaces/{workspaceSlug}/api-keys/{apiKeyId}/revoke. These three management routes require an authenticated owner session and never accept the API key they manage.

Observation and contract requests carry one header and do not use HMAC signing:

Code example

Authorization: Bearer sw_api_...........................................

A key cannot cross its workspace boundary. Revoked and expired keys return 401; a valid key without the required scope returns 403 with {"error":"insufficient_scope"}.

Browser-approved setup authorization uses separate seamward:setup:* scopes. Integration deletion requires seamward:setup:integrations:delete in addition to read access. Existing setup grants are not silently expanded. Run seamward login again to approve the added scope before deleting through the CLI or setup MCP.

Required headers

HeaderValueFormat
X-Seamward-ConnectionYour Integration's public Connection keysw_conn_v1.<integration-token>.<integration-id>
AuthorizationBearer and your secret ingest tokensw_ing_ followed by 43 characters
X-Seamward-SignatureRequest signature (below)t=<unix seconds>,v1=<64 lowercase hex>

Request signing

The signature is an HMAC-SHA256 digest of the current Unix timestamp and the exact request body bytes, keyed with your ingest token:

Code example

import { createHmac } from "node:crypto";
function signBody(body: string, ingestToken: string): string {  const timestamp = Math.floor(Date.now() / 1000);  const digest = createHmac("sha256", ingestToken)    .update(`${timestamp}.${body}`, "utf8")    .digest("hex");  return `t=${timestamp},v1=${digest}`;}

The same in Python, dependency-free:

Code example

import hashlib, hmac, time
def sign_body(body: str, ingest_token: str) -> str:    ts = int(time.time())    digest = hmac.new(        ingest_token.encode(), f"{ts}.{body}".encode(), hashlib.sha256    ).hexdigest()    return f"t={ts},v1={digest}"

Two rules matter in practice:

  • Sign the exact string you send. Serialize the body once, sign that string, and send the same string. Re-serializing after signing changes the bytes and fails verification.
  • Signatures expire. Timestamps more than 300 seconds from server time are rejected, so sign immediately before sending and keep server clocks synchronized.

Signature failures

Authentication failures return 401 with a stable body, and the reason field is the fastest debugging signal:

BodyMeaning
{"error":"unauthenticated"}A header is missing or malformed, a key fails its format, or the token was revoked
{"error":"unauthenticated","reason":"malformed"}The signature header does not match t=...,v1=...
{"error":"unauthenticated","reason":"stale"}The timestamp is outside the 300-second window: check clocks
{"error":"unauthenticated","reason":"mismatch"}The digest is wrong: wrong secret, or signed bytes differ from sent bytes

Temporary write maintenance

An authenticated request can return 503 {"error":"mode_cutover_write_paused"} with Retry-After: 1 during maintenance. Keep the original request body, observation event IDs and operation idempotency key. Wait at least the indicated seconds, then retry with bounded backoff. Regenerate an expiring request signature for each ingest attempt. Read retained operation status after an unknown result before starting a different mutation. This response does not require rotating credentials.

Next steps