Skip to content

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.
  • 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

Publichttps://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
items1 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
callerOptional 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, statequeued, running or done
created_at, started_at, ended_at, expires_atISO times. expires_at is 72 h after the end
max_creditsAs submitted
countsItems 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).

HTTPcodeWhen
400invalid_requestNot JSON, an unknown field, no items or more than 100
400invalid_itemAn item's op is not a bulk op, or its body fails its endpoint's rules. The message starts with items[i] (op):
401key_requiredNo secret key (a job belongs to a project)
404not_foundNo such job or item for this project
405method_not_allowedAny other method or path under /v1/jobs
409item_pendingThe item has not run yet
429too_many_jobsThe project already has 2 open jobs, or the queue holds 20
429monthly_credits_exhaustedThe job's max_credits, with the project's other open jobs, exceeds the credits left this month
503bulk_unavailable, bulk_fullBulk 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.