--- --- # Stonewake API The Stonewake API provides read-only programmatic access to the intelligence in your Stonewake workspace: portfolio monitoring, entity risk, and counterparty and country intelligence for export finance teams. Responses are plain JSON over HTTPS. The API is read-only by design: no endpoint creates, changes, or deletes anything in your workspace. ```text Base URL: https://api.stonewake.ai ``` ## Who gets access The API is available to Stonewake customers, by arrangement. Access is enabled per workspace by Stonewake; once enabled, a workspace admin creates and manages API keys in the dashboard. To arrange access for your workspace, contact your Stonewake representative or write to contact@stonewake.ai. ## Built for developers and AI agents The API is designed to be operated by software, whether that software is written by hand or driven by an AI agent. Responses use stable field names, refusals carry machine-readable error codes rather than prose to parse, and this documentation is itself machine-readable: every page is also served as plain markdown at its path plus `.md`, and the site root serves [llms.txt](/llms.txt) (a one-line index of every page) and [llms-full.txt](/llms-full.txt) (all pages as one markdown file). If you are wiring up an agent, the [Quickstart](/quickstart) ends with a block you can paste straight into its context. ## Contents - [Quickstart](/quickstart): from key to first response in five minutes. - [Authentication](/authentication): Bearer keys, the `swk_live_` format, and handling rules. - [Keys and access management](/keys): creating, listing, and revoking keys. - [API reference](/v1): the live /v1 endpoints, pagination, and response posture. - [Errors and rate limits](/errors): status codes, error bodies, and traffic ceilings. --- # Quickstart From nothing to a first response in five minutes. You need API access enabled for your workspace (see [Keys and access management](/keys)) and an admin seat in the dashboard. ## 1. Create a key In the dashboard, a workspace admin creates an API key and copies the full key at creation. It is shown once and cannot be retrieved later. Put it in an environment variable: ```bash export STONEWAKE_API_KEY="swk_live_EXAMPLExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ``` The value above is a placeholder; use your own key. ## 2. Call the API Send the key as a Bearer token in the `Authorization` header: ```bash curl https://api.stonewake.ai/v1/portfolio \ -H "Authorization: Bearer $STONEWAKE_API_KEY" ``` ## 3. Parse the JSON Every endpoint returns plain JSON, so any JSON tooling works: ```bash curl -s https://api.stonewake.ai/v1/portfolio \ -H "Authorization: Bearer $STONEWAKE_API_KEY" | jq . ``` A `401` here means the key was not accepted; the response does not say why, see [Authentication](/authentication). A `403` or `429` is explained under [Errors and rate limits](/errors). The live endpoints are listed in the [API reference](/v1). ## For AI agents Working through an agent instead of writing code? Paste this into its context: ```text Stonewake API Base URL: https://api.stonewake.ai Auth: send an API key on every request as "Authorization: Bearer ". Keys start with swk_live_ and are read-only. Endpoints: read-only GETs under /v1 (portfolio, entity risk, rubrics, registry, search), listed at https://docs.stonewake.ai/v1.md List endpoints page with limit/offset and answer {items, limit, offset, total}. Docs: https://docs.stonewake.ai Docs index for machines: https://docs.stonewake.ai/llms.txt Full docs as one file: https://docs.stonewake.ai/llms-full.txt Every docs page is also plain markdown at its path plus ".md". Errors: 4xx bodies are JSON with a stable error.code; 401 means the credential was refused (no reason is given). Traffic refusals: rate_limited, daily_quota_exceeded, quota_unavailable. Respect Retry-After on 429 responses. ``` An MCP endpoint for the API is planned and will be documented here when it ships. --- # Authentication Every request to the Stonewake API carries an API key as a Bearer token in the `Authorization` header: ```bash curl https://api.stonewake.ai/v1/portfolio \ -H "Authorization: Bearer swk_live_EXAMPLExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ``` The key above is a placeholder. Real keys are the prefix `swk_live_` followed by 40 random characters. ## How keys work - Keys are created by a workspace admin in the dashboard. See [Keys and access management](/keys). - The full key is shown exactly once, at creation. It is stored as a one-way hash and cannot be retrieved again. If a key is lost, create a new one. - Keys are scoped to your workspace. A key can only read data that belongs to the workspace it was created in. - Keys are read-only. A key cannot change anything in your workspace, and it cannot manage keys, including itself. - Keys are revocable instantly. Revocation takes effect on the key's next request. - Keys can carry an optional expiry, set at creation. ## The 401 posture A `/v1` request whose credential is missing or not accepted receives one and the same response, whatever the reason: ```json { "error": { "code": "unauthenticated", "message": "Missing or invalid credentials." } } ``` A missing header, a malformed key, a wrong key, a revoked key, an expired key, and a key from a workspace whose API access has lapsed are indistinguishable in the response. The API does not reveal why a credential was refused, or whether it ever existed. If a previously working key starts receiving 401, check its status in the dashboard. ## Handling rules Treat API keys like passwords. - Never put a key in a URL or query string. It travels in the `Authorization` header only. - Never ship a key in client-side code or anything a browser downloads. Calls belong on your server side. - Keep keys out of source control and logs. Store them in a secrets manager or an environment variable. - Rotate on suspicion. If a key may have been exposed, create a replacement and revoke the old key immediately. --- # Keys and access management API access is enabled per workspace by Stonewake, as part of your arrangement. Once enabled, workspace admins manage keys themselves. To arrange access for your workspace, contact your Stonewake representative or write to contact@stonewake.ai. ## In the dashboard A workspace admin can create, inspect, and revoke the workspace's keys from the dashboard. The full key is displayed once, at creation. What remains visible afterwards is each key's name, its display prefix (the first characters of the key, for matching a key you hold against the list), and its usage metadata. ## Over the API The same operations exist as endpoints under `/org/api-keys`. These are management endpoints: they authenticate with a workspace admin's session credential, the same identity the dashboard uses, not with an API key. API keys are read-only and cannot manage keys. ### Create a key `POST /org/api-keys` ```json { "name": "reporting-pipeline", "expires_at": "2027-01-01T00:00:00Z" } ``` `name` is required, 1 to 120 characters. `expires_at` is optional; when present it must carry a timezone and lie in the future, otherwise the request is refused with 422. On success the response is `201`: ```json { "secret": "swk_live_EXAMPLExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "key": { "id": "6e9c8a2f-6d1c-4a0b-9d5e-3f2a1b0c9d8e", "name": "reporting-pipeline", "key_prefix": "swk_live_EXA", "role": "viewer", "scopes": ["read"], "created_by": "admin@example.com", "created_at": "2026-08-13T09:30:00Z", "last_used_at": null, "expires_at": "2027-01-01T00:00:00Z", "revoked_at": null } } ``` `secret` is the full key and appears only in this response. Store it immediately; it is not retrievable afterwards. If API access is not enabled for the workspace, creation is refused with `403`: ```json { "detail": "API access is not enabled for your workspace. Contact your Stonewake representative." } ``` ### List keys `GET /org/api-keys` responds `200` with the workspace's keys, newest first, and the workspace's API access status: ```json { "api_access_active": true, "api_access_expires_at": "2026-11-13T00:00:00Z", "keys": [ { "id": "6e9c8a2f-6d1c-4a0b-9d5e-3f2a1b0c9d8e", "name": "reporting-pipeline", "key_prefix": "swk_live_EXA", "role": "viewer", "scopes": ["read"], "created_by": "admin@example.com", "created_at": "2026-08-13T09:30:00Z", "last_used_at": "2026-08-13T11:02:41Z", "expires_at": "2027-01-01T00:00:00Z", "revoked_at": null } ] } ``` Revoked keys stay in the list: keys are never deleted, so the list is a complete history. `api_access_expires_at` is populated only while access is active and carries an end date; otherwise it is `null`. Listing works even when the workspace's API access has lapsed, so you can always see your keys. ### Revoke a key `POST /org/api-keys/{key_id}/revoke` responds `200` with the key's metadata, `revoked_at` now set. - Revocation is immediate: the key is refused from its next request onwards. - Revoking an already revoked key changes nothing; the original `revoked_at` is kept. - An unknown key id responds `404` with `{ "detail": "API key not found." }`. A key id belonging to another workspace answers the same 404; the API does not confirm the existence of anything outside your workspace. - Revocation works regardless of the workspace's API access status. You can always kill a key. ### Key object fields | Field | Type | Meaning | | --- | --- | --- | | `id` | string | The key's identifier, used in the revoke path. | | `name` | string | The label given at creation. | | `key_prefix` | string | The first characters of the full key, for matching. | | `role` | string | Always `viewer` in this version. | | `scopes` | array | Always `["read"]` in this version. | | `created_by` | string | The admin who created the key. | | `created_at` | timestamp | When the key was created. | | `last_used_at` | timestamp or null | When the key last authenticated a request. Refreshed periodically, so it can lag recent use by a short interval. | | `expires_at` | timestamp or null | The optional expiry set at creation. | | `revoked_at` | timestamp or null | When the key was revoked, if it was. | --- # API reference The `/v1` surface is the versioned, read-only API for customer machines: eleven GET endpoints across five families. Every endpoint authenticates as described under [Authentication](/authentication), answers plain JSON, and changes nothing in your workspace. ## Portfolio - `GET /v1/portfolio`: the monitored book, one item per exposure with its current status, score, top signals, and last status change. Ordered by severity, red first. `include_inactive=true` includes deactivated exposures. - `GET /v1/portfolio/summary`: status counts over the active book plus the number of status changes in the last 30 days. - `GET /v1/portfolio/transitions`: the status-change feed, newest first. `since` (an ISO 8601 timestamp) bounds the feed. ## Entity risk - `GET /v1/entities/{node_id}/risk`: your team's current risk assessment for one company, with the scored signal rows and their citations. The response distinguishes not held, held but not yet assessed, and assessed. - `GET /v1/risk-assessments/{assessment_id}`: one assessment by id. Assessments are append-only history, so the id is a stable permalink. ## Scoring rubrics - `GET /v1/risk-profiles`: your team's rubric versions, newest first. - `GET /v1/risk-profiles/{version}`: one version with its full configuration. - `GET /v1/risk-config`: the rubric currently scoring your team's exposures. `version` is `null` while the built-in default is live. ## Counterparty registry - `GET /v1/entities`: the counterparty registry (export credit agencies, banks, sponsors), in name order. - `GET /v1/entities/{entity_id}`: one counterparty's identity row. `{entity_id}` is the registry's own id space. It is not a graph node id: the risk endpoints and search speak `node_id`. ## Search - `GET /v1/search?q=...`: find a company by name or identifier (`lei:5493...`, a bare registration number, or a name). Hits carry the `node_id` the risk endpoints speak, plus jurisdiction and registration data. `connected_sources` names the sources the query ran against, so an empty result is attributable. ## Pagination List endpoints accept `limit` and `offset` and answer one envelope: ```json { "items": [], "limit": 50, "offset": 0, "total": 0 } ``` `total` is the exact count after filtering. The maximum `limit` is 100, except `/v1/search`, which caps at 25 hits. ## Errors Every 4xx from a `/v1` endpoint carries the structured envelope described under [Errors and rate limits](/errors). A 404 is canonical: it never confirms whether a resource exists outside your workspace. ## Posture - Read-only: `/v1` accepts GET requests only. - Companies only: person data never appears on this surface, whatever your workspace's settings. - Cited: responses that carry findings carry their source citations. ## Coverage More endpoint families will be added under `/v1`. Within v1, additions never rename or remove existing routes, fields, or error codes. --- # Errors and rate limits ## Error bodies Every 4xx refusal from a `/v1` endpoint carries one structured `error` object with a stable machine-readable code: ```json { "error": { "code": "not_found", "message": "Resource not found." } } ``` The code and its message are canonical per status code, never per call site, so a refusal does not reveal why a particular request was turned away. Codes served today: `invalid_request` (400), `unauthenticated` (401), `forbidden` (403), `not_found` (404), `method_not_allowed` (405), `conflict` (409), `rate_limited` (429). Traffic refusals (the ceilings below) carry the same envelope plus a `retry_after_seconds` field: ```json { "error": { "code": "rate_limited", "message": "Rate limit exceeded. Retry after the number of seconds in Retry-After.", "retry_after_seconds": 21 } } ``` Branch on `error.code`, never on message text. Codes are stable: new codes may be added over time, existing codes are never renamed. The management endpoints under `/org/api-keys` carry a single `detail` message instead: ```json { "detail": "API key not found." } ``` ## Status codes | Status | Meaning | | --- | --- | | `200` | The request succeeded. | | `401` | The credential was missing or not accepted. One body for every refusal cause; see [Authentication](/authentication). | | `403` | The action is not available to your workspace. Creating a key without API access enabled answers 403; see [Keys and access management](/keys). | | `404` | The resource does not exist in your workspace. The API does not distinguish between a resource that does not exist and one that belongs to another workspace. | | `405` | The method is not supported. `/v1` endpoints accept GET only. | | `422` | The request failed validation. The body names the parameter or field. | | `429` | A traffic ceiling was reached; see below. | | `503` | The request could not be served safely; retry later. See `quota_unavailable` below. | ## Rate limits Two ceilings apply to every request an API key authenticates: - a per-key rate limit over a one-minute window, and - a per-workspace daily quota across all the workspace's keys, which resets at midnight UTC. The limits themselves are part of your arrangement and are not fixed numbers in this documentation. The per-minute ceiling that applies to your key is visible in the response headers below. ### Response headers Every response to a request an API key authenticates carries the state of the minute window, traffic refusals included: | Header | Meaning | | --- | --- | | `X-RateLimit-Limit` | The per-minute ceiling for this key. | | `X-RateLimit-Remaining` | Requests left in the current window. | | `X-RateLimit-Reset` | When the current window resets, as a Unix timestamp in seconds. | 429 responses additionally carry a `Retry-After` header, in seconds. ### 429 with code `rate_limited` The per-key rate limit was exceeded. The window is short; wait for `Retry-After` seconds and retry. ```json { "error": { "code": "rate_limited", "message": "Rate limit exceeded. Retry after the number of seconds in Retry-After.", "retry_after_seconds": 21 } } ``` ### 429 with code `daily_quota_exceeded` The workspace's daily quota was exhausted. `Retry-After` counts to midnight UTC, when the quota resets. ```json { "error": { "code": "daily_quota_exceeded", "message": "Daily quota exceeded. The quota resets at UTC midnight.", "retry_after_seconds": 14580 } } ``` ### 503 with code `quota_unavailable` Usage accounting could not be reached, so the request was refused rather than served unmetered. There is no `Retry-After`; retry with backoff. ```json { "error": { "code": "quota_unavailable", "message": "The daily quota could not be verified. The request was refused." } } ``` ## Retry guidance - On 429, wait for the number of seconds in `Retry-After`, then retry. - On 503, retry with exponential backoff and a bounded number of attempts. - Pace steady workloads against `X-RateLimit-Remaining` rather than reacting to refusals.