Idempotency
Send an Idempotency-Key header with a POST so that a retry after a timeout or a dropped connection does not create a second questionnaire or start a second run. Use a new random value (a UUID is fine) for each logical operation, and the same value for its retries.
- The key is 1–255 visible ASCII characters.
- It is kept for 24 hours, per organization, caller and operation.
- Same key, same request: Tradecraft does not run it again. It answers with the stored response and
Idempotent-Replayed: true. - Same key, different request (another body, file or query):
409 IDEMPOTENCY_CONFLICT. - Same key while the first request is still running:
409 IDEMPOTENCY_CONFLICT; retry after a second. - Only successful responses are stored. If the request failed, the key is released and you can retry with it.
curl -s -H "Authorization: Bearer $TOKEN" -H "Idempotency-Key: $(uuidgen)" \ -F file=@questionnaire.xlsx -F customerName="Acme Corp" \ https://api.tradecrafthub.ai/v1/assessmentsimport { readFile } from "node:fs/promises"
const key = crypto.randomUUID() // keep it for the retries of this uploadconst form = new FormData()form.set("file", new Blob([await readFile("questionnaire.xlsx")]), "questionnaire.xlsx")form.set("customerName", "Acme Corp")
for (let attempt = 1; attempt <= 3; attempt++) { const res = await fetch("https://api.tradecrafthub.ai/v1/assessments", { method: "POST", headers: { Authorization: `Bearer ${token}`, "Idempotency-Key": key }, body: form, }) if (res.status === 201) { const { data } = (await res.json()) as { data: { id: string; status: string } } console.log(data.id, res.headers.get("Idempotent-Replayed") === "true" ? "(replayed)" : "") break } if (res.status !== 409 && res.status < 500) throw new Error(`${res.status}`) await new Promise((r) => setTimeout(r, 1000 * attempt))}Where it is required
Section titled “Where it is required”Without the header these operations answer 400 VALIDATION_ERROR:
POST /v1/assessments— Upload a questionnairePOST /v1/assessments/{id}/analyze— Analyze (step 1 of 4)POST /v1/assessments/{id}/process— Process (step 2 of 4)POST /v1/answers— Propose an answerPOST /v1/answers/{id}/edits— Propose an editPOST /v1/gaps/{clusterId}/answers— Answer a knowledge gapPOST /v1/documents— Add a document or a Q&APOST /v1/documents/{id}/versions— Upload a new version
It is optional, and works the same way, on:
POST /v1/assessments/{id}/questions/{questionId}/regenerate— Queue a new draft for one questionPOST /v1/assessments/{id}/review-complete— Send to Finalize (step 3 of 4)POST /v1/assessments/{id}/finalize— Finalize (step 4 of 4)POST /v1/ask— Ask a security question