API
Errors
Every API error is application/problem+json with a stable code. The full catalogue of 43 codes with status, meaning, the fix and an example response for each.
Every non-2xx response is application/problem+json (RFC 9457). Branch on code, show detail to people, and quote request_id when you ask for help.
HTTP/2 404
content-type: application/problem+json; charset=utf-8
x-request-id: req_yzyjoipk6hhhoco2
{
"type": "https://reuseforge.com/docs/api/errors/not_found",
"title": "Not found",
"status": 404,
"detail": "No item \"nope/nope\".",
"instance": "/api/v1/items/nope/nope",
"code": "not_found",
"request_id": "req_yzyjoipk6hhhoco2"
}| Member | Meaning |
|---|---|
type | A URL on this site that documents the code. It resolves to the page for that code in the catalogue. |
title | Short and stable for a code. Safe to match on, but code is the contract. |
status | The HTTP status, repeated. |
detail | A sentence for people. It can change; never parse it. |
instance | The request path. |
code | The stable machine-readable code from the table below. New codes can be added in a minor release, so keep a default branch. |
request_id | The same value as the X-Request-Id response header. |
| other members | Defined per code: errors (field problems), required_scopes, limit_cents, spent_cents, retry_after, state, current_etag, order_hint. |
Field problems
validation_failed (422) and invalid_parameter / invalid_request (400) carry an errors array. Each entry has a JSON pointer into the request, a short code and a message:
{
"type": "https://reuseforge.com/docs/api/errors/invalid_parameter",
"title": "Invalid parameter",
"status": 400,
"detail": "One or more query parameters are invalid. See `errors`.",
"instance": "/api/v1/items",
"code": "invalid_parameter",
"request_id": "req_ztx7yicxbbkthgvp",
"errors": [{ "pointer": "#/limit", "code": "out_of_range", "message": "Must be at most 100." }]
}Show the message next to the field the pointer names. Unknown fields in a request body are rejected, while unknown fields in a response must be ignored.
What to retry
| Situation | Retry? |
|---|---|
| Network failure, timeout | Yes, with backoff. Safe for reads, and for writes that carry an Idempotency-Key. |
429 rate_limited | Yes, after Retry-After seconds. See rate limits. |
502, 503, 504 | Yes, with backoff (503 unavailable and upstream_error carry retry_after). |
500 internal | Only for idempotent calls (GET, PUT, DELETE, or a POST with an Idempotency-Key). |
409 idempotency_in_progress | Yes, once retry_after has passed. The first call is still running. |
| Any other 4xx | No. Change the request. |
The Python and JavaScript SDKs implement exactly this table, and map each class of error to a typed exception.
Handling errors
Python
import reuseforge
from reuseforge import ReuseForgeError, PaymentRequiredError, RateLimitError
client = reuseforge.Client()
try:
client.install("acme/paid-tool", target="claude-code", dest=".")
except PaymentRequiredError as e:
print("buy first:", e.order_hint) # the create_order request that buys it
except RateLimitError as e:
print("slow down, retry in", e.retry_after)
except ReuseForgeError as e:
print(e.status, e.code, e.detail, e.request_id)JavaScript
import { ReuseForge, PaymentRequiredError, RateLimitError, ReuseForgeError } from '@reuseforge/sdk';
const client = new ReuseForge();
try {
await client.getInstallDescriptor('acme/paid-tool');
} catch (e) {
if (e instanceof PaymentRequiredError) console.log('buy first:', e.orderHint);
else if (e instanceof RateLimitError) console.log('retry in', e.retryAfter);
else if (e instanceof ReuseForgeError) console.log(e.status, e.code, e.detail, e.requestId);
else throw e;
}curl
# -f makes curl fail on 4xx/5xx; the problem document is still printed with --fail-with-body
curl --fail-with-body -sS https://reuseforge.com/api/v1/items/acme/missing || trueCLI
rf items get acme/missing --json # exit code 4; stdout carries the problem plus exit_code
echo $?The CLI maps errors to exit codes (3 authentication, 4 not found, 5 conflict, 6 invalid input, 7 payment, 8 rate limited, 9 network or server, 10 integrity, 11 confirmation); see the CLI reference.
Catalogue
Each code has its own page with a typical response and the fix. The type member of a problem links straight there.
| Code | HTTP | Meaning | Extra members |
|---|---|---|---|
invalid_request | 400 | The request is malformed in a way that fits no narrower code. | errors |
invalid_json | 400 | The body is not a JSON object. | none |
invalid_parameter | 400 | A query or path parameter is unknown or has the wrong form. | errors |
invalid_cursor | 400 | The pagination cursor expired or was altered. | none |
validation_failed | 422 | The body is valid JSON but some fields are not valid. | errors |
unsupported_media_type | 415 | The request body is not application/json. | none |
payload_too_large | 413 | The body, a file or a version is over the published limits. | none |
unauthorized | 401 | The operation needs a token and the request had none. | none |
invalid_token | 401 | The token is malformed, unknown or revoked. | none |
token_expired | 401 | The token is past its expiry date. | none |
insufficient_scope | 403 | The token lacks a scope the operation needs. | required_scopes |
forbidden | 403 | You are signed in but not allowed to do this. | none |
account_suspended | 403 | The account is suspended. | none |
email_unverified | 403 | Paid listings need a verified email address. | none |
spend_limit_exceeded | 403 | The token's lifetime spend limit would be exceeded. | limit_cents, spent_cents |
purchase_required | 403 | Reviewing or accessing this needs a verified purchase. | none |
payment_required | 402 | The content is paid and you have no entitlement. | order_hint |
payment_invalid | 422 | The submitted transaction does not match the order. | none |
rail_unsupported | 422 | The rail is disabled or not valid for these items. | none |
not_found | 404 | No such resource, or it is not visible to you. | none |
method_not_allowed | 405 | The path exists but not for this HTTP method. | none |
gone | 410 | The resource was removed for good. | none |
plan_expired | 410 | Plans can be fetched for 24 hours only. | none |
conflict | 409 | The request conflicts with the current state of the resource. | none |
already_exists | 409 | A duplicate slug, version, review or webhook. | none |
invalid_state | 409 | The lifecycle does not allow this now. | state |
idempotency_key_required | 400 | A call that moves money was sent without an Idempotency-Key. | none |
idempotency_key_reuse | 422 | The same key was used for a different request. | none |
idempotency_in_progress | 409 | The first request with this key is still running. | retry_after |
precondition_failed | 412 | If-Match is stale: the resource changed since you read it. | current_etag |
precondition_required | 428 | If-Match is required and was not sent. | none |
quota_exceeded | 403 | An account limit was reached (50 tokens, 20 webhook endpoints, item count). Publishing a version that does not fit the owning account's storage quota (20 MB unverified, 200 MB verified email, 1 GB verified publisher) answers the same code with status 413. | none |
version_quarantined | 403 | The security scan or a moderator blocked this version, so its files, install descriptor and archive are not served. | none |
review_removed | 403 | A moderator removed your review of this item. It stays as a tombstone, so it cannot be deleted, edited or posted again. | none |
rate_limited | 429 | A rate limit tier is used up. | retry_after |
webhook_url_forbidden | 422 | The URL fails the safety rules for webhook targets. | none |
invalid_signature | 422 | A ledger event's signature or hash is invalid. | none |
integrity_mismatch | 422 | A content_hash or file sha256 differs from the server's computation. | none |
upstream_error | 502 | A connector or a chain RPC failed. | retry_after |
upstream_timeout | 504 | An upstream source was too slow. | none |
not_implemented | 501 | The operation is in the contract but this deployment does not provide it yet. | none |
internal | 500 | Our fault. | none |
unavailable | 503 | Maintenance, or a dependency is down. | retry_after |
Last updated: