Bulk jobs v1: POST /v1/jobs
POST · GET https://api.trailsplits.com/v1/jobs
Secret key only
Up to 100 route analyses, elevation profiles, GPX cleans and simplifications, places along a route or pace plans in one call, answered in the background: the same answers, later. Each item is charged as its own call when it runs; the submission costs 1 credit. Results are kept 72 hours. Needs a secret key from the API console.
Contract
- Review: docs/proposals/r4-bulk-role-review-packet-2026-10-03.md.
Many lines in one call, answered in the background: route analysis, elevation profiles, GPX clean and simplify, places along a route, and pace plans. Each item is exactly the body its single endpoint takes, and its answer is exactly what that endpoint would answer.
What it is, and what it is not.
-
The same answers, later.
- Items run one at a time on a worker of their own, with the single endpoint's limits and deadline.
- The worker gives way to interactive traffic, to the weekly map build and to memory pressure, so a job may wait. Its items never run faster than they would one by one.
-
Charged per item, as it runs.
-
The submission costs 1 credit (
bulk_job). - Each item is charged as its own operation (weight × units) from its own receipt, once, when it ends.
- A refused, failed or never-started item is free.
-
The submission costs 1 credit (
- Kept for 72 hours after the job ends, then deleted with its answers. Nothing else is stored.
- A job belongs to its project. It needs a secret key. Another project's job answers 404, the same as an unknown id.
Where
| Public | https://api.trailsplits.com/v1/jobs, /v1/jobs/{id} and /v1/jobs/{id}/items/{n}. Credits: bulk_job (1) and bulk_job_status (0), api-gateway-v1 |
Submit: POST /v1/jobs
{ "items": [
{ "op": "route_analysis", "body": { "gpx": "<gpx …>" } },
{ "op": "elevation_profile", "body": { "geometry": { "type": "LineString", "coordinates": [[7.65, 45.97], …] }, "points": 500 } }
] }
| Field | |
|---|---|
items | 1 to 100 { op, body }. op is one of route_analysis, elevation_profile, gpx_clean, gpx_simplify, route_places, pace_plan. body is that endpoint's request, checked by its own rules at submission |
caller | Optional label |
Answer: 202, the job's status (below) with poll: "/v1/jobs/{id}" and max_credits. max_credits is the most the job can cost: each item's weight × its units, and for places along a route, 1 per started 25 km.
Poll: GET /v1/jobs/{id}
200, bulk-job-status/1:
| Field | |
|---|---|
id, state | queued, running or done |
created_at, started_at, ended_at, expires_at | ISO times. expires_at is 72 h after the end |
max_credits | As submitted |
counts | Items per state: queued, running, complete, refused, error, terminated |
items[] | { n, op, state, http_status, code }. code is the endpoint's error code for a refused or failed item |
One answer: GET /v1/jobs/{id}/items/{n}
The single endpoint's answer for item n, with its own HTTP status. 409 item_pending while it has not run.
Errors
JSON, { "error": { "code", "message" } } from the service. Refusals by the API gateway (401 key_required, 403 scope_denied, its own 429s for rate and monthly credits) use the gateway's flat shape, { "error": "<code>", "status", "message" } (api-gateway-v1).
| HTTP | code | When |
|---|---|---|
| 400 | invalid_request | Not JSON, an unknown field, no items or more than 100 |
| 400 | invalid_item | An item's op is not a bulk op, or its body fails its endpoint's rules. The message starts with items[i] (op): |
| 401 | key_required | No secret key (a job belongs to a project) |
| 404 | not_found | No such job or item for this project |
| 405 | method_not_allowed | Any other method or path under /v1/jobs |
| 409 | item_pending | The item has not run yet |
| 429 | too_many_jobs | The project already has 2 open jobs, or the queue holds 20 |
| 429 | monthly_credits_exhausted | The job's max_credits, with the project's other open jobs, exceeds the credits left this month |
| 503 | bulk_unavailable, bulk_full | Bulk jobs are not on, or their storage is full (2 GB) |
Versioning
New ops, fields and states may appear in bulk-job-status/1. A change of what an item's state means is /2.