Tori API

Issue, hold, and track AI Credits inside your own product. Every call writes to the same append-only, idempotent, daily-reconciled ledger that powers Tori's card rewards — integer cents, immutable entries, no exceptions.

Base URL: https://www.earntori.com/api/partner/v1

Create a developer account to mint keys and start issuing immediately. New profiles carry a $100 issuance starter limit — contact us to raise it as you scale.

Authentication

Authenticate with a secret key in the Authorization header. Keys look like tori_sk_…, are shown once at creation, and are stored only as hashes. Treat them like passwords: server-side only, never in a browser or repository.

curl https://www.earntori.com/api/partner/v1/users/usr_42/balance \
  -H "Authorization: Bearer tori_sk_your_key_here"

A missing, malformed, or revoked key returns 401.

Idempotency

Send an Idempotency-Key header on any write. Retrying the same key never double-issues: the first call writes the ledger entry, replays return 200 with "duplicate": true and an unchanged balance. Use a stable ID from your own system (an order ID, an event ID) rather than a random value.

Errors

Errors are JSON with an error field. 400 validation, 401 authentication, 500 server. Validation errors include the failing fields.

Issue credits

POST /credits/issue

Credits an end user of your product. Users are identified by your own external_user_id — Tori creates and tracks the account on first touch; no separate user registration call.

FieldTypeNotes
external_user_idstringYour stable ID for the user
amount_centsintegerPositive, max 1,000,000 (=$10,000)
reasonstringShown on the ledger entry
curl -X POST https://www.earntori.com/api/partner/v1/credits/issue \
  -H "Authorization: Bearer tori_sk_your_key_here" \
  -H "Idempotency-Key: order_18732" \
  -H "Content-Type: application/json" \
  -d '{
    "external_user_id": "usr_42",
    "amount_cents": 500,
    "reason": "Purchase reward — order 18732"
  }'

201 {
  "issued": true,
  "duplicate": false,
  "external_user_id": "usr_42",
  "balance_cents": 500
}

Balances

GET /users/:external_user_id/balance

Balances are derived from the ledger on every read — there is no cached balance to drift.

200 {
  "external_user_id": "usr_42",
  "available_cents": 500,
  "pending_cents": 0,
  "reserved_cents": 0,
  "lifetime_earned_cents": 500,
  "lifetime_redeemed_cents": 0
}

Ledger

GET /users/:external_user_id/ledger

The user's most recent 100 entries, newest first. Entries are immutable; corrections appear as offsetting entries, never edits.

200 {
  "external_user_id": "usr_42",
  "entries": [
    {
      "id": "5f0c…",
      "type": "MANUAL_ADJUSTMENT",
      "amount_cents": 500,
      "reason": "Purchase reward — order 18732",
      "effective_at": "2026-07-27T18:04:11.000Z"
    }
  ]
}

Using your AI key

Tori credits can be turned into an AI key from the dashboard — a spend-capped key that works with every major model. The cap equals the credits you redeemed; when it's spent, the key simply stops. No card, no subscription, nothing to cancel.

It works anywhere the OpenAI API format works. Point your tool, SDK, or agent at the base URL below, use your key, and pick a model:

curl https://openrouter.ai/api/v1/chat/completions \
  -H "Authorization: Bearer <your-ai-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-4.5",
    "messages": [{ "role": "user", "content": "Hello" }]
  }'

In an OpenAI SDK, set baseURL to https://openrouter.ai/api/v1 and pass your key as the API key. In tools like Cursor, add it as a custom OpenAI-compatible endpoint with the same base URL. Models are named provider/model, e.g. openai/gpt-4o-mini.

A spent or disabled key returns 401 — that's the cap working, not an outage. Check usage or disable a key anytime from your dashboard under Your AI keys.

Questions or a use case we should know about? Reach out from your developer profile — the roadmap is shaped by what partners need first.