Idempotency
An idempotency key lets you retry a write without performing it twice. If a request times out after BloomText has already sent your message, the retry returns the original result instead of sending again.
Every POST request requires an Idempotency-Key header. Generate a new UUID for each logical write, and reuse it when you retry that write.
Idempotency-Key: 9f1c2b3a-7d4e-4f5a-8b6c-1d2e3f4a5b6cEvery retry must reuse its Idempotency-Key. A new key is a new message. If your integration sends a retry with a freshly generated key, BloomText treats it as a separate request and texts the patient again. That duplicate is a bug in the integration, and BloomText can’t detect it. Store the key with the job before the first attempt, and send the same key on every retry, including after timeouts, 429, and 5xx responses.
How it works
| You send | BloomText returns |
|---|---|
| A new key | Processes the request normally and remembers the result for 24 hours. |
| The same key, method, path, and body within 24 hours | The original response, without running the write again. It carries Idempotent-Replayed: true and the original request’s X-Request-Id, so it matches what you logged the first time. |
| The same key with a different method, path, or body | 409 Conflict with code: idempotency_key_reused. |
| The same key while the first request is still running | 409 Conflict with code: idempotency_request_in_progress. Retry shortly. |
PATCH requests set fields to the values you send, so repeating one is safe and they don’t take a key. DELETE requests are identified by their path, so they don’t take a key either. A second delete of the same resource returns 404, which you can treat as success.
Retry safely
JavaScript
async function sendMessage(patientId, body) {
const idempotencyKey = crypto.randomUUID() // one key per logical send
for (let attempt = 1; attempt <= 4; attempt++) {
try {
const response = await fetch('https://api.bloomtext.com/v1/patient-messages', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLOOMTEXT_API_KEY}`,
'Idempotency-Key': idempotencyKey,
'Content-Type': 'application/json',
},
body: JSON.stringify({ to: { patient_id: patientId }, body, channel: 'sms' }),
})
if (response.status < 500 && response.status !== 429) return await response.json()
} catch {
// Network error: safe to retry with the same key.
}
await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 250))
}
throw new Error('Message not sent after 4 attempts')
}Choose keys
- Generate the key once per logical action, before the first attempt, and keep it with the job. Derive it from something stable, like a UUIDv5 of the draft or appointment ID, so a double click or a rerun of the job can’t send twice.
- Reuse the key on every retry of that action, whatever the failure was: a timeout, a dropped connection,
429, or5xx. Never generate a new key to retry. A new key is a new message. - Use a new key for each different message. Reusing one returns
409 idempotency_key_reused. - Keys are scoped to the credential. The same key sent with two different API keys counts as two separate writes.