Overview
The Corsair REST API provides programmatic access to CPOE signing and verification. All endpoints follow a consistent JSON envelope with request IDs for tracing.
Base URL: https://api.grcorsair.com
Versioning: Unversioned endpoints and /v1 paths are both supported today and behave the same. Use /v1 for forward compatibility.
Persistence
The hosted API requires Postgres for key storage, SCITT entries, and audit trails. Set DATABASE_URL in production. CLI-only usage is file-based and does not require a database.
Data Capture Foundation
The API now includes an append-only internal event_journal table for hosted-operation metadata.
This is not a public API endpoint; it is persisted server-side for analytics/audit use.
- Stream ownership is isolated via
ssf_streams.owner_type+owner_id(api_key,oidc,legacy). - SCITT continuity is hardened with a unique
scitt_entries.tree_sizeconstraint. - Journal rows are immutable in normal operation (
UPDATE/DELETEare blocked by DB rules).
Current journal event types:
sign.success,sign.failureissue.success,issue.failureverify.success,verify.failurescitt.entry.registeredssf.stream.created,ssf.stream.read,ssf.stream.updated,ssf.stream.deletedtrusttxt.hosted.upsert,trusttxt.hosted.verified,trusttxt.hosted.public_fetch
Authentication
Protected endpoints require a Bearer token in the Authorization header. Use an API key or an OIDC token (when OIDC is configured).
OIDC issuers are configured via CORSAIR_OIDC_CONFIG and may include claimMapping and requireJti.
curl -X POST https://api.grcorsair.com/sign \
-H "Authorization: Bearer <api-key-or-oidc-token>" \
-H "Content-Type: application/json" \
-d '{"evidence": {...}}'
Public endpoints (verify, grc translate, health, formats) require no authentication.
| Endpoint | Auth Required |
|---|---|
GET /health | No |
GET /v1/health | No |
GET /formats | No |
GET /intelligence/events | Yes |
POST /sign | Yes |
POST /verify | No |
POST /grc/translate | No |
POST /issue | Yes |
POST /onboard | Yes |
POST /trust-txt/host | Yes |
GET /trust-txt/host/:domain | Yes |
POST /trust-txt/host/:domain/verify | Yes |
GET /trust/:domain/trust.txt | No |
GET /scitt/entries | No |
POST /scitt/entries | Yes |
POST /ssf/streams | Yes |
Response Format
Successful responses return a JSON object specific to the endpoint.
Errors return a JSON object with an error string.
{ "error": "Missing required field: \"evidence\"" }
POST /sign
Sign evidence into a CPOE (JWT-VC). Accepts raw tool output via mapping packs or generic format. Requires authentication (API key or OIDC token).
To support additional tools without code changes, configure the mapping registry on the API server via CORSAIR_MAPPING_DIR, CORSAIR_MAPPING_FILE, or corsair mappings add. Build packs with corsair mappings pack and sign them with corsair mappings sign. Mappings are evaluated by priority (higher wins), then filename order. Signed packs are verified when CORSAIR_MAPPING_PACK_PUBKEY is set.
curl -X POST https://api.grcorsair.com/sign \
-H "Authorization: Bearer $AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"evidence": {
"metadata": { "title": "S3 Controls", "issuer": "Acme", "scope": "AWS Production" },
"controls": [
{ "id": "S3-001", "description": "Bucket versioning enabled", "status": "pass" }
]
},
"scope": "AWS Production - S3 Controls",
"expiryDays": 90
}'
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
evidence | object or string | Yes | Raw tool output (mapping pack or generic) |
format | string | No | Force generic format (bypasses mapping registry) |
did | string | No | Override issuer DID (defaults to did:web:<CORSAIR_DOMAIN>) |
scope | string | Conditional | Required if evidence.metadata.scope is missing |
expiryDays | number | No | CPOE validity in days (default: 90) |
strict | boolean | No | Enforce minimum ingestion contract (fail on missing metadata) |
dryRun | boolean | No | Parse without signing |
dependencies | array | No | Dependency proofs to attach (trust graph) |
registerScitt | boolean | No | Register signed CPOE in SCITT log (default: false). Also accepts header x-corsair-register-scitt: true. |
strict enforces the minimum ingestion contract (issuer/auditor, assessment date, scope). If false or omitted, the API returns warnings instead of failing the request.
Response (issuer defaults to did:web:<CORSAIR_DOMAIN> if not provided):
{
"ok": true,
"data": {
"cpoe": "eyJhbGciOiJFZERTQSIsInR5cCI6InZjK2p3dCIs...",
"marqueId": "marque-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"detectedFormat": "generic",
"summary": {
"controlsTested": 24,
"controlsPassed": 22,
"controlsFailed": 2,
"overallScore": 91
},
"provenance": {
"source": "tool",
"sourceIdentity": "Acme",
"sourceDate": "2026-02-13T00:00:00Z"
},
"extensions": {
"mapping": { "id": "toolx-evidence-only", "evidenceOnly": true },
"passthrough": { "summary": { "passed": 12, "failed": 2 } }
},
"warnings": [],
"expiresAt": "2026-05-14T00:00:00.000Z",
"scittEntryId": "entry-acde1234-..."
}
}
Note: registerScitt is also supported by POST /issue and returns scittEntryId when enabled.
If the request is authenticated with an OIDC token, extensions.ext.oidc.identity (when present) contains hashed claim values.
GET /formats
List supported ingestion formats and mapping-pack identifiers currently loaded by the API. No authentication required.
curl https://api.grcorsair.com/formats
Response:
{
"ok": true,
"data": [
{ "id": "generic", "name": "Generic JSON", "description": "Any JSON payload with { metadata, controls[] }" },
{ "id": "mapping-pack", "name": "Mapping Registry", "description": "Config-driven ingestion using mapping packs" },
{ "id": "mapping:scanner-x", "name": "Scanner X", "description": "Mapping format (scanner-x)" }
],
"meta": {
"mappingCount": 1,
"loadErrors": 0
}
}
GET /intelligence/events
Read operational telemetry from the internal append-only event_journal table.
Requires authentication (API key or OIDC token) and is scoped to the authenticated actor identity.
curl "https://api.grcorsair.com/intelligence/events?limit=25&status=failure" \
-H "Authorization: Bearer $AUTH_TOKEN"
Query Parameters:
| Param | Type | Description |
|---|---|---|
limit | number | Page size (default 50, max 200) |
offset | number | Pagination offset (default 0) |
eventType | string | Exact event type filter |
status | string | success or failure |
targetType | string | Exact target type filter |
targetId | string | Exact target ID filter |
since | ISO timestamp | Start timestamp bound |
until | ISO timestamp | End timestamp bound |
Response:
{
"events": [
{
"eventId": "evt_123",
"eventType": "verify.success",
"eventVersion": 1,
"status": "success",
"occurredAt": "2026-02-28T10:00:00.000Z",
"actorType": "api_key",
"targetType": "issuer",
"targetId": "did:web:acme.com",
"requestPath": "/verify",
"requestMethod": "POST",
"metadata": {
"issuerTier": "self-signed"
}
}
],
"pagination": { "limit": 25, "offset": 0, "count": 1 },
"filters": { "status": "success" },
"scope": { "actorType": "api_key" }
}
POST /grc/translate
Translate one GRC/security JSON payload through multiple low-cost models and return side-by-side commentary. This endpoint is for interpretation only. It does not issue or verify cryptographic proof. No authentication required.
curl -X POST https://api.grcorsair.com/grc/translate \
-H "Content-Type: application/json" \
-d '{
"payload": {
"framework": "SOC2",
"controls": [{ "id": "CC6.1", "status": "pass" }]
},
"mode": "quick",
"redact": true,
"style": "funny"
}'
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
payload | object or array | Yes | JSON evidence to interpret |
mode | string | No | quick (default) or compare |
models | array | No | Optional explicit model IDs (max 10) |
style | string | No | funny (default) or plain |
redact | boolean | No | Redact sensitive values before model calls (default: true) |
audience | string | No | Output audience hint (default: grc-buyer) |
POST /onboard
Generate machine-actionable onboarding artifacts (did.json, jwks.json, trust.txt) for your domain.
Requires authentication (API key or OIDC token).
curl -X POST https://api.grcorsair.com/onboard \
-H "Authorization: Bearer $AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"contact": "[email protected]",
"frameworks": ["SOC2", "ISO27001"],
"includeDefaults": true
}'
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
domain | string | No | Domain for onboarding (must match server domain) |
did | string | No | DID override (must match domain; no path support) |
cpoes | array | No | CPOE URLs to list in trust.txt |
scitt | string | No | SCITT endpoint override |
catalog | string | No | Catalog snapshot URL |
policy | string | No | Policy artifact URL |
flagship | string | No | FLAGSHIP stream endpoint override |
frameworks | array | No | Compliance frameworks in scope |
contact | string | No | Compliance contact |
expiryDays | number | No | trust.txt expiry window in days (default: 365) |
includeDefaults | boolean | No | Populate default SCITT/FLAGSHIP URLs (default: true) |
Response (machine-actionable files):
{
"domain": "acme.com",
"did": "did:web:acme.com",
"urls": {
"didJson": "https://acme.com/.well-known/did.json",
"jwksJson": "https://acme.com/.well-known/jwks.json",
"trustTxt": "https://acme.com/.well-known/trust.txt"
},
"files": {
"didJson": {
"path": "/.well-known/did.json",
"mediaType": "application/did+ld+json",
"content": { "@context": ["https://www.w3.org/ns/did/v1"] }
},
"jwksJson": {
"path": "/.well-known/jwks.json",
"mediaType": "application/jwk-set+json",
"content": { "keys": [] }
},
"trustTxt": {
"path": "/.well-known/trust.txt",
"mediaType": "text/plain",
"content": "DID: did:web:acme.com\\n...",
"parsed": { "did": "did:web:acme.com", "cpoes": [], "frameworks": [] },
"validation": { "valid": true, "errors": [] }
}
}
}
Note: The API outputs the standard
/.well-known/trust.txtpath. If your org can’t host at the root domain, you can host trust.txt elsewhere and delegate discovery via DNS:
_corsair.example.com TXT "corsair-trusttxt=https://trust.example.com/trust.txt"
POST /trust-txt/host
Create or update a hosted trust.txt for a domain. Corsair returns a hosted URL plus the DNS TXT records required for delegation. Requires authentication (API key or OIDC token).
curl -X POST https://api.grcorsair.com/trust-txt/host \
-H "Authorization: Bearer $AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"domain": "acme.com",
"frameworks": ["SOC2", "ISO27001"],
"contact": "[email protected]"
}'
Request Body: Same fields as POST /onboard, except domain is required. Ownership is enforced after POST /trust-txt/host/:domain/verify succeeds.
Response (hosted + DNS delegation):
{
"domain": "acme.com",
"did": "did:web:acme.com",
"status": "pending",
"trustTxt": { "content": "DID: did:web:acme.com\\n...", "hash": "..." },
"urls": { "hosted": "https://trust.grcorsair.com/trust/acme.com/trust.txt" },
"dns": {
"txt": "_corsair.acme.com TXT \"corsair-trusttxt=https://trust.grcorsair.com/trust/acme.com/trust.txt\"",
"hashTxt": "_corsair.acme.com TXT \"corsair-trusttxt-sha256=<hash>\""
}
}
Hosted URLs use CORSAIR_TRUST_HOST (defaults to trust.<CORSAIR_DOMAIN>).
POST /trust-txt/host/:domain/verify
Verify the DNS delegation for a hosted trust.txt. Marks the record active when the DNS TXT record resolves to the hosted URL.
curl -X POST https://api.grcorsair.com/trust-txt/host/acme.com/verify \
-H "Authorization: Bearer $AUTH_TOKEN"
GET /trust-txt/host/:domain
Fetch the current hosted trust.txt configuration and DNS records for a domain.
curl https://api.grcorsair.com/trust-txt/host/acme.com \
-H "Authorization: Bearer $AUTH_TOKEN"
GET /trust/:domain/trust.txt
Public endpoint for hosted trust.txt content. Useful for delegated DNS records and template deployments.
curl https://trust.grcorsair.com/trust/acme.com/trust.txt
POST /verify
Verify a CPOE signature and extract attestation details. No authentication required. Optional policy checks and process receipt verification are supported.
Policy fields include:
requireSource(self|tool|auditor)requireSourceIdentity(array of allowed identities)requireToolAttestation(boolean)requireInputBinding(boolean, requiressourceDocumentHash)requireEvidenceChain(boolean, CLI-only because it needs the JSONL evidence file)requireReceipts(boolean)requireScitt(boolean)
curl -X POST https://api.grcorsair.com/verify \
-H "Content-Type: application/json" \
-d '{
"cpoe": "eyJhbGciOiJFZERTQSIsInR5cCI6InZjK2p3dCIs...",
"policy": {
"requireIssuer": "did:web:grcorsair.com",
"requireFramework": ["SOC2"],
"maxAgeDays": 30,
"minScore": 90,
"requireSource": "tool",
"requireSourceIdentity": ["Scanner v1.2"],
"requireReceipts": true,
"requireScitt": true,
"requireInputBinding": true
},
"receipts": [],
"sourceDocumentHash": "a2b4c6d8..."
}'
Response (POST /verify):
{
"verified": true,
"issuer": "did:web:grcorsair.com",
"issuerTier": "corsair-verified",
"scope": "SOC 2 Type II - AWS Production Controls",
"summary": {
"controlsTested": 24,
"controlsPassed": 22,
"controlsFailed": 2,
"overallScore": 91
},
"provenance": {
"source": "tool",
"sourceIdentity": "Scanner v1.2",
"sourceDate": "2026-01-15"
},
"timestamps": {
"issuedAt": "2026-02-13T00:00:00.000Z",
"expiresAt": "2026-05-14T00:00:00.000Z"
},
"policy": { "ok": true, "errors": [] },
"process": { "chainValid": true, "receiptsVerified": 2, "receiptsTotal": 2 },
"inputBinding": { "ok": true, "errors": [] },
"digests": {
"inputSha256": "3e4e9b...",
"jwtSha256": "3e4e9b..."
},
"extensions": {
"mapping": { "id": "toolx-evidence-only", "evidenceOnly": true }
}
}
sourceDocumentHash should be the SHA-256 of canonical JSON (sorted keys), matching provenance.sourceDocument.
POST /v1/verify returns the same verification payload in the v1 envelope format: { ok, data }.