TokenTip
TokenTip API

Your tip jar, programmatically.

Read your tips and balance, rotate your inference key, and receive the tip lifecycle as it happens — over signed webhooks or a live stream. One bearer token, exact-decimal money, and a contract published as OpenAPI and AsyncAPI.

Base URL

https://tokentip.to/v1

Authentication

Every request carries a bearer token minted in your dashboard. Tokens start with tt_, are shown exactly once, and are stored only as a SHA-256 hash — we cannot recover one for you.

curl -H "Authorization: Bearer tt_your_token" https://tokentip.to/v1/me

Scopes

A token is one of two scopes, enforced server-side on every endpoint and event:

ScopeSeesCan do
creatorIts own account only — its tips, balance, key.Read, manage webhooks, rotate its key.
operatorThe whole system, every creator.Everything, plus force-settle and disable a key.

A creator token calling an /v1/operator/* endpoint gets 403.

Quickstart

Fetch the authenticated account and its current inference credit:

curl -H "Authorization: Bearer tt_your_token" https://tokentip.to/v1/me
from tokentip import TokenTip

with TokenTip("tt_your_token") as tt:
    me = tt.me()
    print(me.handle, me.credit)   # credit is a Decimal, never a float
tt := tokentip.New("tt_your_token")

me, err := tt.Me(context.Background())
if err != nil {
    log.Fatal(err)
}
fmt.Println(me.Handle, me.Credit)
import { TokenTip } from "@copyleftdev/tokentip";

const tt = new TokenTip("tt_your_token");
const me = await tt.me();
console.log(me.handle, me.credit);

Conventions

Money is never a float

Every monetary value is an exact decimal string with six-decimal precision — the same arithmetic the server settles with. Parse it into a decimal type (Decimal, big.Rat, decimal.js), never a binary float.

{ "gross": "5.000000", "net_to_tokens": "4.068225" }

Cursor pagination

List endpoints take limit (1–100, default 40) and return next_cursor. Pass it back as cursor for the next page; null means you have reached the end.

curl -H "Authorization: Bearer tt_…" \
  "https://tokentip.to/v1/tips?limit=20&status=settled&cursor=2026-07-19T10:03:00Z"

Idempotency

Mutating calls accept an Idempotency-Key header (1–255 characters) so a retry after a timeout cannot double-apply. Reuse the same key for the same logical operation. Replayed responses carry Idempotent-Replay: true; a request that arrives while an identical one is still running gets 409.

One exception, deliberately. POST /v1/key/rotate returns a secret the server never stores, so a replay cannot hand it back — it returns 409 explaining that the rotation already happened. The key still does its job: your retry will not silently mint a second key and disable the one you just received.

Keys are remembered for 24 hours.

Rate limits

Requests are limited per token — per client address when unauthenticated — to a sustained 4 requests/second with a burst of 120. Exceeding it returns 429 with a Retry-After header in seconds; wait that long and retry.

Concurrent event streams are capped at 5 per creator. Opening a sixth returns 429. Close streams you are not reading — the budget is released as soon as the connection drops.

Errors

Every non-2xx response is application/problem+json (RFC 7807). The SDKs map these onto typed exceptions.

{ "title": "not an operator", "status": 403,
  "detail": "This endpoint requires an operator-scoped token." }
StatusMeaning
401Missing, malformed, or unknown token.
403Valid token, wrong scope — operator required.
404No such resource, or not yours.
409Conflict — e.g. rotating a key that changed underneath you.
429Rate limited, or too many open streams. See Retry-After.
502Upstream (OpenRouter) failed; safe to retry.

The contract

Both specs are published and validate clean. They share components/schemas.yaml, so a Tip is the same shape whether you read it from REST or receive it in an event — the two surfaces cannot drift.