Idempotencia
Las tools que crean un recurso o mueven dinero aceptan un argumento opcional idempotency_key. Se envía al endpoint como la cabecera X-Idempotency-Key que la API REST ya honra.
{ "name": "billing_invoices_create",
"arguments": {
"billing_account_id": "…",
"total": 1250.00,
"idempotency_key": "7c4a8d09-ca37-4f21-b1f0-3d2f2b8c9a11"
} }
| Formato | Cadena opaca, de 1 a 255 caracteres. Un UUID es la elección habitual. |
| Repetición con el mismo cuerpo | Devuelve la respuesta original, sin ejecutar la operación de nuevo. |
| Repetición con cuerpo distinto | Conflicto. |
Por qué un agente necesita esto más que tu backend
Un cliente MCP que agota el tiempo y reintenta, o un modelo que decide "no estoy seguro de que eso funcionó, lo rehago", es exactamente el patrón que crea la segunda factura, el segundo cobro y el segundo cliente. Tu backend tiene un bucle de reintento que escribiste tú; el agente tiene un juicio.
Envía una clave siempre que la operación cobre, emita o cree. Las lecturas y actualizaciones son naturalmente idempotentes y no piden clave.
Tools que la aceptan
| Familia | Tools |
|---|---|
customers | billing_customers_create, billing_customers_upsert, billing_billing_accounts_create, billing_billing_accounts_upsert |
portal-users | billing_customer_users_create, billing_customer_users_invite |
users, roles | billing_dashboard_users_create, billing_dashboard_users_upsert, billing_team_roles_create, billing_team_roles_upsert |
products, plans, services | billing_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-changes | billing_subscriptions_create, billing_subscriptions_upsert, billing_subscriptions_items_change, billing_plan_changes_execute, billing_plan_changes_rules_create |
invoices | billing_invoices_create, billing_invoices_upsert, billing_invoices_finalize, billing_invoices_pay, billing_invoices_installments_pay, billing_subscriptions_create_invoice |
payments | billing_payments_create, billing_payments_retry, billing_payments_refund, billing_payment_methods_create, billing_payment_methods_upsert |
credits, coupons | billing_credits_create, billing_credits_upsert, billing_coupons_create, billing_coupons_upsert |
nfes, companies | billing_nfes_create, billing_nfes_upsert, billing_nfes_issue, billing_companies_create, billing_companies_upsert |
tax-rules, tax-periods, withholdings | billing_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, templates | billing_proposals_create, billing_proposals_upsert, billing_proposals_send, billing_proposals_accept, billing_proposal_templates_create, billing_proposal_templates_upsert |
usage | billing_usage_report, billing_usage_snapshots_daily_create, billing_usage_snapshots_monthly_create |
:::caution Las que cobran
billing_invoices_pay, billing_payments_create, billing_payments_retry y billing_nfes_issue producen un efecto en el mundo — un cobro en la pasarela, una nota fiscal en la autoridad tributaria — que ningún _delete deshace. Envía idempotency_key en esas incluso cuando creas que no vas a repetir.
:::
Upsert no es idempotencia
*_upsert empareja un registro existente por id, luego external_id, luego por la clave de negocio del recurso, y crea solo si no encuentra ninguno. Eso resuelve "no sé si ya existe", que es un problema distinto de "no sé si mi llamada llegó". Las dos cosas se combinan bien: un upsert con idempotency_key es seguro en ambas dimensiones.