Webhooks & Async

TL;DR

POST /optimize/async returns immediately with a job_id; poll GET /jobs/{job_id} until status is completed, failed, or cancelled, then read .result. Webhooks let the server notify you instead of polling, but treat delivery as best-effort.

Why go async

/optimize/sync blocks the HTTP connection until the solver finishes or options.time_limit_seconds elapses. For large problems, long time limits, or batch processing where you don't want to hold a connection open, submit to /optimize/async instead.

Submit, poll, retrieve

  1. Submit. POST /api/v1/optimize/async with the same OptimizationRequest body you'd send to /optimize/sync. It returns 202 Accepted with a job_id right away — the solve runs in the background.
  2. Poll. GET /api/v1/jobs/{job_id} repeatedly (with a sensible interval, e.g. every 2–5 seconds) until status is a terminal value: completed, failed, or cancelled.
  3. Retrieve. Once status is completed, the job's result field holds the full OptimizationResponse — the same shape you'd get from /optimize/sync.

curl example

# 1. Submit
JOB_ID=$(curl -s -X POST https://api.fieldgenius.be/api/v1/optimize/async \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-adapter-key" \
  -H "X-Client-Key: your-client-key" \
  -d @request.json | jq -r '.job_id')

# 2. Poll
while true; do
  STATUS=$(curl -s https://api.fieldgenius.be/api/v1/jobs/$JOB_ID \
    -H "X-API-Key: your-adapter-key" \
    -H "X-Client-Key: your-client-key" | jq -r '.status')
  echo "status: $STATUS"
  if [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ] || [ "$STATUS" = "cancelled" ]; then
    break
  fi
  sleep 3
done

# 3. Retrieve
curl -s https://api.fieldgenius.be/api/v1/jobs/$JOB_ID \
  -H "X-API-Key: your-adapter-key" \
  -H "X-Client-Key: your-client-key" | jq '.result'

Python example (httpx)

import time
import httpx

BASE = "https://api.fieldgenius.be/api/v1"
HEADERS = {
    "X-API-Key": "your-adapter-key",
    "X-Client-Key": "your-client-key",
}

with httpx.Client(timeout=30, headers=HEADERS) as client:
    submit = client.post(f"{BASE}/optimize/async", json=request_payload)
    submit.raise_for_status()
    job_id = submit.json()["job_id"]

    while True:
        job = client.get(f"{BASE}/jobs/{job_id}").json()
        if job["status"] in ("completed", "failed", "cancelled"):
            break
        time.sleep(3)

    if job["status"] == "completed":
        result = job["result"]
        print(result["status"], result["score"]["total"])
    else:
        print("job did not complete:", job["status"])

You can cancel a still-running job with POST /api/v1/jobs/{job_id}/cancel.

Webhooks (not yet available)

Webhooks are accepted but not yet delivered

The request body accepts a webhooks section (shown below) and the API will not reject a request that includes one. However, no notification is actually sent when a job completes — the field is currently stored and otherwise ignored. Do not rely on webhooks yet: use polling as described above, which is fully supported today. This page will be updated once webhook delivery ships.

The planned shape, for reference:

{
  "webhooks": [
    {
      "url": "https://your-service.example.com/fg-webhooks",
      "events": ["completed", "failed"],
      "headers": { "Authorization": "Bearer your-shared-secret" }
    }
  ]
}

events defaults to ["completed", "failed"] if omitted. headers is a plain dict intended to be merged into the outbound webhook request once delivery is implemented, commonly used to carry an auth token your endpoint would check.

Next steps

FieldGenius VRP API documentation. Generated from the engineering source of truth (.tex docs and app/ source).