API

FARPY Render API quickstart

Inspect a .blend, read the locked quote, start only after you accept it with FARPY_LEGAL_V1, then download the ZIP and receipt. Start is the spend boundary.

FAST PATH

  1. Sign in at /signin and fund the wallet at /topup/.
  2. Use a Job API farpy_agent_ key with scopes jobs:inspect, jobs:quote, jobs:start, and jobs:status. Set max_job_cents to the most one start may reserve. An Account Settings farpy_ key is a different store.
  3. Download the example or run the curl block below.
export FARPY_AGENT_KEY
export FARPY_MAX_JOB_CENTS
python3 farpy_client.py availability
python3 farpy_client.py prepare scene.blend
python3 farpy_client.py start UPLOAD_ID --quote-id QTE-... --legal-acceptance FARPY_LEGAL_V1 --idempotency-key farpy-start:UPLOAD_ID:QTE-...

prepare / inspect / quote do not spend. start is the reservation boundary. Pass legal_acceptance=FARPY_LEGAL_V1 explicitly.

Explicit API flow

Use the full curl workflow when you want to see and control every request.

1. Auth (three systems)

Browser: session cookie. Inspect/start: Job API farpy_agent_… Bearer key. Public API V1 /v1/renders: Account Settings farpy_… key. These stores are not the same. Identity convergence is later work.

export FARPY_AGENT_KEY="farpy_agent_..."

2. Inspect, read the quote, then start

Save this as a shell script. inspect does not reserve funds. start withquote_id and legal_acceptance=FARPY_LEGAL_V1 is the spend boundary. Do not send price_cents.

set -euo pipefail
# Website upload remains the customer path. This is the API path that completed
# a Blender 5.2.2 job: inspect, server quote, one explicit start, receipt, download.
# Do not put a key in this file. Account Settings farpy_ keys do not authorize it.

: "\${FARPY_AGENT_KEY:?Export a farpy_agent_ key with jobs:inspect jobs:quote jobs:start jobs:status}"
ORIGIN="https://farpy.com"
FILE="\${1:-scene.blend}"
MAX_CHARGE_CENTS="\${FARPY_MAX_JOB_CENTS:?Export the key's max_job_cents. Start is refused above it.}"
AUTH=(-H "Authorization: Bearer $FARPY_AGENT_KEY" -A farpy_client)
test -f "$FILE"

echo "1. Sign in at $ORIGIN/signin/ and fund the wallet at $ORIGIN/topup/ before start."
echo "2. Live availability. Read remaining_jobs from this response. Do not hardcode it."
curl -fsS "$ORIGIN/node/v1/blender52/availability" | jq '{open,accepting,reason,active_jobs,remaining_jobs,entrypoint,runtime}'

echo "3. Inspect. This does not reserve or debit."
INSPECT="$(curl -fsS "\${AUTH[@]}" -F "file=@$FILE;filename=$(basename "$FILE")" "$ORIGIN/node/v1/uploads/inspect")"
echo "$INSPECT" | jq '{ok,upload_id,quote_id,frame_start,frame_end,frame_count,price_cents,payment_status,quote}'
UPLOAD="$(printf '%s' "$INSPECT" | jq -er '.upload_id')"
QUOTE="$(printf '%s' "$INSPECT" | jq -er '.quote_id // .quote.quote_id')"
PRICE="$(printf '%s' "$INSPECT" | jq -er '.price_cents // .quote.price_cents')"
FRAMES="$(printf '%s' "$INSPECT" | jq -er '.frame_count // .quote.frame_count')"
test "$PRICE" -ge 1 -a "$PRICE" -le "$MAX_CHARGE_CENTS"
test "$FRAMES" -ge 1 -a "$FRAMES" -le 10

echo "4. Approve that quote. No price_cents field. The key cap is the maximum charge."
IDEM="farpy-start:$UPLOAD:$QUOTE"
BODY="$(jq -nc --arg quote_id "$QUOTE" '{quote_id:$quote_id, legal_acceptance:"FARPY_LEGAL_V1"}')"
# If this curl times out or returns 502/503/504, run the SAME curl again.
# Do not upload a new file and do not change Idempotency-Key.
# A failed record with a released reservation replays that failure and does not create a replacement job.
START="$(curl -fsS --max-time 180 "\${AUTH[@]}" -H "Idempotency-Key: $IDEM" -H "content-type: application/json" --data-binary "$BODY" "$ORIGIN/node/v1/uploads/$UPLOAD/start")"
echo "$START" | jq '{ok,job_id,quote_id,price_cents,payment_status,status,error}'
JOB="$(printf '%s' "$START" | jq -er '.job_id')"
RECEIPT_TOKEN="$(printf '%s' "$START" | jq -er '.receipt_token')"
DOWNLOAD_TOKEN="$(printf '%s' "$START" | jq -er '.download_token')"

echo "5. Poll until status is complete. 409 receipt_unavailable means retry this GET, not a new start."
while true; do
  STATUS="$(curl -fsS "\${AUTH[@]}" "$ORIGIN/node/v1/jobs/$JOB")"
  echo "$STATUS" | jq '{job_id,status,payment_status,reservation_status,price_cents}'
  STATE="$(printf '%s' "$STATUS" | jq -er '.status')"
  case "$STATE" in
    complete) break ;;
    failed|cancelled|expired|refunded) echo "Terminal $STATE. Do not start a replacement." >&2; exit 1 ;;
  esac
  sleep 10
done
curl -fsS "\${AUTH[@]}" "$ORIGIN/node/v1/jobs/$JOB/receipt?token=$RECEIPT_TOKEN" -o "$JOB.receipt.json"
curl -fsS "\${AUTH[@]}" "$ORIGIN/node/v1/jobs/$JOB/download?token=$DOWNLOAD_TOKEN" -o "$JOB.zip"
python3 - "$JOB.zip" "$JOB.receipt.json" <<'PY'
import hashlib, json, sys
got = hashlib.sha256(open(sys.argv[1], "rb").read()).hexdigest()
want = json.load(open(sys.argv[2], encoding="utf-8")).get("output_sha256")
print("download_sha256", got)
print("receipt_sha256", want)
if got != want:
    raise SystemExit("download does not match the receipt")
PY

MCP is not validated

Do not send User-Agent: farpy-mcp-v1 on this path. That user agent is classified as MCP, and MCP ordering has not been validated. Use farpy_client. See MCP for agents for the unvalidated surface.

Discover current service facts first

The job API health endpoint is GET /node/v1/health on https://farpy.com.https://farpy.com/health is not that endpoint. Cohort admission is GET /node/v1/blender52/availability. Read remaining_jobs and active_jobs from that response.

curl -fsS https://farpy.com/node/v1/health

After health succeeds, inspect current renderer capabilities, public pricing, request limits, and the OpenAPI contract.

Production flow

inspect (.blend)
read locked quote (NO SPEND)
decide
start (quote_id + FARPY_LEGAL_V1) — SPEND
status
download ZIP
receipt / proof

API properties

  • Public health preflight before authenticated work
  • API-key authentication
  • Price: the locked quote. Limited website and API ordering are open: 1–10 frames per job, one active job, shared slots, while slots remain. MCP ordering has not been validated.
  • Frame ranges from 1 to 10 frames during limited availability per request
  • 100 MiB upload limit
  • Idempotent submission support
  • 60 requests per minute plus burst capacity
  • Cancellation before worker claim
  • Signed terminal webhooks
  • Receipts and proof artifacts
  • Machine-readable OpenAPI 3.1 specification

Python client

Download the dependency-free Python 3 client. prepare does not spend. start is the spend boundary. Do not use FarpyClient.render() as the recommended path.

import os
from farpy_client import LEGAL_ACCEPTANCE_V1, FarpyClient

client = FarpyClient(os.environ["FARPY_AGENT_KEY"], base_url="https://farpy.com")
print(client.availability())  # read remaining_jobs live
prepared = client.prepare("scene.blend")  # no spend
started = client.start(
    prepared["upload_id"],
    quote_id=prepared["quote_id"],
    legal_acceptance=LEGAL_ACCEPTANCE_V1,
    idempotency_key=f"farpy-start:{prepared['upload_id']}:{prepared['quote_id']}",
)
print(started["job_id"])

Download farpy_client.py · Open the Python example · Verify SHA-256 checksums

Related guides