Skip to main content
For AI agents: a documentation index is available at https://docs.coverbase.com/llms.txt — this page is also available in markdown by appending .md to the URL.
This page covers behavior shared by every public API endpoint. Read it once; the per-resource pages assume it.

Base URLs

EnvironmentBase URL
Productionhttps://api.coverbase.app
Sandboxhttps://sandbox.api.coverbase.app
All requests are JSON over HTTPS. Every path is versioned under /v1. The sandbox is a full, isolated copy of the API for integration testing — build and run your integration there first, then repoint the base URL at production.

Authentication

Every request must send a bearer token:
Authorization: Bearer ak_...
Two credential types are accepted:
  • Service-account API key (ak_...) — the primary credential for integrations. A Coverbase admin mints it from the dashboard (Clerk-backed). It is scoped to exactly one organization; everything the key reads or writes is implicitly scoped to that org. It authenticates as a service account. By default a key carries no scopes and can only call ordinary /v1/* routes; an admin can additionally grant it elevated scopes.
  • Logged-in user JWT — the dashboard session token, useful for first-party scripts running as a specific user.
Admins can list, create, rotate, and revoke ak_* keys self-service via the API Keys API — no support ticket required.

API key scopes

Most ak_* keys carry no scopes: they authenticate ordinary /v1/* calls but cannot reach the admin surfaces. An admin can mint a long-lived key with one or both elevated scopes for headless or middleware use:
ScopeGrants
keys:manageList, create, rotate, and revoke ak_* keys via the API Keys API.
audit:readRead the org system audit log (GET /v1/system_audit_log and GET /v1/system_audit_log/metadata).
These admin endpoints accept either an admin dashboard JWT or an ak_* key carrying the required scope, so you can run them headlessly without a short-lived session token. The JWT path is unchanged: key management requires the org admin role; audit-log reads accept the same dashboard roles as before (member, siloed-member, admin, guest). Scopes can only be granted from an admin dashboard session (JWT). A scoped key cannot grant scopes — not to itself and not to another key — so a leaked integration key can never escalate its own access. A request that passes scopes without an admin JWT is rejected with 403 scope_grant_forbidden; a call to a scoped endpoint with a key that lacks the required scope returns 403 insufficient_scope. There is no unauthenticated surface. An invalid/expired/revoked key returns 401 "Invalid or expired API key.". A credential whose role is not allowed on the public API returns 401 "Unauthorized role.".

Public API audit log

Every authenticated call that lands on a /v1/* public-API route is recorded in the org’s activity log as type=external_api_call with target_type=external_api_request. Each record captures:
  • the HTTP method, request path, and matched route template
  • the response status code and duration in milliseconds
  • the names of query parameters used (values are not recorded)
  • the ak_* key ID and human-readable name that authenticated the request
  • the client IP and a per-request correlation ID
Bodies are never persisted. The records surface in the dashboard under Settings → Audit log (filtered to the Integrations group) and through GET /v1/system_audit_log (and GET /v1/system_audit_log/metadata). Both read endpoints accept either a dashboard session JWT (member, siloed-member, admin, or guest role — unchanged from before) or an ak_* key that carries the audit:read scope. Requests that fail authentication before the ak_* key resolves (e.g. an unknown or revoked key returning 401) are not stored in the activity log — they appear in Coverbase’s structured request logs only and can be retrieved via support if needed for incident response.

Test your credentials

GET /v1/utils/authtest returns 200 and {"msg": "Auth successful"} for any valid token.
cURL
curl -H "Authorization: Bearer ak_live_xxx" \
  https://sandbox.api.coverbase.app/v1/utils/authtest

API IP allowlist

An organization can optionally restrict all public API (ak_... key) requests to a set of IPv4/IPv6 CIDRs. This is an org-level control configured by an admin.
  • Default is unrestricted. An empty allowlist ([], the default) imposes no restriction.
  • When configured, a request whose client IP is outside every configured CIDR is rejected with 403:
    { "detail": { "code": "ip_not_allowed", "message": "This request originates from an IP address that is not in your organization's API allowlist." } }
    
  • Fail-closed. If an allowlist is configured but the client IP cannot be determined, the request is denied.
  • Scope. The allowlist applies to every /v1/* public API route. It does not affect dashboard sign-in.
  • Client IP source. The connecting IP is the value the Coverbase load balancer appends to X-Forwarded-For. Clients cannot spoof it by sending their own X-Forwarded-For header.

Configure the allowlist

Set the allowlist from the dashboard (Configuration → Coverbase API → “API IP allowlist”) or via the org update API. Your organization ID is the id field on the org object (also visible in the dashboard).
api_ip_allowlist
string[]
A list of CIDR strings (IPv4 or IPv6). Each entry is validated server-side; an invalid CIDR rejects the whole request with 422. An empty list clears the allowlist (unrestricted).
Set the allowlist
curl -X POST "https://api.coverbase.app/v1/org/cborg_xxx" \
  -H "Authorization: Bearer ak_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "api_ip_allowlist": ["203.0.113.0/24", "198.51.100.7/32"] }'
Read it back
curl "https://api.coverbase.app/v1/org/cborg_xxx" \
  -H "Authorization: Bearer ak_live_xxx" | jq .api_ip_allowlist
Clear it (unrestricted again)
curl -X POST "https://api.coverbase.app/v1/org/cborg_xxx" \
  -H "Authorization: Bearer ak_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "api_ip_allowlist": [] }'
The org object (GET/POST /v1/org/{id}) now includes api_ip_allowlist (a string array; [] when unrestricted). An invalid CIDR returns 422 with a free-form error body (not the coded envelope):
{ "detail": { "error": "Invalid CIDR in api_ip_allowlist", "value": "not-a-cidr" } }
Add the egress IPs of your integration before enabling the allowlist, and keep a dashboard admin path available — a too-narrow allowlist locks every API client out until it is widened or cleared from the dashboard.

Find your current IP

method
GET
GET /v1/whoami/ip
A convenience endpoint (authenticated) that returns the caller’s source IP as the API sees it — the load-balancer-appended, unspoofable value. This is the same value evaluated by the org api_ip_allowlist check, so use it to confirm which IP to allowlist (it backs the dashboard editor’s “Add my current IP” helper).
cURL
curl -sS "https://api.coverbase.app/v1/whoami/ip" \
  -H "Authorization: Bearer ak_live_xxx"   # → {"client_ip":"203.0.113.42"}
client_ip
string | null
The caller’s source IP. null if the IP cannot be determined — the same condition under which a configured allowlist fails closed.

IDs

Resource IDs are opaque, prefixed strings of the form <prefix>_<32 hex characters> (e.g. cbvndr_e448ba62882143f3ba0c140bb2e30162). Treat them as opaque — never parse or construct them.
PrefixResource
cbvndr_Vendor
cbsvc_Service (child of a vendor)
cbuser_User
cbqsrw_Assessment
cbtask_Finding
cboblg_Obligation
cbwr_Workflow run
cbwd_Workflow definition
cbwh_Webhook subscription
cbctst_Control set
cbst_Status
cbtag_Tag
cbsclvl_Scale level (inherent/residual risk level)
cbvdoc_Vendor document
cbbom_Bill of materials
cbbomc_Bill of materials component
cbevt_Webhook delivery event
cbwhd_Webhook delivery (attempt record)
cborg_Organization
Findings are internally called “tasks”, so finding IDs use the cbtask_ prefix. This is expected — a cbtask_... value is a finding ID.

Timestamps

Every timestamp is a Unix epoch value in seconds (integer) — not milliseconds, not ISO-8601. This applies to created_at, updated_at, initiated_at, started_at, completed_at, due_date, occurred_at, and attempted_at.

Idempotency

State-changing POSTs that support safe retries accept an optional Idempotency-Key request header.
EndpointHonors Idempotency-Key
POST /v1/usersYes
POST /v1/vendorsYes
POST /v1/assessmentsYes
POST /v1/workflows/{workflow_name}/runYes
POST /v1/vendors/{vendor_id}/servicesNo (header ignored)
POST /v1/findings, POST /v1/obligationsNo (header ignored)
POST /v1/webhooks and other webhook writesNo (header ignored)
PATCH endpointsNo
Behavior on supported endpoints: keys are scoped to (org, endpoint, key) and expire 24 hours after first use. A replay within the window returns the original response body verbatim (same status) and creates nothing new. A replay is never rejected — there is no conflict error. Sending the header to an endpoint that does not support it is harmless but has no effect.
Use a stable identifier from the source system (a procurement request ID, a ticket number) as the idempotency key so source-side retries deduplicate naturally.

Pagination

List endpoints (GET /v1/findings, GET /v1/obligations) use limit/offset query parameters:
ParamDefaultRange
limit501200
offset0≥ 0
List responses wrap results as { "items": [...], "total": <int>, "limit": <int>, "offset": <int> }. total is the full filtered count (ignoring limit/offset); page until offset + len(items) >= total.

Errors

Application errors use FastAPI’s detail envelope. Most endpoints return a coded object:
{ "detail": { "code": "vendor_not_found", "message": "Vendor not found." } }
POST /v1/assessments is the one exception — creation failures use a free-form error key:
{ "detail": { "error": "..." } }

Validation errors

Bodies/params that fail schema validation return 422 with FastAPI’s standard array:
{
  "detail": [
    { "loc": ["body", "vendor_id"], "msg": "Field required", "type": "missing" }
  ]
}

Common status codes

StatusMeaning
400Rejected by a business rule (bad reference ID, weak secret, non-HTTPS URL, missing vendor/assessment on a finding).
401Missing/invalid credential, or a role not allowed on the public API.
403Authenticated but not authorized — the key lacks a required scope (insufficient_scope), a non-admin tried to grant scopes (scope_grant_forbidden), or the client IP is outside the org allowlist (ip_not_allowed).
404Resource not found or not in the API key’s org.
409Conflict — e.g. user_exists, or running a disabled workflow (workflow_disabled).
422Request body/query failed schema validation.
502A downstream dispatch failed (e.g. workflow_dispatch_failed); safe to retry.