# FarmLoop Processing API — instructions for AI agents

You are integrating with the FarmLoop Processing API. This file
is the complete, authoritative spec. Follow it exactly; do not invent
endpoints, parameters, or behaviors that are not listed here.

Purpose: send one sliced 3D-print file to the FarmLoop server and receive the
FarmLoop-modified file back (detachment/auto-ejection sequences injected).

## Authentication

- Every request needs the header `X-API-Key: fl_...`.
- The key is issued to the user during onboarding. **Ask the user for
  their key; read it from an environment variable (e.g. `FARMLOOP_API_KEY`).
  Never hardcode it, commit it, print it, or log it.**
- The key authenticates as the user's FarmLoop account. 401 = bad/missing
  key; 403 = account not API-enabled or subscription inactive — both are
  account problems the user must resolve with FarmLoop support, not code bugs.

## Endpoints

Base URL: `https://api.3d-farmers.com`

### GET /api/v1/process
Returns a JSON self-description of the API. Use it as an auth smoke test:
a 200 JSON response means the key and account are good.

### POST /api/v1/process
`multipart/form-data` with exactly one file part and optional parameter fields.

File part — field name `file`:
- Accepted: `.gcode.3mf`, `.3mf` (must contain sliced gcode), `.gcode`
- Rejected: `.bgcode` (binary G-code), project files without gcode
- Max size: 200 MB (413 above)
- One file per request. There is NO multi-file/merge endpoint yet.

Parameter fields (all optional, sent as form fields, values as strings):

| Field | Allowed | Default |
|---|---|---|
| detachmentMode | `push`, `lift` | `push` |
| stage | `1`, `2` | `1` |
| cooldownType | `temperature`, `time` | `temperature` |
| cooldownTemperature | number 20–100 (°C) | `30` |
| cooldownTime | number 0–86400 (seconds) | `300` |
| heatedBedTemperature | number 0–120 (°C) | value from the slicer file |
| zOffset | number −5 to 5 (mm) | value from the slicer file |
| pushHeight | number 0–300 (mm) | `5` |
| useFanUpgrade | `true`, `false` | `false` |
| copies | integer 1–10 | `1` |

Out-of-range or malformed values → 400 with a message naming the field.

`copies` repeats the entire print inside the returned gcode with the
ejection sequence between loops (one unattended job prints N parts). The
expanded output is capped at 400 MB — the server rejects the request with
413 when `copies × file size` would exceed it; reduce copies in that case.
Do not implement copies by sending the same request N times unless the
user explicitly wants separate jobs.

## Success response (200)

Body: the modified file bytes (binary). Save them as-is.

Headers:
- `X-FarmLoop-Output-Name` — URL-encoded suggested filename. Decode it and
  save the file under this name.
- `X-FarmLoop-Printer` — detected printer model (e.g. `P1S`, `X1E`, `H2D`).
- `X-FarmLoop-Stage` — `1` or `2`.
- `X-FarmLoop-Copies` — number of copies baked into the returned file.
- `X-FarmLoop-Warning` — present ONLY when the input has compatibility
  issues. See Warnings below.

**Critical filename rule:** Stage 2 outputs are named with an `FL_S2_`
prefix. Never strip or rename away this prefix — FarmLoop hardware running
Digital Mode (Full MQTT) arms itself by detecting it in the print job name.
If your pipeline renames files, preserve the prefix.

## Warnings (X-FarmLoop-Warning)

Processing succeeded, but the input file is problematic. Always surface the
warning text to the user; do not silently ignore it. Causes:
- File sliced with OrcaSlicer (unsupported — user must re-slice with Bambu
  Studio v02.04+).
- File carries an outdated Bambu Studio printer profile (pre-v02.04 — user
  should update Bambu Studio, select the stock printer profile, re-slice).
- Filename indicates the file was already processed by FarmLoop
  (re-processing duplicates the injected sequences).

## Errors and required client behavior

| Status | Meaning | What your code must do |
|---|---|---|
| 400 | Invalid parameters / unsupported file type | Fix the request. Do not retry unchanged. |
| 401 | Bad or missing API key | Tell the user to check their key. Do not retry. |
| 403 | API access not enabled or no active subscription | Tell the user to contact FarmLoop support. Do not retry. |
| 413 | File over 200 MB, or expanded output over 400 MB | Do not retry unchanged. Reduce copies or file size. |
| 422 | Unreadable 3MF / no gcode inside / unknown printer | Do not retry. The file itself is the problem. |
| 429 | Rate limit: 10 requests per 5 minutes per key | Wait `Retry-After` seconds, then retry. Queue client-side if batching. |
| 503 | Server at capacity (max 2 concurrent jobs) | Wait `Retry-After` seconds (or ~10 s), retry with backoff. |

Error bodies are JSON: `{ "success": false, "message": "..." }`.

Rules: honor `Retry-After` exactly; retry only 429 and 503; never retry 4xx
unchanged; process files sequentially rather than in parallel (server allows
2 concurrent jobs total across all users).

## Reference implementations

curl:

```bash
curl -sS https://api.3d-farmers.com/api/v1/process \
  -H "X-API-Key: $FARMLOOP_API_KEY" \
  -F "file=@part.gcode.3mf" \
  -F "stage=2" \
  -F "detachmentMode=push" \
  -OJ
```

Python (requests):

```python
import os, requests
from urllib.parse import unquote

resp = requests.post(
    "https://api.3d-farmers.com/api/v1/process",
    headers={"X-API-Key": os.environ["FARMLOOP_API_KEY"]},
    files={"file": open("part.gcode.3mf", "rb")},
    data={"stage": "2"},
    timeout=300,
)
resp.raise_for_status()
if w := resp.headers.get("X-FarmLoop-Warning"):
    print("FarmLoop warning:", w)
name = unquote(resp.headers["X-FarmLoop-Output-Name"])
open(name, "wb").write(resp.content)
```

Node.js (fetch, Node 18+):

```js
const fs = require("node:fs");
const form = new FormData();
form.append("file", new Blob([fs.readFileSync("part.gcode.3mf")]), "part.gcode.3mf");
form.append("stage", "2");
const resp = await fetch("https://api.3d-farmers.com/api/v1/process", {
  method: "POST",
  headers: { "X-API-Key": process.env.FARMLOOP_API_KEY },
  body: form,
});
if (!resp.ok) throw new Error(`${resp.status}: ${await resp.text()}`);
const name = decodeURIComponent(resp.headers.get("x-farmloop-output-name"));
fs.writeFileSync(name, Buffer.from(await resp.arrayBuffer()));
```

## Human-readable documentation

Full manual for people:
https://help.3d-farmers.com/integrations/processing-api/

Support: https://help.3d-farmers.com/contact/ — mention "Processing API".
