Back to Documentation

Carpe Diem — SDK Integration Guide

Base URL: https://carpe-diem.xyz/api/operator/v1

Carpe Diem is fully OpenAI and Anthropic compatible. Any SDK or tool that works with OpenAI or Anthropic works with Carpe Diem — just change the base URL and API key. Works with Claude Code, Cursor, Cline, and all standard SDKs.


Table of Contents

  1. Quick Start
  2. Authentication
  3. Chat Completions
  4. Streaming
  5. Image Generation
  6. Image Editing
  7. Video Generation (Async)
  8. Models
  9. Payment & Billing
  10. Usage Tracking
  11. Error Handling
  12. Rate Limits
  13. Health & Status
  14. Provider Integration
  15. Endpoint Reference

1. Quick Start

Get an API Key

  1. Connect your wallet on the dashboard
  2. Sign a message to create a session
  3. Go to API Keys → Create Key
  4. Copy the key (shown only once) — it starts with cdm_

Python (OpenAI SDK)

from openai import OpenAI

client = OpenAI(
    base_url="https://carpe-diem.xyz/api/operator/v1",
    api_key="cdm_your_api_key_here",
)

response = client.chat.completions.create(
    model="llama-3.3-70b",
    messages=[{"role": "user", "content": "Hello!"}],
)

print(response.choices[0].message.content)

JavaScript (OpenAI SDK)

import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://carpe-diem.xyz/api/operator/v1",
  apiKey: "cdm_your_api_key_here",
});

const response = await client.chat.completions.create({
  model: "llama-3.3-70b",
  messages: [{ role: "user", content: "Hello!" }],
});

console.log(response.choices[0].message.content);

cURL

curl https://carpe-diem.xyz/api/operator/v1/chat/completions \
  -H "Authorization: Bearer cdm_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "llama-3.3-70b",
    "messages": [{"role": "user", "content": "Hello!"}]
  }'

Python (Anthropic SDK)

import anthropic

client = anthropic.Anthropic(
    base_url="https://carpe-diem.xyz/api/operator",
    api_key="cdm_your_api_key_here",
)

message = client.messages.create(
    model="claude-opus-4-6",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello!"}],
)

print(message.content[0].text)

Claude Code / Cursor / Cline

Set these environment variables and use Claude Code normally:

export ANTHROPIC_BASE_URL=https://carpe-diem.xyz/api/operator
export ANTHROPIC_API_KEY=cdm_your_api_key_here

Available Claude models: claude-opus-4-7, claude-opus-4-6, claude-opus-4-6-fast, claude-sonnet-4-6, claude-opus-4-5, claude-sonnet-4-5 — plus all Venice open-source models (LLaMA, Mistral, Qwen, Gemini, GPT, etc.).


2. Authentication

Carpe Diem supports two authentication methods. Both use the Authorization: Bearer <token> header.

API Keys (cdm_ prefix)

Persistent keys tied to your wallet. Best for bots, scripts, and production backends.

Create a key (requires JWT session):

curl -X POST https://carpe-diem.xyz/api/operator/auth/api-keys \
  -H "Authorization: Bearer <jwt_token>" \
  -H "Content-Type: application/json" \
  -d '{"name": "my-bot"}'

Response:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "key": "cdm_a1b2c3d4e5f6...",
  "prefix": "cdm_a1b2c3d4..."
}

The full key is shown only once. Store it securely.

List keys (requires JWT):

GET /auth/api-keys

Revoke a key (requires JWT):

DELETE /auth/api-keys/:id

Per-key payment rails

The payment rail (see §9) is a property of the key, set once at creation — so an app using your key never has to deal with payment. POST /auth/api-keys accepts three optional fields:

curl -X POST https://carpe-diem.xyz/api/operator/auth/api-keys \
  -H "Authorization: Bearer <jwt_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-bot",
    "rail": "prepaid",
    "fallback": true,
    "prepaidAccount": "0xYourDedicatedSmartAccount"
  }'
FieldTypeDescription
rail"auto" | "credits" | "prepaid"Which rail backs this key. auto (default) uses prepaid if an account is registered, else credits.
fallbackbooleanIf the primary rail can't cover a request, fall back to the other rail instead of failing. Default false.
prepaidAccountaddressRegister the dedicated smart account this key pulls from (required for rail: "prepaid").

Omit all three for the classic credits behaviour — fully backward-compatible. Rail settings are scoped to the key; keys without a rail set inherit your wallet-level default.

JWT Sessions

Short-lived tokens from wallet signature. Best for dApps and frontends.

Create a session:

curl -X POST https://carpe-diem.xyz/api/operator/auth/session \
  -H "Content-Type: application/json" \
  -d '{
    "wallet": "0xYourWalletAddress",
    "message": "Sign in to Carpe Diem\nWallet: 0xYourWalletAddress\nTimestamp: 1707654321000",
    "signature": "0x..."
  }'

Response:

{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "wallet": "0xyourwalletaddress",
  "expiresIn": "7d"
}

Refresh a session (before expiry):

curl -X POST https://carpe-diem.xyz/api/operator/auth/refresh \
  -H "Authorization: Bearer <jwt_token>"

Response:

{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "wallet": "0xyourwalletaddress",
  "expiresIn": "7d"
}

Sessions have a maximum lifetime of 24 hours. After that, you must re-authenticate with a wallet signature.

Which to use?

Use caseMethod
Bot / script / backendAPI Key
dApp / browser frontendJWT Session
Creating/managing API keysJWT Session (required)

3. Chat Completions

POST /v1/chat/completions

Fully OpenAI-compatible. Proxied through Carpe Diem's multi-provider network.

Request

{
  "model": "llama-3.3-70b",
  "messages": [
    { "role": "system", "content": "You are a helpful assistant." },
    { "role": "user", "content": "Explain quantum computing in simple terms." }
  ],
  "temperature": 0.7,
  "top_p": 0.9,
  "max_tokens": 1024,
  "stream": false,
  "venice_parameters": {
    "enable_web_search": "auto",
    "enable_web_scraping": true
  }
}
ParameterTypeRequiredDescription
modelstringYesModel ID (see Models). Lowercase, max 100 chars.
messagesarrayYesConversation messages (max 2000). Each has role and content.
temperaturenumberNoSampling temperature, 0–2. Default varies by model.
top_pnumberNoNucleus sampling, 0–1.
max_tokensintegerNoMaximum completion tokens, 1–16384.
streambooleanNoEnable SSE streaming. Default false.
venice_parametersobjectNoPassthrough to Venice. Web search (enable_web_search: off, auto or on) and URL scraping (enable_web_scraping) are off unless set here; what they retrieve counts as input tokens, and each search or page read adds Venice's $0.01 fee, with the same multiplier as the tokens.

Response (non-streaming)

{
  "id": "chatcmpl-abc123",
  "object": "chat.completion",
  "created": 1707654321,
  "model": "llama-3.3-70b",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Quantum computing uses..."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 25,
    "completion_tokens": 150,
    "total_tokens": 175
  }
}

4. Streaming

Set "stream": true to receive Server-Sent Events (SSE).

SSE Format

Each event is a line prefixed with data: followed by JSON. The stream ends with data: [DONE].

data: {"id":"chatcmpl-abc123","choices":[{"delta":{"role":"assistant"},"index":0}]}

data: {"id":"chatcmpl-abc123","choices":[{"delta":{"content":"Quantum"},"index":0}]}

data: {"id":"chatcmpl-abc123","choices":[{"delta":{"content":" computing"},"index":0}]}

data: {"id":"chatcmpl-abc123","choices":[],"usage":{"prompt_tokens":25,"completion_tokens":150}}

data: [DONE]

Python Streaming

from openai import OpenAI

client = OpenAI(
    base_url="https://carpe-diem.xyz/api/operator/v1",
    api_key="cdm_your_api_key_here",
)

stream = client.chat.completions.create(
    model="llama-3.3-70b",
    messages=[{"role": "user", "content": "Tell me a story"}],
    stream=True,
)

for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)

JavaScript Streaming

import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://carpe-diem.xyz/api/operator/v1",
  apiKey: "cdm_your_api_key_here",
});

const stream = await client.chat.completions.create({
  model: "llama-3.3-70b",
  messages: [{ role: "user", content: "Tell me a story" }],
  stream: true,
});

for await (const chunk of stream) {
  const content = chunk.choices[0]?.delta?.content;
  if (content) process.stdout.write(content);
}

Manual SSE Parsing (JavaScript)

const response = await fetch("https://carpe-diem.xyz/api/operator/v1/chat/completions", {
  method: "POST",
  headers: {
    "Authorization": "Bearer cdm_your_api_key_here",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "llama-3.3-70b",
    messages: [{ role: "user", content: "Hello" }],
    stream: true,
  }),
});

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  const text = decoder.decode(value, { stream: true });
  for (const line of text.split("\n")) {
    if (!line.startsWith("data: ") || line === "data: [DONE]") continue;
    const data = JSON.parse(line.slice(6));
    const content = data.choices?.[0]?.delta?.content;
    if (content) process.stdout.write(content);
  }
}

5. Image Generation

POST /v1/image/generate

Request

{
  "model": "flux-2-pro",
  "prompt": "A sunset over a cyberpunk city",
  "negative_prompt": "blurry, low quality",
  "width": 1024,
  "height": 1024,
  "steps": 30,
  "cfg_scale": 7.0,
  "seed": 42,
  "safe_mode": false,
  "format": "webp"
}
ParameterTypeRequiredDescription
modelstringYesImage model ID (see Models).
promptstringYesText description of the image.
negative_promptstringNoWhat to avoid in the image.
widthintegerNoImage width in pixels. Default 1024.
heightintegerNoImage height in pixels. Default 1024.
stepsintegerNoInference steps (more = higher quality, slower).
cfg_scalenumberNoPrompt adherence strength.
seedintegerNoDeterministic seed for reproducibility.
safe_modebooleanNoEnable safety filter. Default false.
formatstringNoOutput format: "webp", "png", "jpeg". Default "webp".

Response

The response is the raw JSON from the image provider, typically containing base64-encoded image data.

Pricing

ModelCost per image
nano-banana-pro$0.02 (2 credits)
flux-2-pro$0.03 (3 credits)
flux-2-max$0.05 (5 credits)
gpt-image-1-5$0.08 (8 credits)

Heavy models — async queue (/queue → /retrieve → /complete)

POST /v1/image/generate is synchronous, and the edge proxy in front of the API caps a single request at ~60s. Models that routinely run longer — gpt-image-2, nano-banana-pro, recraft-v4-pro, flux-2-*, qwen-image-* — will return a 502 at the edge even though the image was produced. For those, use the async pattern (identical shape to Video): queue returns a queue_id in <1s, then you poll retrieve until it returns the binary image. There is no duration limit, and billing is charged only on a stored, retrievable success — a failed generation is never billed.

  • POST /v1/image/generate/queue — same body as /v1/image/generate → 202 { "queue_id", "status": "pending" }
  • POST /v1/image/generate/retrieve — body { "queue_id" } → pending: JSON { "status": "pending" } · done: the binary image (Content-Type: image/*) · failed: the upstream error
  • POST /v1/image/generate/complete — body { "queue_id" } → frees the result (best-effort; a 15-min TTL is the backstop)

Python

import time, requests

BASE = "https://carpe-diem.xyz/api/operator/v1"
H = {"Authorization": f"Bearer {API_KEY}"}

queue_id = requests.post(f"{BASE}/image/generate/queue", headers=H, json={
    "model": "gpt-image-2",
    "prompt": "a neon koi pond",
}).json()["queue_id"]

while True:
    r = requests.post(f"{BASE}/image/generate/retrieve", headers=H,
                      json={"queue_id": queue_id})
    if r.headers.get("content-type", "").startswith("image/"):
        with open("out.webp", "wb") as f:
            f.write(r.content)          # completed
        break
    if r.status_code >= 400:
        raise RuntimeError(r.text)      # failed (not billed)
    time.sleep(2)                       # pending — poll again

requests.post(f"{BASE}/image/generate/complete", headers=H,
              json={"queue_id": queue_id})   # optional cleanup

JavaScript

const BASE = "https://carpe-diem.xyz/api/operator/v1";
const H = { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" };

const { queue_id } = await (await fetch(`${BASE}/image/generate/queue`, {
  method: "POST", headers: H,
  body: JSON.stringify({ model: "gpt-image-2", prompt: "a neon koi pond" }),
})).json();

let imageBlob;
while (true) {
  const r = await fetch(`${BASE}/image/generate/retrieve`, {
    method: "POST", headers: H, body: JSON.stringify({ queue_id }),
  });
  const ct = r.headers.get("content-type") || "";
  if (ct.startsWith("image/")) { imageBlob = await r.blob(); break; } // completed
  if (!r.ok) throw new Error(await r.text());                         // failed
  await new Promise((s) => setTimeout(s, 2000));                      // pending
}

await fetch(`${BASE}/image/generate/complete`, {                       // optional
  method: "POST", headers: H, body: JSON.stringify({ queue_id }),
});

6. Image Editing

POST /v1/image/edit

Edit an existing image using AI-powered inpainting, style transfer, or montage.

Request

{
  "model": "flux-2-max-edit",
  "prompt": "Change the sky to a sunset",
  "image": "data:image/png;base64,iVBORw0KGgo...",
  "aspect_ratio": "16:9"
}
ParameterTypeRequiredDescription
modelstringYesImage edit model ID.
promptstringYesEdit instruction.
imagestringYesBase64-encoded image (with or without data: prefix). Max 5 MB.
aspect_ratiostringNoOutput aspect ratio (e.g., "16:9", "1:1").

Response

{
  "images": ["iVBORw0KGgo..."],
  "format": "png"
}

The images array contains base64-encoded PNG data.

Pricing

ModelCost per edit
nano-banana-pro-edit$0.02 (2 credits)
qwen-edit$0.04 (4 credits)
seedream-v4-edit$0.05 (5 credits)
flux-2-max-edit$0.06 (6 credits)
gpt-image-1-5-edit$0.08 (8 credits)

Heavy edit models — async queue (/queue → /retrieve → /complete)

Like generation, POST /v1/image/edit is synchronous and the edge proxy caps a single request at ~60–120s. Real edits with the highest-quality models — notably gpt-image-2-edit — routinely run longer than that and will return a 502 at the edge even though the edit was produced. For those, use the async pattern (same shape as async generation and Video): queue returns a queue_id in <1s, then you poll retrieve until it returns the binary image. There is no duration limit, and billing is charged only on a stored, retrievable success — a failed edit is never billed.

  • POST /v1/image/edit/queue — same body as /v1/image/edit → 202 { "queue_id", "status": "pending" }
  • POST /v1/image/edit/retrieve — body { "queue_id" } → pending: JSON { "status": "pending" } · done: the binary image (Content-Type: image/*) · failed: the upstream error
  • POST /v1/image/edit/complete — body { "queue_id" } → frees the result (best-effort; a 15-min TTL is the backstop)
import requests, time, base64

queue_id = requests.post(f"{BASE}/image/edit/queue", headers=H, json={
    "model": "gpt-image-2-edit",
    "prompt": "Reframe as a cinematic wide shot, add a city skyline through the window",
    "image": "data:image/png;base64,iVBORw0KGgo...",
    "aspect_ratio": "16:9",
}).json()["queue_id"]

while True:
    r = requests.post(f"{BASE}/image/edit/retrieve", headers=H, json={"queue_id": queue_id})
    if r.headers["content-type"].startswith("application/json") and r.json().get("status") == "pending":
        time.sleep(3); continue
    r.raise_for_status()
    open("edited.png", "wb").write(r.content)          # binary image
    break

requests.post(f"{BASE}/image/edit/complete", headers=H, json={"queue_id": queue_id})  # optional

Tip: route gpt-image-2-edit (and any future slow edit model) through this async path by default. Fast edit models (e.g. nano-banana-pro-edit) still work on the synchronous /v1/image/edit.


7. Video Generation (Async)

Video generation is asynchronous — you queue a job, then poll for the result.

Step 1: Get a Quote

POST /v1/video/quote

{
  "model": "ltx-2-fast-text-to-video",
  "prompt": "A cat playing piano"
}

Returns cost_usdc — what /v1/video/queue charges for this payload at the current price (Venice's own quote × the dynamic multiplier) — and venice_quote_usd, Venice's figure. The queue prices the job the same way, so the quote is optional.

Step 2: Queue the Job

POST /v1/video/queue

{
  "model": "wan-2.5-preview-text-to-video",
  "prompt": "A cat playing piano",
  "duration": "5s"
}

duration is required and per-model — a string with an "s" suffix ("5s", not 5). Read the accepted set off constraints.durations in GET /v1/models; it differs by family and version (the seedance-2-0 line takes every whole second from "4s" to "15s", seedance-2-5 to "30s", seedance-1-5 stops at "12s"). Validating any payload is free: POST /v1/video/quote returns 400 if a field is rejected.

On a Seedance reference-to-video edit or extend, duration: "auto" (or "-1") and aspect_ratio: "adaptive" (or "auto") make the output follow the source clip. Neither is listed in constraints; both are accepted. See the API reference for what they require.

Image conditioning passes through verbatim. Image-to-video and frame-control models take image fields alongside prompt — the operator forwards your body to Venice unchanged, filtering nothing:

{
  "model": "wan-2.5-preview-image-to-video",
  "prompt": "the photo comes to life",
  "duration": "5s",
  "image_url": "https://example.com/frame.png",
  "end_image_url": "https://example.com/last-frame.png"
}
  • image_url — source / first frame (image-to-video, and the start frame for first-last-frame models). https:// URL or data: URI.
  • end_image_url — last frame, for first-last-frame interpolation models.
  • image_urls — array of reference images, for models that condition on a set.

Any other parameter the model accepts (seed, negative_prompt, motion/camera controls, …) also reaches Venice unchanged — consult the model's spec.

Response includes a queue_id for polling. VPS-backed models (Grok Imagine Private variants) also include a download_url:

{
  "model": "grok-imagine-text-to-video-private",
  "queue_id": "abc123",
  "download_url": "https://...pre-signed-url..."
}

Save the download_url immediately — Venice only returns it once and it's valid for 24 hours. For VPS-backed models, you'll use this URL to fetch the video after status hits completed (the retrieve endpoint never returns it).

Step 3: Poll for Result

POST /v1/video/retrieve

{
  "queue_id": "abc123",
  "model": "wan-2.5-preview-text-to-video"
}

While processing:

{
  "status": "processing",
  "average_execution_time": 145000,
  "execution_duration": 32000
}

When complete (standard models):

{
  "status": "completed",
  "video_url": "/v1/video/file/abc123",
  "video": "data:video/mp4;base64,AAAAIGZ0eXBpc29t..."
}

When complete (VPS-backed / Grok Imagine):

{ "status": "completed" }

— no URL fields. Fetch the download_url you saved from /queue.

Error Handling (Critical)

POST /v1/video/retrieve returns several fatal status codes that should stop your polling loop immediately. Do not swallow these silently — re-queueing is the only recovery for most of them.

StatuscodeMeaningAction
200—Processing or completedContinue / use video
404VIDEO_JOB_NOT_FOUNDUnknown / expired queue_id, or operator restart wiped the pinRe-queue
410VIDEO_KEY_REVOKEDProvider key removed mid-job, or pre-signed URL >24h oldRe-queue
403FORBIDDENqueue_id does not belong to this walletCheck auth
422VENICE_ERRORContent moderation rejectionModify prompt, do NOT retry
429—Rate limitedBack off and retry
5xx—Transient operator/Venice errorRetry with exponential backoff

Polling Pattern (Python)

import time
import requests

BASE = "https://carpe-diem.xyz/api/operator/v1"
HEADERS = {
    "Authorization": "Bearer cdm_your_api_key_here",
    "Content-Type": "application/json",
}

# Queue the job
queue_res = requests.post(f"{BASE}/video/queue", headers=HEADERS, json={
    "model": "wan-2.5-preview-text-to-video",
    "prompt": "A cat playing piano",
    "duration": "5s",
})
queue_res.raise_for_status()
queue_data = queue_res.json()
queue_id = queue_data["queue_id"]
download_url = queue_data.get("download_url")  # set for VPS-backed models

MAX_WAIT_SECONDS = 600
POLL_INTERVAL_SECONDS = 5

deadline = time.time() + MAX_WAIT_SECONDS
while time.time() < deadline:
    r = requests.post(f"{BASE}/video/retrieve", headers=HEADERS, json={
        "queue_id": queue_id,
        "model": "wan-2.5-preview-text-to-video",
    })

    # FATAL codes — stop, surface to user, re-queue if appropriate
    if r.status_code in (404, 410, 422):
        raise RuntimeError(f"Generation failed: HTTP {r.status_code}: {r.text}")

    # TRANSIENT codes — retry on the next poll iteration
    if r.status_code in (429,) or 500 <= r.status_code < 600:
        time.sleep(POLL_INTERVAL_SECONDS)
        continue

    r.raise_for_status()
    result = r.json()

    if result["status"] == "completed":
        # Standard models: video_url/video are present in `result`
        # VPS-backed: use the `download_url` saved from /queue above
        video_ref = result.get("video_url") or result.get("video") or download_url
        if not video_ref:
            raise RuntimeError("Completed but no video URL available")
        print(f"Video ready: {video_ref}")
        break

    time.sleep(POLL_INTERVAL_SECONDS)
else:
    raise TimeoutError(f"Video did not complete within {MAX_WAIT_SECONDS}s")

Common pitfall — naive polling loops that ignore HTTP status codes (while True: poll(); if "completed" in result: break) will silently retry on 422 (content violation) and 404 (job lost) until your watchdog fires. Always branch on the status code first.

Pricing

Text-to-Video:

ModelCost
ltx-2-fast-text-to-video$0.10 (10 credits)
kling-2.6-pro-text-to-video$0.25 (25 credits)

Image-to-Video:

ModelCost
ovi-image-to-video$0.22 (22 credits)
ltx-2-fast-image-to-video$0.26 (26 credits)
kling-2.5-turbo-pro-image-to-video$0.39 (39 credits)
sora-2-image-to-video$0.44 (44 credits)
wan-2.5-preview-image-to-video$0.55 (55 credits)
kling-2.6-pro-image-to-video$0.77 (77 credits)
wan-2.6-image-to-video$0.83 (83 credits)
veo3-fast-image-to-video$0.88 (88 credits)
vidu-q3-image-to-video$0.97 (97 credits)
veo3-full-image-to-video$1.76 (176 credits)
kling-o3-pro-image-to-video$1.77 (177 credits)

8. Models

GET /models

Returns all available models grouped by type. No authentication required.

Response

{
  "text": [
    { "id": "llama-3.3-70b", "name": "Llama 3.3 70B", "type": "text", "tier": "standard", "offline": false },
    { "id": "deepseek-r1-671b", "name": "Deepseek R1 671B", "type": "text", "tier": "frontier", "offline": false }
  ],
  "code": [
    { "id": "qwen-2.5-coder-32b", "name": "Qwen 2.5 Coder 32B", "type": "code", "tier": "standard", "offline": false }
  ],
  "image": [
    { "id": "flux-2-pro", "name": "Flux 2 Pro", "type": "image", "tier": "premium", "offline": false }
  ],
  "imageEdit": [
    { "id": "flux-2-max-edit", "name": "Flux 2 Max Edit", "type": "imageEdit", "tier": "frontier", "offline": false }
  ],
  "video": [
    { "id": "ltx-2-fast-text-to-video", "name": "LTX 2 Fast", "type": "video", "tier": "standard", "offline": false }
  ],
  "imageToVideo": [
    { "id": "sora-2-image-to-video", "name": "Sora 2", "type": "imageToVideo", "tier": "premium", "offline": false }
  ]
}

Model Tiers

TierDescriptionExamples
standardFast, cost-effectiveLlama 3.3 70B, Qwen 2.5 Coder
premiumHigher quality, moderate costFlux 2 Pro, Sora 2
frontierState-of-the-art, highest costGPT 5.2, Gemini 3 Pro, Claude Opus 4.7

Model pricing is dynamic (15–100% of base price). See Payment & Billing.


9. Payment & Billing

Payment rails

Carpe Diem settles every request in USDC on Base at the same dynamic price — but there are three ways to fund that spend. Pick by who's calling:

RailCustodyWithdrawableAuthBest for
Prepaid (recommended)dedicated smart account you ownYescdm_ key / JWTapps & humans — same effort as credits, but funds stay yours
Creditsusage entitlement (protocol-held)Nocdm_ key / JWTtopping up a non-refundable balance you don't withdraw
Wallet (x402)your walletn/a (nothing pre-funded)none — a signed payment per requestautonomous AI agents

Prepaid and Credits are both used through a cdm_ key — the rail is a property of the key, chosen at creation (see §2 Per-key payment rails). To a caller the two are identical — same endpoints, same headers — and take the same single deposit step; only custody differs. Prepaid (recommended) pulls the exact per-request cost from an ERC-4337 smart account whose owner is your wallet — self-custodial, and you can withdraw the remainder anytime. Credits draw down a pooled, non-refundable balance (your USDC becomes protocol funds; no withdrawal).

Wallet (x402) needs no key and no pre-funded balance: an agent holding USDC on Base signs each payment (or one reusable session permit) and Carpe settles the exact cost on-chain per request. Use the /v1/x402/… endpoints. See the API reference for the full x402 flow and the two schemes (carpe-session sign-once vs exact per-request).

The billing details below (credit unit, dynamic pricing, balance headers, snapshot endpoints) apply to the Credits and Prepaid rails; x402 agents read their cost from the X-Payment-Amount-Usdc response header instead.

Credit System

100 credits= $1 = 1 USDC
Minimum balance1 credit ($0.01)
Non-refundableCredits are a one-way purchase

Dynamic Pricing

Your price is the higher of two signals, so you pay for your own demand — not just the time of day:

  • Daily pool usage — how much of the day's shared DIEM pool has been consumed. Mostly free → near the floor; nearly depleted → near the ceiling. The curve stays low while the pool is healthy and ramps up steeply as consumption approaches ~70% of the pool.
  • Your request's footprint — its size relative to the remaining capacity that can serve it. Small requests stay near the pool price; a large request (e.g. a video consuming a big share of remaining capacity) pays a premium for its footprint.

Bounds (both signals): floor ~15%, ceiling 100% of the Venice base price — the 5% protocol fee is inside the ceiling, so you never pay more than going to Venice directly.

Text/code model pricing is per-token (input and output priced separately). Image and video models have fixed per-request costs.

Insufficient Credits (HTTP 402)

When your balance is too low, you'll receive:

{
  "error": "Insufficient credits",
  "code": "PAYMENT_REQUIRED",
  "available": "0.0500",
  "minRequired": "0.01",
  "credits_available": 5.0,
  "credits_required": 1.0
}

available and minRequired are decimal USDC strings; credits_* fields are in credits (1 credit = $0.01). Agents should treat 402 as a hard stop — retrying with the same wallet won't help until a deposit settles.

Real-time balance for AI agents

Inference responses always carry the wallet's effective credit state, so agents don't need to poll a separate endpoint:

HeaderDescription
X-Carpe-Available-CreditsThe credit balance the user can actually spend right now. Equals escrow − pending − holds. This is what UIs and agents should display and track — it's the number that determines whether the next call will be authorized.
X-Carpe-Pending-CreditsDebt accumulated since the last on-chain settlement. Already deducted from available, surfaced for transparency / audit.
X-Carpe-Balance-CreditsCumulative on-chain escrow balance (V8.X audit trail of historical deposits — never decremented). Useful for accounting reconciliations, not for "remaining credits" UIs.
X-Carpe-Cost-Usdc-MicroCost of this call in micro-USDC (1e-6 USDC). Non-streaming only.
X-Carpe-Input-Tokens / X-Carpe-Output-TokensToken counts used to compute the cost. Non-streaming only.
X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-ResetPer-wallet request rate limit, OpenAI-compatible.

For streaming chat completions, the cost is delivered as extensions to the standard OpenAI usage object in a final chat.completion.chunk, written after the upstream's last event and immediately before the stream's single data: [DONE], which is always the last line:

data: {"id":"chatcmpl-carpe-…","object":"chat.completion.chunk","created":1780845678,"model":"claude-opus-4-7","choices":[],"usage":{"prompt_tokens":234,"completion_tokens":187,"total_tokens":421,"carpe_cost_usdc_micro":12345,"carpe_credits":1.2345}}

data: [DONE]

A client that stops reading at [DONE] (the OpenAI SDKs do) therefore still receives the cost. The upstream may also emit its own usage chunk earlier in the stream (token counts only); the last usage you see is Carpe's and is the authoritative one. The same framing applies to /router/chat/completions when the router streams.

Strict OpenAI parsers (e.g. opencode, AI SDK typed validators) ignore unknown fields inside usage by convention — the chunk is shape-valid, so the parser never throws. Tolerant clients can read usage.carpe_cost_usdc_micro to update a credit display in real time.

Prior to operator v145 (2026-06-08) the cost was emitted in a non-canonical { "carpe_billing": { … } } chunk after [DONE]. That shape broke strict parsers. v145 retires it: read usage.carpe_cost_usdc_micro going forward. Older operators also wrote the usage chunk after the upstream's [DONE] with no terminator behind it; clients that read to end of stream keep working either way.

Stream errors

A stream that dies after the 200 has been committed cannot become an HTTP error code — the status is already on the wire. The operator instead emits one final chunk in OpenAI's streaming error envelope, which strict parsers accept as part of the chunk union:

data: {"error":{"message":"Provider dropped the stream. Please retry.","type":"upstream_error","code":"PROVIDER_STREAM_INTERRUPTED"}}
codeMeaningRetry?
PROVIDER_STREAM_INTERRUPTEDThe upstream connection dropped mid-answer.Yes — a different provider key is usually picked.
PROVIDER_STREAM_IDLEThe upstream stopped sending data for STREAM_IDLE_TIMEOUT_MS (90s) and the socket was reclaimed.Yes.
STREAM_MAX_DURATIONThe answer ran past the operator's absolute ceiling (STREAM_MAX_DURATION_MS, 1h).Not as-is — split the request into smaller steps.

A stream that ends this way carries no finish_reason and no usage, and is not billed. On /v1/messages the same conditions produce Anthropic's event: error envelope instead.

Prior to 2026-09-01 this was a non-canonical { "carpe_error": { … } } chunk. It satisfied neither branch of the OpenAI chunk union, so strict clients surfaced a schema-validation dump instead of the message — the same defect carpe_billing had, in the object next to it. Streaming answers were also capped at the model's wall-clock timeout regardless of whether they were still making progress; they are now bounded by silence, not by duration.

Snapshot endpoints

When an agent needs to check its budget without making an inference call:

GET /v1/credits (or alias GET /v1/billing/balance) — requires authentication.

{
  "escrowUsdc": 12.47,
  "pendingUsdc": 0.35,
  "holdsUsdc": 0,
  "availableUsdc": 12.12,
  "escrowCredits": 1247,
  "pendingCredits": 35,
  "holdsCredits": 0,
  "availableCredits": 1212,
  "updatedAt": "2026-05-10T15:42:08.123Z"
}

GET /v1/rate_limits — requires authentication. Returns the configured limit, current window, and remaining requests for the caller.

{
  "max": 100,
  "windowMs": 60000,
  "keying": "per-wallet (or per-IP for unauthenticated)",
  "remaining": 87,
  "resetSeconds": 42
}

Buy Credits

  • Dashboard: Visit the buyer dashboard → Buy Credits
  • Direct deposit: Send USDC to the escrow contract on Base
  • Any token: Use the cross-chain deposit widget (powered by LI.FI) — deposit ETH, USDT, DAI, WETH, cbETH from any chain

10. Usage Tracking

Usage History (Paginated)

GET /buyer/usage?limit=20&offset=0

Requires authentication.

ParameterTypeDefaultDescription
limitinteger20Results per page (1–100).
offsetinteger0Pagination offset.

Response:

{
  "events": [
    {
      "id": 42,
      "model": "llama-3.3-70b",
      "provider": "0xAbc...",
      "prompt_tokens": 150,
      "completion_tokens": 300,
      "cost_usdc": 0.000045,
      "multiplier": 0.35,
      "created_at": "2026-02-18 14:30:00"
    }
  ],
  "total": 1542
}

Usage Summary (Aggregated)

GET /buyer/usage/summary?days=7

Requires authentication.

ParameterTypeDefaultDescription
daysinteger7Lookback period (1–90).

Response:

{
  "byModel": [
    { "model": "llama-3.3-70b", "totalRequests": 150, "totalTokens": 45000, "totalCost": 0.0135 }
  ],
  "byDay": [
    { "date": "2026-02-18", "totalCost": 0.005, "totalRequests": 42 }
  ],
  "totals": {
    "requests": 150,
    "tokens": 45000,
    "cost": 0.0135
  }
}

Note: cost values are in USDC. Multiply by 100 to convert to credits.


11. Error Handling

Error Response Format

All errors follow a consistent format:

{
  "error": "Human-readable error message",
  "code": "ERROR_CODE"
}

Error Codes

HTTPCodeDescriptionRetry?
400BAD_REQUESTInvalid request parametersNo
400INVALID_MODELModel name invalid or malformedNo
400MODEL_ERRORModel not available on providerNo
400VENICE_BAD_REQUESTProvider rejected the requestNo
400NOT_JWTTried to refresh an API key (use JWT)No
400REPLAY_REJECTEDProvision message expired (>5 min)No
401AUTH_REQUIREDMissing or invalid auth tokenNo
401AUTH_FAILEDWallet signature verification failedNo
401TOKEN_EXPIREDJWT expired, re-authenticateNo
402PAYMENT_REQUIREDInsufficient creditsNo
403JWT_REQUIREDAPI key management requires JWT sessionNo
403ADMIN_REQUIREDAdmin-only endpointNo
403INVALID_SIGNATUREProvision signature invalidNo
409DUPLICATE_KEYVenice API key already provisioned by another providerNo
413PAYLOAD_TOO_LARGEImage exceeds 5 MBNo
429RATE_LIMITEDToo many requests (per-wallet limit, see /v1/rate_limits)Yes — wait, honour Retry-After
429UPSTREAM_RATE_LIMITAll providers rate-limitedYes — backoff
429FAUCET_COOLDOWNTestnet faucet: 1 claim per 24hYes — wait
502VENICE_ERRORProvider returned an errorYes — backoff
502QUOTE_FAILEDDeposit swap quote failedYes — backoff
503TEE_NOT_READYNo provider keys provisioned yetYes — later
503NO_PROVIDERSNo providers availableYes — later
503NO_PROVIDER_CAPACITYAll providers at capacityYes — backoff
503UPSTREAM_AUTH_FAILEDAll provider keys invalidNo — re-provision
503BALANCE_CHECK_FAILEDOn-chain balance check failedYes — backoff
503SETTLEMENT_DISABLEDSettlement not configuredNo
503FAUCET_DISABLEDTestnet faucet not configuredNo

Retry Strategy

400, 401, 402, 403, 409, 413 → Do NOT retry
429                           → Retry after delay (respect Retry-After header if present)
502, 503                      → Retry with exponential backoff

Python Retry Wrapper

import time
from openai import OpenAI, APIError, RateLimitError

client = OpenAI(
    base_url="https://carpe-diem.xyz/api/operator/v1",
    api_key="cdm_your_api_key_here",
)

def chat_with_retry(messages, model="llama-3.3-70b", max_retries=3):
    for attempt in range(max_retries):
        try:
            return client.chat.completions.create(
                model=model,
                messages=messages,
            )
        except RateLimitError:
            wait = 2 ** attempt
            print(f"Rate limited, waiting {wait}s...")
            time.sleep(wait)
        except APIError as e:
            if e.status_code in (502, 503):
                wait = 2 ** attempt
                print(f"Server error {e.status_code}, retrying in {wait}s...")
                time.sleep(wait)
            else:
                raise
    raise Exception("Max retries exceeded")

JavaScript Retry Wrapper

import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://carpe-diem.xyz/api/operator/v1",
  apiKey: "cdm_your_api_key_here",
});

async function chatWithRetry(messages, model = "llama-3.3-70b", maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await client.chat.completions.create({ model, messages });
    } catch (error) {
      if (error.status === 429 || error.status === 502 || error.status === 503) {
        const wait = 2 ** attempt * 1000;
        console.log(`Error ${error.status}, retrying in ${wait}ms...`);
        await new Promise((r) => setTimeout(r, wait));
      } else {
        throw error;
      }
    }
  }
  throw new Error("Max retries exceeded");
}

12. Rate Limits

LimitValue
Requests per minute30 per wallet
Time window1 minute (rolling)

When rate-limited, you'll receive:

{
  "error": "Too many requests",
  "code": "RATE_LIMITED"
}

Rate limits are per wallet address, regardless of how many API keys you have.


13. Health & Status

Health Check

GET /health

No authentication required.

{
  "status": "healthy",
  "components": {
    "database": { "status": "healthy" },
    "settlement": { "status": "healthy", "detail": "enabled" },
    "providers": { "status": "healthy", "detail": "3 provider(s)" },
    "settlementFreshness": { "status": "healthy", "detail": "last success 2 min ago" }
  }
}
Overall statusHTTP codeMeaning
healthy200All systems operational
degraded200Some components impaired but functional
unhealthy503Critical component down

Prometheus Metrics

GET /metrics

Returns metrics in Prometheus text format. No authentication required.

Includes: settlement counters, provider scores, pending debts, pricing model count.


14. Provider Integration

Providers supply Venice API keys to the network and earn 65% of revenue in DIEM tokens.

Register as a Provider

POST /tee/provision

{
  "apiKey": "your_venice_api_key",
  "message": "Provision Carpe Diem\nWallet: 0xYourProviderWallet\nTimestamp: 1707654321000",
  "signature": "0x..."
}

The message must include a timestamp within the last 5 minutes (replay protection).

Response:

{
  "status": "Provisioned",
  "secure": true,
  "provider": "0xYourProviderWallet",
  "totalProviders": 3
}

List Providers

GET /tee/providers

No authentication required.

{
  "count": 3,
  "providers": [
    {
      "address": "0xAbc...",
      "maskedKey": "vnc_a1b2****",
      "metrics": {
        "successRate": 0.95,
        "avgLatency": 1200,
        "headroom": 0.8,
        "score": 0.85,
        "requestCount": 1542
      }
    }
  ]
}

Provider Selection

Carpe Diem automatically routes requests across providers using a composite score:

  • Success rate (50% weight, time-decayed)
  • Latency (30% weight)
  • Headroom (20% weight)
  • Score floor of 20% ensures penalized providers can recover
  • Tiebreaker: least recently selected provider wins

Revenue

Providers earn 65% of each request's cost, settled on-chain in DIEM tokens. The remaining 35% is retained as protocol revenue (treasury + operating costs + future external audit funding).

Admin: Revoke a Provider

DELETE /tee/providers/:address

Requires admin authentication.


15. Endpoint Reference

MethodPathAuthDescription
GET/healthNoSystem health check
GET/metricsNoPrometheus metrics
GET/modelsNoList all available models
POST/auth/sessionNoCreate JWT session (wallet signature)
POST/auth/refreshJWT or refresh tokenRefresh JWT session (email sessions: rotating refresh token, 30 days)
POST/auth/email/startNoEmail sign-in: send a 6-digit code + fallback link
POST/auth/email/verify-codeNoEmail sign-in with the code → JWT + refresh token
POST/auth/email/verifyNoEmail sign-in with the link → JWT + refresh token
POST/auth/logoutRefresh tokenEnd one email session
POST/auth/logout-allJWTEnd every email session of the account
POST/auth/step-up/startJWT (email)Send a re-verification code
POST/auth/step-up/verifyJWT (email)Re-verify → 10-min token for API key create/revoke
POST/auth/api-keysJWTCreate API key (email session: re-verified token)
GET/auth/api-keysJWTList API keys
DELETE/auth/api-keys/:idJWTRevoke API key (email session: re-verified token)
POST/v1/chat/completionsYesChat completions — OpenAI-compatible (streaming/non-streaming)
POST/v1/messagesYesMessages API — Anthropic-compatible (Claude Code, Cursor, Cline)
POST/v1/image/generateYesGenerate image
POST/v1/image/editYesEdit image
POST/v1/video/quoteYesGet video generation quote
POST/v1/video/queueYesQueue video generation
POST/v1/video/retrieveYesRetrieve video result
GET/v1/creditsYesBalance snapshot (credits or prepaid)
POST/v1/prepaid/registerYesRegister your dedicated prepaid smart account
GET/v1/prepaid/statusYesPrepaid account balance + remaining allowance
GET/v1/credits/migrate/quoteYesMigratable credit balance + EIP-712 domain
POST/v1/credits/migrateYesMigrate credits → your prepaid account (prepaid-only)
POST/v1/x402/…None (signed)Pay-per-request from a wallet (agents) — see API reference
GET/router/marketsNoneBest-price router: live market snapshot (rolling out)
POST/router/quoteNoneQuote a request across Carpe + external markets (read-only)
POST/router/chat/completionsYesChat via the best-price router + X-Carpe-Route-* headers — see API reference
GET/buyer/usageYesUsage history (paginated)
GET/buyer/usage/summaryYesUsage summary (aggregated)
GET/deposits/quoteNoGet deposit swap quote
POST/tee/provisionSignatureRegister as provider
GET/tee/providersNoList providers with scores
GET/tee/statusNoTEE encryption status
DELETE/tee/providers/:addressAdminRevoke provider
GET/provider/usage/summaryNoProvider usage summary
GET/provider/statsNoProvider stats
GET/providers/:wallet/yieldWallet/AdminAPR on the DIEM you sold over 7 days, every revenue counted (Carpe, Carpe Reach, rebate)
GET/providers/:wallet/rewards/dailyWallet/AdminPer-day consumed vs DIEM reward + APR
GET/billing/pendingAdminView pending debts
POST/billing/settle-nowAdminTrigger immediate settlement
POST/testnet/faucetNoClaim testnet tokens (Base Sepolia)