Skip to content
UpVoiced Docs

API guides

Idempotency

A request whose response was lost may or may not have happened. Send an Idempotency-Key with a POST and you can repeat it safely: UpVoiced applies it once and answers every repeat with the first response.

curl -X POST https://api.upvoiced.com/v1/posts \
  -H "Authorization: Bearer $UPVOICED_API_KEY" \
  -H "X-Workspace: $UPVOICED_WORKSPACE_ID" \
  -H "Idempotency-Key: crm-import-2026-10-22-row-17" \
  -H "Content-Type: application/json" \
  -d '{"board_id": "0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d", "title": "Dark mode"}'

Rules

77 of the 88 POST operations accept the header; the reference marks them. GET, PUT and DELETE are idempotent by nature. PATCH is not: do not repeat a PATCH blindly after a timeout, read the resource first.

With the SDK

With retries on (the default), @upvoiced/sdk sends a generated key with every POST and reuses it on each retry. Pass your own key to make a POST safe across process restarts too:

import { UpVoiced } from '@upvoiced/sdk';

const apiKey = process.env['UPVOICED_API_KEY']; // uv_live_… (Settings → API keys)
const workspace = process.env['UPVOICED_WORKSPACE_ID']; // sent as X-Workspace
if (!apiKey || !workspace) throw new Error('Set UPVOICED_API_KEY and UPVOICED_WORKSPACE_ID.');
const upvoiced = new UpVoiced({ apiKey, workspace });

// A key derived from your own record makes the POST safe across restarts and re-runs: the API
// answers a repeat with the first response instead of creating a second post.
const { data, status, headers } = await upvoiced.request('post', '/v1/posts', {
  body: { board_id: '0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d', title: 'Dark mode' },
  idempotencyKey: 'crm-import-2026-10-22-row-17',
});
console.log(status, data.id, headers.get('idempotent-replayed')); // 'true' on a replay