external_id and idempotency
Tabba's public API is designed so you never need to store a Tabba
UUID. Instead you stamp your own id — the ClickUp task id, the Monday
board id, the Jira ticket key — onto every resource via
external_id, and address it later by ext:your-id.
Creating a resource
Send external_id in the create body:
curl -X POST https://www.tabba.io/api/v1/customers \
-H "Authorization: Bearer $TABBA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"external_id": "clickup:cust_ACME",
"name": "ACME Corp",
"email": "billing@acme.example"
}'
Response echoes the external_id:
{ "object": "customer", "id": "…", "external_id": "clickup:cust_ACME", "name": "ACME Corp", … }
Upsert semantics
Repeating a create with the same external_id upserts. A retried
sync is safe by construction — no separate Idempotency-Key header, no
duplicate rows.
# Second call with the same external_id — updates the same row.
curl -X POST https://www.tabba.io/api/v1/customers \
-H "Authorization: Bearer $TABBA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"external_id": "clickup:cust_ACME",
"name": "ACME Corp (Denmark)",
"email": "billing@acme.example"
}'
This is the ONE idempotency primitive. Stripe's Idempotency-Key header
is not supported — external_id is the sole mechanism.
Addressing by ext:
Everywhere the API takes a resource ref (in URLs, in bodies), it accepts three forms:
ext:your-id— yourexternal_id.email:someone@example.com— users only.- A raw UUID — Tabba's internal id.
# Read your customer by ext id — no UUID round-trip needed.
curl https://www.tabba.io/api/v1/customers/ext:clickup:cust_ACME \
-H "Authorization: Bearer $TABBA_API_KEY"
# Patch it.
curl -X PATCH https://www.tabba.io/api/v1/customers/ext:clickup:cust_ACME \
-H "Authorization: Bearer $TABBA_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "email": "new-billing@acme.example" }'
Where external_id is required
Required on the money-moving creates:
POST /invoices— a one-time invoice.POST /recurring-series— a retainer.POST /credit-notes— a reversal.
Optional (but recommended) everywhere else:
- Customers, products, users (tag an existing user with
PATCH /users/{ref}to attachexternal_id). - Work registrations (label-only; the row's identity is still the
(line, user, entry-month)composite key).
external_id is NOT external_reference
Customers and products have a separate external_reference field
that passes through to Dinero — used for the accounting-side lookup.
external_id— Tabba's correlation id for YOUR world. Never sent to Dinero.external_reference— Dinero's identifier as shown in Dinero.
Keep them separate so a change to your ClickUp id doesn't accidentally overwrite the Dinero reference (and vice versa).
Uniqueness
external_id is unique within (integration, resource_type). Two
integrations can safely use the same string; two resource types on the
same integration can too (a customer and a product may share
clickup:demo without conflict).