Tabba.
FunktionerPriserKontakt
Log indKom i gang
Tabba.

Moderne økonomistyring for virksomheder.

Produkt

  • Funktioner
  • Priser
  • Integrationer
  • Ændringslog

Ressourcer

  • Blog
  • Hjælpecenter
  • Udviklere

Virksomhed

  • Karriere
  • Kontakt

Juridisk

  • Privatlivspolitik
  • Servicevilkår
  • Cookie-politik
  • Sikkerhed

Forbind

  • Twitter
  • GitHub
  • LinkedIn
  • Discord

© 2026 Tabba. Alle rettigheder forbeholdes.

PrivatlivspolitikServicevilkårCookie-politik
Back to developer docs

Guides

  • Getting started
  • Authentication
  • external_id and idempotency
  • Work registration
  • Issuance and auto-send
  • Webhooks
  • Pagination
  • Errors

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 — your external_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 attach external_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).