On this page

API

Python SDK

The reuseforge Python package, standard library only: one method per operation, auto-paginating lists, retries, typed errors, safe installs, webhook verification and a CLI.

reuseforge is a client for the whole API: one method per operation, the JSON the server sent, no dependencies beyond the Python 3.9+ standard library, and type hints (py.typed). It behaves exactly like the JavaScript SDK; both are tested against the same examples from the contract.

Not on PyPI yet. Install it from a checkout of the repository: pip install ./sdk/python.

bash
git clone https://github.com/Longfordjames/Claude-1 && cd Claude-1
pip install ./sdk/python
python3 -c "import reuseforge; print(reuseforge.__version__)"

First calls

python
import reuseforge

client = reuseforge.Client()          # token from REUSEFORGE_TOKEN, else the file `rf login` wrote, else anonymous

for item in client.search_items(q="invoice pdf", kind=["skill", "harness"], max_price_cents=2000):   # auto-paginates
    print(item["full_name"], reuseforge.dollars(item["price_cents"]))

plan = client.plan_goal(goal="RAG chatbot over invoices", budget_cents=5000, online=True)
print(plan["recommended"]["paid_total_cents"], "cents to buy,", len(plan["pareto_front"]), "options")

result = client.install("acme/invoice-extractor", target="claude-code", dest=".")     # never pays, never runs anything
print(result["version"], [f["path"] for f in result["files"]])

Calling conventions

  • Names. One method per operationId, verbatim: client.search_items, client.get_install_descriptor, client.create_order. No namespaces. The reference shows the exact call for every operation.
  • Arguments. Path parameters are positional in path order, except that owner and slug collapse into one item argument: "owner/slug", or an item object (its full_name is used). Query parameters and body fields are keyword arguments. Lists for comma-separated query parameters (kind, capability, license) are joined for you.
  • Results. Plain dicts with the wire keys, exactly what curl prints. Nothing is renamed or converted. TypedDicts for every schema live in reuseforge.models for editors and type checkers.
  • Money. Integer cents. reuseforge.dollars(1200) == "12.00" and reuseforge.cents("12.00") == 1200 exist for people; cents parses strings and Decimal, never floats.

Configuration

Resolved highest first: arguments, environment (REUSEFORGE_TOKEN, REUSEFORGE_API_URL, REUSEFORGE_PROFILE), ./.reuseforge/config.json (never tokens), ~/.config/reuseforge/config.json, then credentials.json next to it (mode 0600; a file other users can read is refused), then anonymous against https://reuseforge.com/api/v1. REUSEFORGE_CONFIG_DIR moves the directory. The CLI and both SDKs share all of it.

python
client = reuseforge.Client(token="rf_...", base_url="https://reuseforge.com/api/v1", timeout=30.0, max_retries=2)
fast = client.with_options(timeout=5, max_retries=0)          # a clone with different defaults
client.last_response.request_id, client.last_response.rate_limit, client.last_response.etag

The token is never logged, never in an exception message, and repr(client) shows rf_...abcd. It is not sent over plain http except to loopback addresses. Timeouts are per request: 30 seconds, and 60 for plan_goal, discover_external and pay_order.

Pagination

List methods return a Page: .data, .next_cursor, .has_more. Iterating it fetches further pages lazily, page.pages() iterates page objects, and max_items= stops early. See pagination.

python
page = client.search_items(q="pdf", limit=100)
print(len(page.data), page.has_more)
for item in client.search_items(q="pdf", max_items=500):
    ...

ETags, retries, idempotency

python
item = client.get_item("acme/invoice-extractor")
same = client.get_item("acme/invoice-extractor", etag=client.last_response.etag)    # None on 304
client.update_item("acme/invoice-extractor", {"summary": "New summary"}, if_match="auto")   # reads the ETag first
  • Retried up to max_retries (default 2): network errors and timeouts, 429, 502, 503, 504, and 500 for GET, PUT, DELETE and for a POST that carries an Idempotency-Key. Other 4xx are never retried.
  • Backoff is min(8 s, 0.5 s * 2^attempt) with full jitter. Retry-After wins up to 60 seconds; a longer one raises immediately.
  • Every operation that declares Idempotency-Key gets a fresh UUID that is reused across the retries of that call. Pass idempotency_key="..." to control it. See idempotency.

Errors

All failures derive from ReuseForgeError with status, code, title, detail, request_id, errors, problem (the raw body) and headers.

ClassWhen
AuthenticationError401
ForbiddenError403, with required_scopes; SpendLimitError for spend_limit_exceeded
PaymentRequiredError402, with order_hint
NotFoundError404, 410
ConflictError409, 412, 428 (problem["current_etag"])
ValidationError400, 413, 415, 422
RateLimitError429, with retry_after
ServerError5xx, including a proxy's HTML error page (code internal)
NetworkErrorno response
IntegrityError, UnsafePathErrora hash did not match, or an install path escaped the root (client side)
SignatureErrorwebhook verification failed
python
try:
    client.pay_order("ord_tqjv5mw3k2na")
except reuseforge.SpendLimitError as e:
    print("token may spend", e.problem["limit_cents"], "and has spent", e.problem["spent_cents"])
except reuseforge.RateLimitError as e:
    print("retry in", e.retry_after)

See errors for every code.

Installing safely

client.install(item, target, dest) fetches the install descriptor and the bundle, then:

  1. verifies every file's sha256 and the bundle content_hash before writing anything;
  2. refuses path traversal, absolute paths, symlink escapes and oversize bundles (100 files, 512 KiB each, 2 MiB per version);
  3. never overwrites a file that differs from reuseforge.lock unless force=True;
  4. writes atomically (rolling back on failure), strips setuid and setgid, and caps modes at 0755;
  5. merges JSON, TOML and Markdown config patches without clobbering your keys, and records everything in reuseforge.lock.

It never runs a package's commands and never pays: a paid item you do not own raises PaymentRequiredError whose order_hint is the order to create.

python
reuseforge.content_hash({"SKILL.md": b"...", "src/run.py": b"..."})     # "sha256:..."
reuseforge.verify_bundle(bundle)                                          # IntegrityError on any mismatch
lock = reuseforge.Lockfile.read("."); lock.verify(".")                    # offline drift check of installed files
manifest = reuseforge.read_manifest(".")                                  # reuseforge.json, schema-checked

Buying

python
plan = client.plan_goal(goal="RAG chatbot over invoices", budget_cents=5000)
order = client.create_order(plan_id=plan["id"], rail="simulated")       # Idempotency-Key generated for you
order = client.pay_order(order["id"])
order = client.wait_for_order(order["id"], states=("funded", "settled"))   # polls with backoff; WaitTimeoutError on timeout

Accepting delivery (accept_order) needs the orders:accept scope; see authentication. The orders operations are not enabled on reuseforge.com yet (see the availability table).

Webhooks

python
event = reuseforge.webhooks.verify(raw_body, headers["ReuseForge-Signature"], secret)   # tolerance 300 s, rotation-safe

Pass the raw body bytes. Details, retries and a receiver: webhooks.

The reuseforge command

The package also installs a command line, reuseforge (and rf), with the same flags, exit codes and files as the Node CLI. It covers login, search, plan, install, update, list, verify, uninstall, init, publish, orders, doctor and more; everything else is available as SDK methods. The full command set, generated from the real --help, is on the CLI reference page.

bash
reuseforge login --with-token < token.txt
reuseforge search "invoice pdf" --kind skill --max-price 20 --verified
reuseforge install acme/invoice-extractor@^1.2 --target claude-code

Development

bash
cd sdk/python
python3 -m unittest discover -s tests          # offline: a stub transport serves the contract's own examples
python3 tools/gen_models.py --check            # models.py and the operation table match api/openapi.json

The offline suite answers every operation with the first 2xx example from the contract, so the OpenAPI examples are the shared oracle for both SDKs and the Worker.

Last updated: