API
Authentication and scopes
Bearer tokens, the 12 scopes, legacy scope names, and the spend limit and minting rules that keep an autonomous agent from overspending or accepting its own purchases.
The API authenticates with a personal access token sent as a bearer token. Public reads (search, items, versions, plans by id, projects) work without one, at lower rate limits. Anything that changes data or reads private data needs a token with the right scopes.
curl https://reuseforge.com/api/v1/me -H "Authorization: Bearer $REUSEFORGE_TOKEN"Tokens
- A token looks like
rf_followed by random characters. It is shown once, when it is created. ReuseForge stores only a hash, so a lost token cannot be recovered: revoke it and create another. Theprefixfield (the first ten characters) is what lists show, so you can tell tokens apart. - Tokens expire. The default is 90 days (
expires_in_days; the web app also offers 7, 30, 180, 365 days and no expiry). An expired token answers401 token_expired, a revoked or unknown one401 invalid_token. - An account can hold 50 active tokens (
quota_exceededbeyond that). - Revoke a token any time in the web app or with
revoke_token. Revoking ends it immediately. - Tokens are sent only in the
Authorizationheader. Never put one in a URL, a query string, a repository or a prompt.
Create a token
In the web app: Settings, API tokens. Choose a name, scopes and an expiry. The web app offers the legacy scope names described below, which is the simplest route for a person.
From code, with a token that holds tokens:manage, call create_token. This is how you give a CI job or an agent a narrower token than your own:
curl
curl -X POST https://reuseforge.com/api/v1/tokens \
-H "Authorization: Bearer $REUSEFORGE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "ci-agent", "scopes": ["items:read", "orders:read", "orders:create", "orders:pay"], "expires_in_days": 30, "spend_limit_cents": 2000}'Python
import reuseforge
client = reuseforge.Client()
tok = client.create_token(name="ci-agent", scopes=["items:read", "orders:read", "orders:create", "orders:pay"],
expires_in_days=30, spend_limit_cents=2000)
print(tok["id"], tok["scopes"]) # tok["token"] is the secret, shown only in this responseJavaScript
import { ReuseForge } from '@reuseforge/sdk';
const client = new ReuseForge();
const tok = await client.createToken({ name: 'ci-agent', scopes: ['items:read', 'orders:read', 'orders:create', 'orders:pay'],
expires_in_days: 30, spend_limit_cents: 2000 });
console.log(tok.id, tok.scopes); // tok.token is the secret, shown only in this responseCLI
rf tokens create --name ci-agent --scopes items:read,orders:read,orders:create,orders:pay --spend-limit 20 --expires-days 30rf tokens create defaults to read-only scopes and prints the secret once. The response of create_token is the only time token appears.
Scopes
Every operation lists the scopes it needs. When it lists several, all of them are required. A token without one gets 403 insufficient_scope with the missing scopes in required_scopes. The catalogue, with how many operations use each scope:
| Scope | Allows | Operations |
|---|---|---|
items:read | Read your private and unlisted items, item statistics and your stars. | none (widens public reads) |
items:write | Create, edit, retract, star and review items you may act for. | 8 |
items:publish | Publish and yank versions and request verifications. | 3 |
orders:read | Read your orders, payouts and earnings. | 4 |
orders:create | Create, cancel and dispute orders; deliver or refund items you sell. Cannot move money. | 5 |
orders:pay | Fund escrow for an order or a project backing. Bounded by the token spend limit. | 2 |
orders:accept | Confirm satisfaction and release escrow. Irreversible. Never granted by default. Only a signed-in person can grant it. | 1 |
projects:read | Read your portfolio. | 1 |
projects:write | Create projects, deliver and vote on milestones, transfer units. | 9 |
webhooks:manage | Create, read, update and delete webhooks and read deliveries. | 9 |
tokens:manage | List, create and revoke API tokens. | 3 |
admin | Platform operator actions (dispute resolution). Honoured only for admin accounts. | 1 |
Notes on particular scopes:
items:readis not required by any operation: public reads work without it. It widens what you see, adding your private and unlisted items and your stars.items:writeanditems:publishare separate on purpose, so a CI token that only publishes versions cannot rewrite item metadata.orders:createcan create, cancel and dispute orders and deliver what you sell, but it cannot pay or release money.orders:payfunds an order or a project backing and is bounded by the token's spend limit.orders:acceptreleases escrow. See below.adminis honoured only for accounts that are administrators.
Writing to an organization's items also needs a write role in that organization; get_me lists the organizations you belong to and your role in each.
Legacy scope names
The web app's token page issues four older names. They are expanded to the fine-grained scopes whenever the token is checked, so they work everywhere:
| Legacy scope | Expands to |
|---|---|
read | items:read, orders:read, projects:read |
write | items:write, items:publish, projects:write, webhooks:manage |
orders | orders:create, orders:pay |
admin | admin |
A legacy token therefore never gains orders:accept or tokens:manage. A legacy orders token has orders:pay, so it gets the default spend limit of 5000 cents ($50) unless a limit was set. Prefer the fine-grained scopes for anything automated.
Agents and spending limits
ReuseForge assumes that some callers are autonomous programs that can be tricked by the content they read. These rules are enforced by the server, so a prompt injection cannot talk its way around them.
- A spend limit on the token.
spend_limit_centsis a lifetime cap on money committed throughpay_orderandback_projectwith that token;spent_centsreports what it has committed. Going over answers403 spend_limit_exceededwithlimit_centsandspent_cents. The counter moves only when funding is accepted, in the same step as the order state, so retrying with the sameIdempotency-Keycannot count twice.get_meshows both numbers for the token in use. The cap is exact on the simulated rail. On EVM and BTC it covers money committed throughpay_order; a payment sent on-chain without calling the API is funded by the keeper and cannot be counted against a token, so treat the limit as advisory there and keep the token's other protections (small scopes, noorders:accept) in place. - A default limit. A token that holds
orders:payand was created without a limit gets 5000 cents. Set it explicitly ("spend_limit_cents": 2000) for anything you hand to an agent. A token you create with a token can never have a larger limit than the creator's. - No self-approval.
accept_orderis the only call that releases escrow. It needs theorders:acceptscope andconfirm: truein the body. That scope is never in a default token and can be granted only from an interactive session, that is, by a signed-in person in the web app. An agent that holdsorders:paycan buy, but it cannot declare satisfaction. If the buyer is silent the acceptance window expiring still releases the money (release_reason: window_expired), anddispute_orderneeds onlyorders:create, so an agent can always raise a problem. - Unlimited spend needs a person.
spend_limit_cents: null(unlimited) is accepted only from an interactive session. - Minting is a subset. A token can create tokens whose scopes are a subset of its own and whose limit is not larger. It cannot create a token with
orders:acceptor an unlimited limit; only a signed-in person can. - Payout addresses are out of reach. They cannot be changed through the API, so a stolen token cannot redirect proceeds.
- Existence is not leaked. A private resource you may not see answers
404 not_found, not403.
Release escrow (orders:accept)
Give orders:accept only to a human-operated tool, never to an autonomous agent, never in CI. The hosted MCP server hides accept_order from tokens that lack the scope, and the CLI refuses to try without it (rf mcp serve never forwards it). A good pattern for agents: let the agent hold items:read orders:read orders:create orders:pay with a small limit, and accept deliveries yourself after checking them.
Sessions and cross-site calls
The web app calls the same API with your signed-in session. A session carries every scope your account is entitled to and is treated as interactive. Session cookies are accepted only from the site's own origin: a cross-site request that carries one is refused or treated as anonymous. For any other client, use a token. The API answers CORS preflights and allows any origin without credentials, so a browser app can call it with a bearer token, but never ship a token that can write or pay to a browser.
Failures
| Status | Code | Meaning |
|---|---|---|
| 401 | unauthorized | No credentials where they are required. The WWW-Authenticate: Bearer header says so. |
| 401 | invalid_token | The token is malformed, unknown or revoked. |
| 401 | token_expired | The token is past expires_at. Create a new one. |
| 403 | insufficient_scope | The token lacks a scope. required_scopes lists what is missing. |
| 403 | spend_limit_exceeded | Paying would exceed the token's lifetime spend limit. |
| 403 | forbidden | Authenticated, but not allowed (role, ownership, interactive-only action). |
Failed bearer attempts count against the anonymous per-address read budget, so a script retrying a wrong token is rate limited. Hosted MCP clients that cannot paste a token sign in with OAuth instead, and receive an ordinary short-lived token with the limits you chose on the consent page.
Last updated: