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
- Open the Integration's Collector setup tab.
- Copy its public Connection key (
sw_conn_v1...). - 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. - 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:
| Scope | Grants |
|---|---|
observations:read | List redacted observations, evidence links, and usage |
incidents:read | Read explicit-mode incident lists and bounded evidence references. |
repairs:read | Read explicit-mode proposal state and validation/decision provenance. |
contracts:read | List and inspect immutable versions and operation schemas |
contracts:write | Register a new draft version |
integrations:read | List workspace Integrations and their environment connections |
integrations:manage | Create canonical workflows, review/apply a missing mode connection, and read the original key’s operation receipts |
contracts:activate | Activate 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
| Header | Value | Format |
|---|---|---|
X-Seamward-Connection | Your Integration's public Connection key | sw_conn_v1.<integration-token>.<integration-id> |
Authorization | Bearer and your secret ingest token | sw_ing_ followed by 43 characters |
X-Seamward-Signature | Request 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:
| Body | Meaning |
|---|---|
{"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
- Check a connection: validate these credentials without recording traffic.
- Ingest observations: the endpoint these headers authenticate.
- Contract API: scoped contract registration and activation.
