REST API Reference
Endpoint-by-endpoint reference for the AI Provenance HTTP API. The @certnode/sdk npm package wraps this surface; this page documents the underlying calls for any other language.
Base URL
https://certnode.io/api/v1/provenanceLive mode only. Test-mode API keys (cn_test_…) hit the same base URL but skip Stripe metering. Both modes return real receipts that verify identically.
Authentication
Pass your API key as a Bearer token in the Authorization header. Alternative: X-Api-Key header. Keys live + test:
cn_live_…— production. Hits Stripe metering on overage.cn_test_…— CI / staging. Tracks usage locally; never bills.
curl -X POST https://certnode.io/api/v1/provenance/sign \
-H "Authorization: Bearer $CERTNODE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "content": "Hello world", "model": "claude-opus-4-7", "provider": "anthropic" }'/signAuth: Bearer (live or test key)
Request body
{
"content": string, // required. raw text or base64 for binary
"contentType": "ai_output" | // default 'ai_output'
"image" |
"document" |
"json",
"model": string?, // e.g. 'claude-opus-4-7'
"provider": string?, // e.g. 'anthropic'
"promptHash": string? // sha256 of prompt, for privacy-preserving audit
}200 OK response
{
"receiptId": "uuid",
"verifyUrl": "https://certnode.io/verify/uuid",
"signature": "JWS compact form",
"contentHash": "sha256:abc...",
"signedAt": "2026-05-10T22:44:09Z",
"timestamps": {
"certnode": { "id": "ts_..." },
"rfc3161": "base64-DER token",
"bitcoin": { "status": "pending" | "anchored" | "skipped" }
},
"metadata": { "contentType": "ai_output", "model": "...", "provider": "..." }
}Errors
| content_required | 400 | Body missing `content` field |
| invalid_content_type | 400 | contentType not one of allowed values |
| content_too_large | 413 | Content exceeds 1 MB |
| free_tier_exceeded | 402 | Over 100/mo without an active subscription. Response includes `upgrade_url`. |
| invalid_or_revoked_api_key | 401 | API key missing, malformed, or revoked |
/verifyAuth: None (public)
Request body
{
// Mode 1: by receipt ID (looks up stored receipt)
"receiptId": "uuid",
"content": string? // optional re-hash check
// Mode 2: by raw signature + content (no DB lookup)
"signature": "JWS compact",
"content": string
}200 OK response
{
"valid": boolean, // overall pass/fail
"signatureValid": boolean,
"contentMatches": boolean | null, // null if no content provided
"receipt": { // present in mode 1
"id": "uuid",
"contentHash": "sha256:...",
"contentType": "ai_output",
"model": "...",
"provider": "...",
"signedAt": "2026-05-10T22:44:09Z",
"verifyUrl": "https://certnode.io/verify/uuid",
"timestamps": { ... three-layer chain ... }
},
"payload": { ... }, // signed JWS payload (mode 2)
"error": string?
}Errors
| receipt_not_found | 404 | No receipt with this ID |
| invalid_request | 400 | Neither receiptId nor signature+content provided |
| signature_invalid | 200 | Returned in body with valid=false — signature is malformed or tampered |
/receipts/exportAuth: Bearer API key OR Clerk session
Query params
| format | `csv` or `json` (default: csv) |
| q | Substring vs id / model / provider |
| from | ISO date (signed_at >=) |
| to | ISO date (signed_at <= end-of-day) |
| api_key | Filter to a single api_key_id (uuid) |
| limit | Max rows; default 1000, max 10000 |
200 OK response
{
"export": {
"type": "certnode_provenance_receipts",
"version": "1.0",
"organization_id": "uuid",
"exported_at": "2026-05-10T22:44:09Z",
"filter": { "q": null, "from": null, "to": null, "api_key_id": null, "limit": 1000 },
"row_count": 42
},
"receipts": [
{
"id": "uuid",
"signed_at": "2026-05-10T22:44:09Z",
"content_type": "ai_output",
"model": "claude-opus-4-7",
"provider": "anthropic",
"content_hash": "sha256:...",
"prompt_hash": "sha256:..." | null,
"bitcoin_anchor_status": "anchored" | "pending" | null,
"certnode_timestamp_id": "ts_...",
"rfc3161_token_present": true,
"verify_count": 3,
"last_verified_at": "2026-05-10T22:50:00Z",
"api_key_id": "uuid",
"verify_url": "https://certnode.io/verify/uuid"
},
...
]
}Errors
| invalid_format | 400 | format must be csv or json |
| unauthorized | 401 | No valid Clerk session or Bearer API key |
| no_organization | 403 | Auth succeeded but no org associated |
/keysAuth: Clerk session (dashboard surface)
Request body
{
"name": string?, // human label for the key
"mode": "live" | "test" // default 'live'
}200 OK response
{
"key": { "id", "name", "key_prefix", "key_last4", "created_at", "revoked_at": null, "last_used_at": null },
"fullKey": "cn_live_..." // shown ONCE — store immediately, won't be returned again
}Errors
| no_organization | 403 | User must be a member of an org |
| invalid_mode | 400 | mode must be "live" or "test" |
/keys/{id}Auth: Clerk session
200 OK response
{ "ok": true }Errors
| key_not_found | 404 | No key with this ID under your org |
/billingAuth: Clerk session
Request body
(empty)200 OK response
{ "url": "https://checkout.stripe.com/c/..." }Errors
| missing_price_id_env | 500 | Stripe metered price not configured server-side |
Webhooks (Phase 2 backlog)
Customer-facing webhooks (subscribe to sign / verify / cap-warning events) are tracked in the AI Provenance Phase 2 buildout plan. Not live yet. When live, they will follow the same shape as Stripe webhooks (event type + payload + signing secret for HMAC verification). Until then, query the export endpoint for batch consumption.
Rate limits
The Sign endpoint enforces the free-tier cap (100/mo without subscription) but no per-minute rate limit is currently enforced at the route level. Customers running backfill workloads or high-volume agent loops should self-throttle to avoid Stripe meter-event back-pressure. Recommended:
- — Throttle to ≤1000 calls/minute for steady-state production traffic.
- — For backfill workloads >100K signings, contact contact@certnode.io for batch pricing.