Skip to main content
← All docs

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/provenance

Live 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" }'
POST/sign

Auth: 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_required400Body missing `content` field
invalid_content_type400contentType not one of allowed values
content_too_large413Content exceeds 1 MB
free_tier_exceeded402Over 100/mo without an active subscription. Response includes `upgrade_url`.
invalid_or_revoked_api_key401API key missing, malformed, or revoked
POST/verify

Auth: 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_found404No receipt with this ID
invalid_request400Neither receiptId nor signature+content provided
signature_invalid200Returned in body with valid=false — signature is malformed or tampered
GET/receipts/export

Auth: Bearer API key OR Clerk session

Query params

format`csv` or `json` (default: csv)
qSubstring vs id / model / provider
fromISO date (signed_at >=)
toISO date (signed_at <= end-of-day)
api_keyFilter to a single api_key_id (uuid)
limitMax 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_format400format must be csv or json
unauthorized401No valid Clerk session or Bearer API key
no_organization403Auth succeeded but no org associated
POST/keys

Auth: 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_organization403User must be a member of an org
invalid_mode400mode must be "live" or "test"
DELETE/keys/{id}

Auth: Clerk session

200 OK response

{ "ok": true }

Errors

key_not_found404No key with this ID under your org
POST/billing

Auth: Clerk session

Request body

(empty)

200 OK response

{ "url": "https://checkout.stripe.com/c/..." }

Errors

missing_price_id_env500Stripe 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.