1. FARPY
  2. Docs
  3. Quickstart

FARPY Public API V1

FARPY Render API quickstart

Check service health, inspect current capabilities and pricing, upload a supported render file, submit the render, then download its result, receipt and proof.

Recommended public flow

  1. Inspect the uploaded .blend.
  2. Read quote_id.
  3. Read amount.
  4. Read currency.
  5. Read expires_at.
  6. Accept the quote, then start.

Upload your Blender file. See the exact price before start. Do not hard-code an amount.

FAST PATH

  1. Create an API key in Account Settings.
  2. Download farpy_client.py.
  3. Run:
export FARPY_API_KEY="farpy_..."
python3 farpy_client.py render scene.blend

The client performs preflight → upload → submit → wait → download → receipt → proof.

Explicit API flow

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

1. Create an API key

Sign in, open Account Settings, and create an API key. The secret is shown once, so store it securely and never place it in browser code or public logs.

export FARPY_API_KEY="YOUR_FARPY_API_KEY"

2. Copy, paste, render, verify

Save this as a shell script and pass a Blender file. set -e and curl -f stop immediately if health or any later request fails. The upload fingerprint uses the frame range saved inside the .blend; Blender CLI must read that authoritative range before any upload occurs.

set -euo pipefail

BASE="https://api.farpy.com/v1"
FILE="\${1:-scene.blend}"

: "\${FARPY_API_KEY:?Export FARPY_API_KEY first}"

command -v curl >/dev/null
command -v jq >/dev/null
command -v sha256sum >/dev/null
command -v stat >/dev/null
command -v blender >/dev/null || {
  echo "ERROR: Blender CLI is required to read the frame range saved inside the .blend before upload." >&2
  exit 2
}
test -f "$FILE"

case "$FILE" in
  *.blend) ;;
  *) echo "ERROR: input must be a .blend file" >&2; exit 2 ;;
esac

AUTH="Authorization: Bearer $FARPY_API_KEY"
FILENAME="$(basename "$FILE")"
SCENE_METADATA="$(
  blender --disable-autoexec -b "$FILE" \
    --python-expr "import bpy,json; s=bpy.context.scene; print('FARPY_SCENE_METADATA='+json.dumps({'frame_start':int(s.frame_start),'frame_end':int(s.frame_end)}))" \
    2>/dev/null | sed -n 's/^FARPY_SCENE_METADATA=//p' | tail -n 1
)"
test -n "$SCENE_METADATA" || {
  echo "ERROR: could not read authoritative scene frame metadata from $FILE" >&2
  exit 2
}
FRAME_START="$(printf '%s' "$SCENE_METADATA" | jq -er '.frame_start | select(type == "number" and floor == . and . >= 1)')"
FRAME_END="$(printf '%s' "$SCENE_METADATA" | jq -er --argjson start "$FRAME_START" '.frame_end | select(type == "number" and floor == . and . >= $start)')"
FRAME_COUNT="$((FRAME_END - FRAME_START + 1))"
SIZE_BYTES="$(stat -c%s "$FILE")"
FILE_SHA256="$(sha256sum "$FILE" | awk '{print $1}')"
UPLOAD_KEY="upload-$FILE_SHA256"
FINGERPRINT="$(printf 'filename=%s\nrenderer=blender\nframe_start=%s\nframe_end=%s\nframe_count=%s\nsize_bytes=%s\nsha256=%s' \
  "$FILENAME" "$FRAME_START" "$FRAME_END" "$FRAME_COUNT" "$SIZE_BYTES" "$FILE_SHA256" \
  | sha256sum | awk '{print $1}')"

TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT

echo "1/11 HEALTH"
curl -fsS "$BASE/health" -o "$TMP/health.json"
jq . "$TMP/health.json"

echo
echo "2/11 CAPABILITIES"
curl -fsS "$BASE/capabilities" -H "$AUTH" -o "$TMP/capabilities.json"
jq . "$TMP/capabilities.json"

echo
echo "3/11 LIMITS"
curl -fsS "$BASE/limits" -H "$AUTH" -o "$TMP/limits.json"
jq . "$TMP/limits.json"

echo
echo "4/11 UPLOAD"
curl -fsS "$BASE/renders" \
  -H "$AUTH" \
  -H "Idempotency-Key: $UPLOAD_KEY" \
  -H "X-Farpy-Request-Fingerprint: $FINGERPRINT" \
  -H "X-Farpy-Filename: $FILENAME" \
  -H "X-Farpy-Renderer: blender" \
  -H "X-Farpy-Frame-Start: $FRAME_START" \
  -H "X-Farpy-Frame-End: $FRAME_END" \
  -H "X-Farpy-Frame-Count: $FRAME_COUNT" \
  -H "X-Farpy-Size-Bytes: $SIZE_BYTES" \
  -H "X-Farpy-Content-SHA256: $FILE_SHA256" \
  -F "file=@$FILE" \
  -o "$TMP/upload.json"

jq . "$TMP/upload.json"

JOB_ID="$(jq -er '
  .job_id //
  .render.job_id //
  .render.id //
  .id //
  empty
' "$TMP/upload.json")"

echo "JOB_ID=$JOB_ID"

echo
echo "5/11 PRICE"
curl -fsS "$BASE/pricing" \
  -H "$AUTH" \
  -o "$TMP/pricing.json"

jq . "$TMP/pricing.json"

echo
echo "6/11 PREFLIGHT"
jq -n \
  --arg filename "$FILENAME" \
  --argjson frame_start "$FRAME_START" \
  --argjson frame_end "$FRAME_END" \
  --argjson frame_count "$FRAME_COUNT" \
  '{
    filename: $filename,
    renderer: "blender",
    frame_start: $frame_start,
    frame_end: $frame_end,
    frame_count: $frame_count
  }' > "$TMP/preflight-request.json"

curl -fsS "$BASE/renders/preflight" \
  -H "Content-Type: application/json" \
  --data-binary @"$TMP/preflight-request.json" \
  -o "$TMP/preflight.json"

jq . "$TMP/preflight.json"

echo
echo "7/11 SUBMIT"
curl -fsS -X POST "$BASE/renders/$JOB_ID/submit" \
  -H "$AUTH" \
  -o "$TMP/submit.json"

jq . "$TMP/submit.json"

echo
echo "8/11 STATUS"
while :; do
  curl -fsS "$BASE/renders/$JOB_ID" \
    -H "$AUTH" \
    -o "$TMP/status.json"

  jq . "$TMP/status.json"

  STATE="$(jq -r '
    .status //
    .state //
    .render.status //
    .render.state //
    empty
  ' "$TMP/status.json" | tr "[:lower:]" "[:upper:]")"

  case "$STATE" in
    DONE|COMPLETED|COMPLETE|SUCCEEDED|SUCCESS)
      break
      ;;
    FAILED|ERROR|CANCELLED|CANCELED)
      echo "ERROR: terminal state $STATE" >&2
      exit 3
      ;;
  esac

  sleep 5
done

echo
echo "9/11 RECEIPT"
curl -fsS "$BASE/renders/$JOB_ID/receipt" \
  -H "$AUTH" \
  -o "$TMP/receipt-response.json"

jq . "$TMP/receipt-response.json"

RECEIPT_URL="$(jq -er '
  .receipt_url //
  .receipt.url //
  .render.receipt_url //
  empty
' "$TMP/receipt-response.json")"

curl -fsSL "$RECEIPT_URL" -o "$TMP/receipt.json"
jq . "$TMP/receipt.json"

echo
echo "10/11 DOWNLOAD"
curl -fsS "$BASE/renders/$JOB_ID/download" \
  -H "$AUTH" \
  -o "$TMP/download-response.json"

jq . "$TMP/download-response.json"

DOWNLOAD_URL="$(jq -er '
  .download_url //
  .output_url //
  .artifact_url //
  .download.url //
  .artifact.url //
  .render.download_url //
  .render.output_url //
  empty
' "$TMP/download-response.json")"

OUTPUT="$PWD/$JOB_ID.output"
curl -fsSL "$DOWNLOAD_URL" -o "$OUTPUT"

echo "OUTPUT=$OUTPUT"

echo
echo "11/11 SHA256 VERIFY"
EXPECTED_SHA256="$(jq -er '
  .output_sha256 //
  .artifact_sha256 //
  .sha256 //
  .output.sha256 //
  .artifact.sha256 //
  .artifacts[0].sha256 //
  empty
' "$TMP/receipt.json")"

ACTUAL_SHA256="$(sha256sum "$OUTPUT" | awk '{print $1}')"

echo "EXPECTED_SHA256=$EXPECTED_SHA256"
echo "ACTUAL_SHA256=$ACTUAL_SHA256"

test "$ACTUAL_SHA256" = "$EXPECTED_SHA256"

echo
echo "VERIFY_OK=true"
echo "JOB_ID=$JOB_ID"
echo "RECEIPT_URL=$RECEIPT_URL"
echo "DOWNLOAD_URL=$DOWNLOAD_URL"

MCP primary agent flow

Prefer MCP at https://api.farpy.com/mcp for agent-operated renders. Authenticate with FARPY_API_KEY in the agent MCP configuration. MCP is an HTTP endpoint.

  1. farpy_prepare_upload
  2. PUT local file bytes to the returned upload_url with the required headers
  3. farpy_submit_render
  4. farpy_job_status
  5. farpy_download
  6. farpy_receipt

farpy_inspect_blend is optional and is NOT required after the PUT upload. Discover live FARPY MCP tools from the server; do not hard-code a tool count. REST remains supported separately below at https://api.farpy.com/v1.

Discover current service facts first

Call the public health endpoint before sending an API key or uploading a supported render file. If it does not return HTTP 200, stop and retry later.

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

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

Production flow

health
capabilities
pricing
upload
submit
status
download
receipt
proof

API properties

  • Public health preflight before authenticated work
  • API-key authentication
  • Public price: locked quote with quote_id, amount, currency, and expiry
  • Frame ranges from 1 to 10,000 frames 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 and run the complete workflow with one high-level method.

import os
from farpy_client import FarpyClient, FarpyError

client = FarpyClient(os.environ["FARPY_API_KEY"])

capabilities = client.capabilities()
pricing = client.pricing()
limits = client.limits()

print(capabilities)
print(pricing)
print(limits)

try:
    result = client.render(
        "scene.blend",
        output_path="render.zip",
        poll_interval=2.0,
        overall_timeout=3600.0,
    )
    print(result["job_id"])
    print(result["receipt"])
    print(result["proof"])
except FarpyError as exc:
    print(exc.code)
    print(exc.retryable)
    print(exc.suggested_action)
    print(exc.request_id)
    raise

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

Related guides

API overview · Agent integration guide · Error and retry guide · Webhook delivery guide · Quickstart · OpenAPI · Pricing · Status · Proof