Skip to main content

Idempotency

The tools that create a resource accept an optional idempotency_key argument. It is sent to the endpoint as the X-Idempotency-Key header the REST API already honors.

{ "name": "dashboards_portal_users_create",
"arguments": {
"email": "joao@example.com",
"tenant_id": "cliente-1042",
"idempotency_key": "7c4a8d09-ca37-4f21-b1f0-3d2f2b8c9a11"
} }
FormatOpaque string, 1 to 255 characters. A UUID is the usual choice.
Retry with the same bodyReturns the original response without running the operation again.
Retry with a different bodyConflict.

Why an agent needs this more than your backend​

An MCP client that times out and retries, or a model that decides "I'm not sure that worked, I'll do it again", is exactly the pattern that creates the second role, the second invitation and the second user. Your backend has a retry loop you wrote; the agent has judgment.

Send a key whenever the operation creates something or sends an invitation. Reads and updates are naturally idempotent and need no key.

Tools that accept it​

FamilyTools
portal-usersdashboards_portal_users_create, dashboards_portal_users_invite
users, rolesdashboards_dashboard_users_create, dashboards_dashboard_users_upsert, dashboards_team_roles_create, dashboards_team_roles_upsert

:::caution Invitations dashboards_portal_users_invite sends an email to the portal user — an effect in the world that no _delete undoes. Send idempotency_key with it even when you think you won't retry. :::

Upsert is not idempotency​

*_upsert matches an existing record by id, then external_id, then by the resource's business key, and creates only when nothing matches. That solves "I don't know if it already exists", a different problem from "I don't know if my call arrived". The two combine well: an upsert with an idempotency_key is safe on both counts.