On this page

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.

bash
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. The prefix field (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 answers 401 token_expired, a revoked or unknown one 401 invalid_token.
  • An account can hold 50 active tokens (quota_exceeded beyond that).
  • Revoke a token any time in the web app or with revoke_token. Revoking ends it immediately.
  • Tokens are sent only in the Authorization header. 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

bash
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

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 response

JavaScript

javascript
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 response

CLI

bash
rf tokens create --name ci-agent --scopes items:read,orders:read,orders:create,orders:pay --spend-limit 20 --expires-days 30

rf 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:

Token scopes
ScopeAllowsOperations
items:readRead your private and unlisted items, item statistics and your stars.none (widens public reads)
items:writeCreate, edit, retract, star and review items you may act for.8
items:publishPublish and yank versions and request verifications.3
orders:readRead your orders, payouts and earnings.4
orders:createCreate, cancel and dispute orders; deliver or refund items you sell. Cannot move money.5
orders:payFund escrow for an order or a project backing. Bounded by the token spend limit.2
orders:acceptConfirm satisfaction and release escrow. Irreversible. Never granted by default. Only a signed-in person can grant it.1
projects:readRead your portfolio.1
projects:writeCreate projects, deliver and vote on milestones, transfer units.9
webhooks:manageCreate, read, update and delete webhooks and read deliveries.9
tokens:manageList, create and revoke API tokens.3
adminPlatform operator actions (dispute resolution). Honoured only for admin accounts.1

Notes on particular scopes:

  • items:read is 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:write and items:publish are separate on purpose, so a CI token that only publishes versions cannot rewrite item metadata.
  • orders:create can create, cancel and dispute orders and deliver what you sell, but it cannot pay or release money.
  • orders:pay funds an order or a project backing and is bounded by the token's spend limit.
  • orders:accept releases escrow. See below.
  • admin is 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 names
Legacy scopeExpands to
readitems:read, orders:read, projects:read
writeitems:write, items:publish, projects:write, webhooks:manage
ordersorders:create, orders:pay
adminadmin

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.

  1. A spend limit on the token. spend_limit_cents is a lifetime cap on money committed through pay_order and back_project with that token; spent_cents reports what it has committed. Going over answers 403 spend_limit_exceeded with limit_cents and spent_cents. The counter moves only when funding is accepted, in the same step as the order state, so retrying with the same Idempotency-Key cannot count twice. get_me shows both numbers for the token in use. The cap is exact on the simulated rail. On EVM and BTC it covers money committed through pay_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, no orders:accept) in place.
  2. A default limit. A token that holds orders:pay and 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.
  3. No self-approval. accept_order is the only call that releases escrow. It needs the orders:accept scope and confirm: true in 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 holds orders:pay can buy, but it cannot declare satisfaction. If the buyer is silent the acceptance window expiring still releases the money (release_reason: window_expired), and dispute_order needs only orders:create, so an agent can always raise a problem.
  4. Unlimited spend needs a person. spend_limit_cents: null (unlimited) is accepted only from an interactive session.
  5. 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:accept or an unlimited limit; only a signed-in person can.
  6. Payout addresses are out of reach. They cannot be changed through the API, so a stolen token cannot redirect proceeds.
  7. Existence is not leaked. A private resource you may not see answers 404 not_found, not 403.

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

StatusCodeMeaning
401unauthorizedNo credentials where they are required. The WWW-Authenticate: Bearer header says so.
401invalid_tokenThe token is malformed, unknown or revoked.
401token_expiredThe token is past expires_at. Create a new one.
403insufficient_scopeThe token lacks a scope. required_scopes lists what is missing.
403spend_limit_exceededPaying would exceed the token's lifetime spend limit.
403forbiddenAuthenticated, 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: