Joring Docs
Developers

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-guide

Rules, 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/trails

201 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}/document

If-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}/publish

Publishing needs the trails:publish scope and refuses with 422 and an issues list when the trail is not ready.

Routes

Method and pathScope
GET /meanyWho the key acts as, its workspace and scopes
GET /authoring-guidereadRules, limits, tools, existing trails, document schema
GET /trailsreadList; status, q, limit (≤ 100), cursor
POST /trailswriteCreate a draft
GET /trails/{id}readTrail with document and publish_issues; honours If-None-Match
PATCH /trails/{id}writevisibility (all_teams or restricted), sort_order
DELETE /trails/{id}writeDelete the trail and its versions
GET /trails/{id}/documentreadThe stored draft; ETag is the revision
PUT /trails/{id}/documentwriteReplace the draft; If-Match required
POST /trails/{id}/publishpublishMake it live
POST /trails/{id}/unpublishpublishBack to draft
POST /trails/{id}/archive, .../unarchivepublishHide from or restore to the catalog
GET /trails/{id}/versions, GET .../versions/{v}readPublished snapshots
POST /trails/{id}/versions/{v}/restorewritePut a snapshot back in the draft
GET /trails/{id}/grants, POST .../grants, DELETE .../grants/{user_id}read / writeWho sees a restricted trail ({"email": ...}, must be a member)
POST /trails/{id}/surfacing-testwrite{"draft_text": ..., "document"?: ...}
POST /trails/{id}/run-exampleswriteRun every trigger example; stores the summary
POST /content-assetswriteMultipart 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.

StatusSlug
400bad-requestMalformed parameter or body
401unauthorizedMissing, wrong, revoked, or expired key
402upgrade-requiredThe plan does not include API access
403insufficient-scopeThe key lacks the scope named in required_scope
403feature-not-enabled, membership-revoked, forbiddenSee API keys
404not-foundNo such trail in this workspace
409conflict, idempotency-in-flightState refuses the action; or a retry arrived while the first request is still running
412revision-conflictIf-Match is stale; current_revision says what is current
422validation, unprocessable, idempotency-key-mismatchissues lists fields to fix; a rule refused it; the key was reused for a different request
428precondition-requiredPUT .../document without If-Match
429Rate 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.

On this page