Developer Guide

If you use the OpenAI SDK, you are 3 lines away. Every call gets a signed, verifiable receipt.

1. Get a key

Sign up at /signup, then mint an API key at /api-keys. Keys start with atk_.

2. Python (OpenAI SDK)

python
from openai import OpenAI

client = OpenAI(
    base_url="https://api.attestic.ai/v1",
    api_key="atk_your_key_here",
)

response = client.chat.completions.create(
    model="gpt-5.5",   # or claude-opus-4-8, gemini-3.1-pro-preview,
    messages=[         #    meta/llama-3.3-70b-instruct, ...
        {"role": "user", "content": "Explain provenance in one sentence."}
    ],
)
print(response.choices[0].message.content)
# Signed receipt in response headers: x-attestic-receipt-id

3. Anthropic / Claude SDK

Wrote your app against the Anthropic SDK? Point its base URL here. Same key, same gateway, same signed receipts. Routes any model, not only Claude.

python
from anthropic import Anthropic

client = Anthropic(
    base_url="https://api.attestic.ai",
    api_key="atk_your_key_here",
)

msg = client.messages.create(
    model="claude-fable-5", max_tokens=512,
    messages=[{"role": "user", "content": "Explain provenance in one sentence."}],
)
print(msg.content[0].text)
# Streaming works too: client.messages.stream(...). Receipt: x-attestic-receipt-id

Short-lived tokens (recommended for production)

Keep your atk_ key in a vault. Exchange it for a 1-hour access token (OAuth client-credentials) and use that for calls, so a leaked request credential expires within the hour.

bash
# exchange the long-lived key for a short-lived token
curl -s https://api.attestic.ai/v1/oauth/token \
  -H "content-type: application/json" \
  -d '{"api_key":"atk_your_key_here"}'
# -> { "access_token": "...", "token_type": "Bearer", "expires_in": 3600 }
# then call /v1/chat/completions or /v1/messages with that access_token

Want to force this pattern? Mint an exchange-only key. It is rejected on direct API calls (401) and can only be traded for a short-lived token, so the long-lived secret never rides on a request.

Optional: the attestic SDK

A thin convenience layer for Python and JavaScript: pre-configured clients, automatic short-lived-token refresh, and one-call offline receipt verification.

bash + python
pip install attestic[all]

from attestic import Attestic
at = Attestic(api_key="atk_your_key_here")   # exchanges + refreshes 1h tokens for you
r = at.openai.chat.completions.create(model="gpt-5.5",
        messages=[{"role": "user", "content": "hi"}])
# Verify the call's signed receipt offline, no trust in Attestic required:
assert at.verify(r.id)  # Ed25519 signature + Merkle inclusion
bash + javascript
npm install attestic openai

import OpenAI from "openai";
import { Attestic } from "attestic";
const at = new Attestic("atk_your_key_here");      // 1h token, auto-refreshed
const client = new OpenAI(await at.openaiConfig());
const r = await client.chat.completions.create({ model: "gpt-5.5",
        messages: [{ role: "user", content: "hi" }] });
// Verify the call's signed receipt offline, no trust in Attestic required:
await at.verify(r._request_id);  // Ed25519 signature + Merkle inclusion

4. Public hash verification (no account)

Every response includes x-attestic-entry-hash in the headers. Anyone — auditors, customers, regulators — can confirm the receipt is real at attestic.ai/verify without seeing model, cost, or tenant details.

bash
# From any API response headers:
ENTRY_HASH="<x-attestic-entry-hash>"

curl -s "https://api.attestic.ai/v1/receipts/check?hash=$ENTRY_HASH"
# -> { "verified": true, "signed_at_ms": ..., "chain_index": ..., "checks": { ... } }

# Or open in a browser:
# https://attestic.ai/verify?hash=$ENTRY_HASH

5. Full offline verification (developers)

Authenticated fetch returns the full bundle for your tenant. Verify Ed25519 + Merkle offline with the SDK or manually.

python, offline verification
import hashlib, urllib.request, json
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

GW = "https://api.attestic.ai"
bundle = json.load(urllib.request.urlopen(urllib.request.Request(
    f"{GW}/v1/receipts/YOUR_RECEIPT_ID",
    headers={"Authorization": "Bearer atk_your_key"}
)))

# Verify Ed25519 signature
pk = Ed25519PublicKey.from_public_bytes(bytes.fromhex(bundle["public_key"]))
pk.verify(bytes.fromhex(bundle["signature"]), bytes.fromhex(bundle["signed_bytes"]))

# Verify Merkle inclusion
h = bytes.fromhex(bundle["merkle"]["leaf_hash"])
for step in bundle["merkle"]["proof"]:
    sib = bytes.fromhex(step["sibling"])
    h = hashlib.sha256((sib + h) if step["side"] == "left" else (h + sib)).digest()
assert h.hex() == bundle["merkle"]["root"]   # ✓ verified offline

Models

Route by model id: gpt-5.5, claude-opus-4-8, gemini-3.1-pro-preview, meta/llama-3.3-70b-instruct. GET /v1/models?live=1 returns the real current list from each provider.

Streaming

stream:true works across all providers. The final SSE chunk includes attestic_provenance (receipt_id, entry_hash, signature, cost_usd) inline.

Tools & JSON mode

OpenAI-format tools translate to each provider's native schema. response_format:json_schema works on OpenAI and Gemini natively; best-effort on Anthropic.

Embeddings

POST /v1/embeddings routes text-embedding-3-* to OpenAI and gemini-embedding-* to Gemini. Same auth, same receipts, real token metering.

6. API reference

Base URL https://api.attestic.ai. Authenticate with Authorization: Bearer atk_… or a short-lived token. Account and key management require your JWT; inference and provenance accept either.

Auth & keys

POST/v1/auth/signup

Create an account; returns a JWT.

POST/v1/auth/login

Exchange email + password for a JWT.

POST/v1/oauth/token

Trade an atk_ key for a 1-hour Bearer token.

GET/v1/keys

List your API keys.

POST/v1/keys

Mint an atk_ key — the secret is shown once.

DELETE/v1/keys/{id}

Revoke a key.

Inference (OpenAI / Anthropic-compatible)

POST/v1/chat/completions

OpenAI-compatible chat. stream:true supported.

POST/v1/messages

Anthropic-compatible messages.

POST/v1/embeddings

Text embeddings (OpenAI / Gemini).

GET/v1/models

Routable models. ?live=1 queries each provider.

Provenance & verification

GET/v1/receipts/{id}

Full signed receipt + Merkle proof (own tenant).

GET/v1/receipts/public/{id}

Redacted public receipt — no model, cost, or tenant.

GET/v1/receipts/check

Verify by ?hash= entry hash — public, no account.

POST/v1/receipts/verify

Verify a receipt bundle server-side.

GET/v1/provenance/pubkey

The Ed25519 public key for offline verification.

GET/v1/transparency

Public Merkle-chain checkpoint.

GET/v1/audit/{tenant}

Your tenant's signed audit log.

Usage & billing

GET/v1/usage

Token + cost attribution, last 30 days.

GET/v1/billing/plans

Available plans.

GET/v1/billing/portal

A Stripe customer-portal link.

7. Errors

Every error returns the same JSON shape and a standard status code. On an upstream provider failure the gateway never synthesizes a completion — it surfaces the error, so a 429 or 5xx can never masquerade as a real (signed) answer.

json
{ "error": { "type": "invalid_request", "message": "human-readable detail" } }
400invalid_request — malformed body or parameters.
401unauthorized — missing/invalid key or token, or an exchange-only key used on a direct call.
403Cross-tenant access, or insufficient role.
404not_found — unknown receipt, key, or route.
409Conflict — e.g. that email is already registered.
429Per-tenant rate or monthly budget exceeded; honor Retry-After.
5xxupstream_error — provider failure; idempotent calls are retried or failed over.