Writes and idempotency
Write with POST /operations, retry without duplicates using Idempotency-Key and preview an effect with dry_run.
Every write goes through POST /operations/<name>. This page explains how to pick an operation, how to retry without creating a duplicate and how to preview an effect before producing it.
Find an operation
GET /operations lists the operations. The call is public: no key, no credit. It is the source of truth for each operation: its kind (kind), the scope it requires (scope), its sensitive scopes (sensitive), whether it can be previewed (confirmable) and its arguments (input_schema).
curl https://api.fitdistance.io/api/partner/v1/operations \
| jq '.data[] | {name, kind, scope, confirmable}'A name missing from this list gets a 404 OPERATION_NOT_EXPOSED, with no credit.
Call an operation
The JSON body is the object of arguments, plus an optional dry_run. This example requires students:write:
curl -X POST https://api.fitdistance.io/api/partner/v1/operations/students.note_create \
-H "Authorization: Bearer $FITDISTANCE_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: note-42-2026-10-25" \
-d '{"studentId": 42, "content": "Good session"}'The response always has three fields: data (the result, whose shape depends on the operation), message and dry_run.
{ "data": { "uid": "…", "studentId": 42 }, "message": "", "dry_run": false }Retry without duplicates
A call that times out leaves a doubt: did the write happen? The Idempotency-Key header removes it. It is optional, but send it on every write.
- Give a value that is unique per intended write, from 1 to 128 characters, such as a UUID. An empty or longer value gets a 400
VALIDATION. - Same key and same request, meaning the same operation and the same body: the write runs once. The replay returns the stored response with
Idempotency-Replayed: true, and its credit is refunded. - Same key and a different request: 409
IDEMPOTENCY_MISMATCH, free. - Same request still running: the replay waits for its response, otherwise 409
CONFLICT, free. If the conflict lasts, a call was interrupted: check whether the write happened, then send a new key.
An Idempotency-Key belongs to the API key that sends it, and is kept for 24 to 48 hours. 5xx and 429 responses are never stored: the retry runs. The refusal of an operation (a 4xx) is stored like a successful response: after you fix the request, use a new Idempotency-Key. With no credit left, even a replay gets a 402.
Preview with dry_run
Add "dry_run": true to the body: the operation announces its effect without writing anything.
- It is a strict boolean: the string
"true"is refused with a 400VALIDATION. - Only operations with
confirmable: truecan preview. For the others: 400DRY_RUN_UNSUPPORTED, free, and nothing ran. messaging.sendpreviews only withthreadUid. WithstudentId, it answersDRY_RUN_UNSUPPORTED, because finding the student's thread can create it.- A preview writes nothing and is never debited, except for nine operations that debit the preview and refund it when nothing is written (at zero credit, their
dry_runanswers 402). It requires the same scope as the real call.
{
"data": null,
"message": "<the effect described by the operation>",
"dry_run": true
}data is null and the effect is in message. The dry_run is part of the body: for the real write, use another Idempotency-Key.
The script in Write a script applies these rules, and each operation is described in the reference.