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
- Inspect the uploaded .blend.
- Read
quote_id. - Read
amount. - Read
currency. - Read
expires_at. - Accept the quote, then start.
Upload your Blender file. See the exact price before start. Do not hard-code an amount.
FAST PATH
- Create an API key in Account Settings.
- Download farpy_client.py.
- 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.
farpy_prepare_upload- PUT local file bytes to the returned
upload_urlwith the required headers farpy_submit_renderfarpy_job_statusfarpy_downloadfarpy_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)
raiseDownload 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