Trails REST API
The /v1 API behind the MCP tools, for agents you build and for scripts.
Base URL https://api.joring.ai/v1. Every request carries
Authorization: Bearer jor_api_... (API keys). The
full route table and every schema are in the OpenAPI document at
https://api.joring.ai/openapi.json.
Quick start
Read the guide
curl -H "Authorization: Bearer $JORING_API_KEY" \
https://api.joring.ai/v1/authoring-guideRules, field limits, the supported AI tools, the trails that already exist, and the JSON Schema for a document. Same content as the authoring guide.
Create a draft
curl -X POST -H "Authorization: Bearer $JORING_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"document": { ... }}' \
https://api.joring.ai/v1/trails201 with the trail, an ETag (its revision) and a Location. The body's
publish_issues lists what still blocks publishing.
Save changes
curl -X PUT -H "Authorization: Bearer $JORING_API_KEY" \
-H "Content-Type: application/json" \
-H 'If-Match: "1"' \
-d '{ ...the full document... }' \
https://api.joring.ai/v1/trails/{id}/documentIf-Match is required and names the revision you edited from. If someone
saved in between you get 412 with current_revision; reload and reapply.
Run the examples, then publish
curl -X POST -H "Authorization: Bearer $JORING_API_KEY" \
https://api.joring.ai/v1/trails/{id}/run-examples
curl -X POST -H "Authorization: Bearer $JORING_API_KEY" \
https://api.joring.ai/v1/trails/{id}/publishPublishing needs the trails:publish scope and refuses with 422 and an
issues list when the trail is not ready.
Routes
| Method and path | Scope | |
|---|---|---|
GET /me | any | Who the key acts as, its workspace and scopes |
GET /authoring-guide | read | Rules, limits, tools, existing trails, document schema |
GET /trails | read | List; status, q, limit (≤ 100), cursor |
POST /trails | write | Create a draft |
GET /trails/{id} | read | Trail with document and publish_issues; honours If-None-Match |
PATCH /trails/{id} | write | visibility (all_teams or restricted), sort_order |
DELETE /trails/{id} | write | Delete the trail and its versions |
GET /trails/{id}/document | read | The stored draft; ETag is the revision |
PUT /trails/{id}/document | write | Replace the draft; If-Match required |
POST /trails/{id}/publish | publish | Make it live |
POST /trails/{id}/unpublish | publish | Back to draft |
POST /trails/{id}/archive, .../unarchive | publish | Hide from or restore to the catalog |
GET /trails/{id}/versions, GET .../versions/{v} | read | Published snapshots |
POST /trails/{id}/versions/{v}/restore | write | Put a snapshot back in the draft |
GET /trails/{id}/grants, POST .../grants, DELETE .../grants/{user_id} | read / write | Who sees a restricted trail ({"email": ...}, must be a member) |
POST /trails/{id}/surfacing-test | write | {"draft_text": ..., "document"?: ...} |
POST /trails/{id}/run-examples | write | Run every trigger example; stores the summary |
POST /content-assets | write | Multipart file (PNG, JPEG, WebP, GIF ≤ 10 MB); returns Markdown for the setup guide |
Conventions
Errors are RFC 9457 application/problem+json. The type is
https://api.joring.ai/problems/{slug}; compare the slug.
| Status | Slug | |
|---|---|---|
| 400 | bad-request | Malformed parameter or body |
| 401 | unauthorized | Missing, wrong, revoked, or expired key |
| 402 | upgrade-required | The plan does not include API access |
| 403 | insufficient-scope | The key lacks the scope named in required_scope |
| 403 | feature-not-enabled, membership-revoked, forbidden | See API keys |
| 404 | not-found | No such trail in this workspace |
| 409 | conflict, idempotency-in-flight | State refuses the action; or a retry arrived while the first request is still running |
| 412 | revision-conflict | If-Match is stale; current_revision says what is current |
| 422 | validation, unprocessable, idempotency-key-mismatch | issues lists fields to fix; a rule refused it; the key was reused for a different request |
| 428 | precondition-required | PUT .../document without If-Match |
| 429 | Rate limited; Retry-After and RateLimit-* headers |
Concurrency. ETag on trail responses is the revision, quoted ("7").
PUT .../document requires If-Match with that value, or * to overwrite.
Idempotency. Send Idempotency-Key (any string up to 255 characters,
unique per request) on POST, PUT, PATCH, and DELETE. A retry with the same key
and body replays the original response with Idempotent-Replayed: true for 24
hours.
Pagination. GET /trails returns items and next_cursor; pass the
cursor back to get the next page. Cursors are opaque.
Rate limits are per key, each a burst bucket that refills one request at a fixed interval: 120 in a burst then one refilled per second for most routes; 10 then one every three seconds for routes that run a model (surfacing tests, run-examples, publish, document saves, restores); 20 then one every two seconds for uploads.
Request IDs. Every response carries Request-Id. Quote it when you
contact support.