Skip to main content

Idempotency

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

{ "name": "billing_invoices_create",
"arguments": {
"billing_account_id": "…",
"total": 1250.00,
"idempotency_key": "7c4a8d09-ca37-4f21-b1f0-3d2f2b8c9a11"
} }
FormatOpaque string, 1 to 255 characters. A UUID is the usual choice.
Replay, same bodyReturns the original response, without running the operation again.
Replay, different bodyConflict.

Why an agent needs this more than your backend does​

An MCP client that times out and tries again, or a model that decides "I am not sure that worked, let me redo it", is exactly the pattern that creates the second invoice, the second charge and the second customer. Your backend has a retry loop you wrote; the agent has a judgement call.

Send a key whenever the operation charges, issues or creates. Reads and updates are naturally idempotent and take no key.

Tools that accept it​

FamilyTools
customersbilling_customers_create, billing_customers_upsert, billing_billing_accounts_create, billing_billing_accounts_upsert
portal-usersbilling_customer_users_create, billing_customer_users_invite
users, rolesbilling_dashboard_users_create, billing_dashboard_users_upsert, billing_team_roles_create, billing_team_roles_upsert
products, plans, servicesbilling_products_create, billing_products_upsert, billing_product_groups_create, billing_product_groups_upsert, billing_plans_create, billing_plans_upsert, billing_plan_groups_create, billing_plan_groups_upsert, billing_service_items_create, billing_service_items_upsert
subscriptions, plan-changesbilling_subscriptions_create, billing_subscriptions_upsert, billing_subscriptions_items_change, billing_plan_changes_execute, billing_plan_changes_rules_create
invoicesbilling_invoices_create, billing_invoices_upsert, billing_invoices_finalize, billing_invoices_pay, billing_invoices_installments_pay, billing_subscriptions_create_invoice
paymentsbilling_payments_create, billing_payments_retry, billing_payments_refund, billing_payment_methods_create, billing_payment_methods_upsert
credits, couponsbilling_credits_create, billing_credits_upsert, billing_coupons_create, billing_coupons_upsert
nfes, companiesbilling_nfes_create, billing_nfes_upsert, billing_nfes_issue, billing_companies_create, billing_companies_upsert
tax-rules, tax-periods, withholdingsbilling_tax_rules_create, billing_tax_rules_upsert, billing_tax_periods_process_month, billing_withholdings_create, billing_withholdings_upsert, billing_withholdings_convert_to_credit, billing_withholdings_convert_to_credits
proposals, templatesbilling_proposals_create, billing_proposals_upsert, billing_proposals_send, billing_proposals_accept, billing_proposal_templates_create, billing_proposal_templates_upsert
usagebilling_usage_report, billing_usage_snapshots_daily_create, billing_usage_snapshots_monthly_create

:::caution The ones that charge billing_invoices_pay, billing_payments_create, billing_payments_retry and billing_nfes_issue produce an effect in the world — a charge at the gateway, a tax invoice at the tax authority — that no _delete undoes. Send an idempotency_key on those even when you think you will not repeat. :::

Upsert is not idempotency​

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