Tabba.
IntegrationerSådan virker TabbaFeatures

© 2026 Tabba. Alle rettigheder forbeholdes.

Udviklere
Udviklere
Udviklere
Getting startedQuickstartAuthenticationAccounting platformsexternal_id and idempotencyWork registrationIssuance and auto-sendWebhooksPaginationErrors

Andre spor

AI clientsAPI reference

Quickstart

From a key to recognised revenue, in curl, Node and Python.

This page takes a key from its first call to recognised revenue: prove the key works, create a customer and a product, draft an invoice, register work on it and read back what it earned. Everything runs against a real company, since there is no sandbox. Use a throwaway customer and product, and delete them when you are done.

1. Mint a key

An admin or a bookkeeper of the company mints a key in the company's settings, under Developers (/workspace/settings/company/developers). Select the company you will test against in the top bar first, and choose the scope read_write. The raw tba_… key is shown once: put it in an environment variable before you close the dialog.

export TABBA_API_KEY="tba_…"

2. Read the configuration

GET /config confirms that the key is live and which company it is bound to. It also answers the company's base currency, which the invoice in step 7 is written in.

curl https://www.tabba.io/api/v1/config \
  -H "Authorization: Bearer $TABBA_API_KEY"

A 401 means the key is missing (UNAUTHORIZED), or unknown or revoked (INVALID_API_KEY). A 403 with SCOPE_INSUFFICIENT means a read key on a write endpoint. See Errors.

3. Write the client once

The rest of this page is one call per step, so wrap the boilerplate now. On a failure, read the code and the request_id: code is what you branch on, and request_id is what you quote when you ask about a call.

# A shell function is enough: base URL, key, JSON.
tabba() {
  local method="$1" path="$2"
  shift 2
  curl -sS -X "$method" "https://www.tabba.io/api/v1$path" \
    -H "Authorization: Bearer $TABBA_API_KEY" \
    -H "Content-Type: application/json" "$@"
}

4. Create a customer

Stamp your own key on it. external_id is how you name the customer from here on, and repeating the call with the same external_id updates that customer instead of making a second one.

name, country_key, is_person and send_method are required, and send_method: "email" requires an email to send to. The customer is written to the accounting system first, so it is a real customer there.

tabba POST /customers -d '{
  "external_id": "quickstart:test-corp",
  "name": "Test Corp",
  "country_key": "DK",
  "is_person": false,
  "send_method": "email",
  "email": "billing@test.example"
}'

The answer is 201 for a new customer and 200 when the external_id was taken and the customer was updated.

If the create is refused with 422

One of the two accounting systems files every customer in a VAT zone and a customer group. There the create is refused with VALIDATION_FAILED, and field_errors names vat_zone_ref and customer_group_ref. Read a customer the company already has with GET /customers, and send the same two values. See Accounting platforms.

Read the customer back by your own key at any time. ext: in the path names it:

tabba GET /customers/ext:quickstart:test-corp

5. Create a product

A product states the revenue account a line that bills it posts to. account_number is an account of the company's chart, and unit is one the accounting system knows, so take all three of account_number, account_ref and unit from a product the company already has:

tabba GET /products

Then create yours with those values:

tabba POST /products -d '{
  "external_id": "quickstart:consulting",
  "product_number": "QS-001",
  "name": "Consulting",
  "account_number": 1000,
  "account_ref": "1000",
  "unit": "hours",
  "default_price": 1200
}'

6. Find who does the work

Every invoice line names at least one person, because revenue is recognised per person, per line. People are invited in the workspace and never created through the API. Read the people who can work in the key's company and find yourself:

tabba GET /users
# { "object": "list", "data": [ { "object": "user", "id": "…", "email": "…", … } ], … }

You do not need the person's id for what follows: a person is named by email:<address> wherever a request names one.

7. Draft an invoice

One hourly line, with you on it at 1,200 per hour. The invoice is a draft: nothing is asked of the accounting system and no customer hears from you until it is issued.

A request names a record by reference

A field named for a record (customer, product, user, invoice, series) takes a reference: the record's id, or ext: and your own external_id. A person is also named by email: and their address. A field that ends in _id states an id, and is what a response answers.

Write the invoice in the base_currency step 2 answered, and send auto_send: false. A new invoice has auto_send on unless the request says otherwise, and the daily run issues such an invoice by itself.

tabba POST /invoices -d '{
  "external_id": "quickstart:inv-001",
  "customer": "ext:quickstart:test-corp",
  "currency": "DKK",
  "title": "Quickstart",
  "auto_send": false,
  "lines": [{
    "product": "ext:quickstart:consulting",
    "recognition_method": "hourly",
    "description_override": "Consulting, July",
    "assignees": [{
      "user": "email:you@your-company.example",
      "hourly_rate_override": 1200
    }]
  }]
}'
# Keep the id of the line from the answer: .lines[0].id
export LINE_ID="…"

Two kinds of line

An hourly line bills registered hours at each person's rate: every person on it states hourly_rate_override, and the line states no agreed_value. A value line bills a fixed agreed_value and is recognised by completion: its people share it by distribution_percentage, a fraction from 0 to 1 that sums to 1 over the line, or evenly where nobody states one.

8. Register the work

This is the call that moves revenue. hours is the person's total on the line, not an increment: Tabba registers the difference against what is registered already, so sending the same total again registers nothing.

tabba PUT /work-registrations -d '{
  "invoice": "ext:quickstart:inv-001",
  "line": "'"$LINE_ID"'",
  "user": "email:you@your-company.example",
  "hours": 12
}'

The answer states the person's total after the request (hours) and what the request registered (hours_delta).

9. Read the revenue back

Twelve hours at 1,200 is 14,400 earned. The invoice has not been issued, so nothing is billed, and all of it is work in progress.

tabba GET /invoices/ext:quickstart:inv-001/recognition

earned is what the registered work has earned, and billed is what issued invoices have billed, net of booked credit notes. The difference is deferred (billed ahead of the work) or wip (earned ahead of the invoice). The figures are in the company's base currency and are the ones the month-end accrual posts from. Issuing the invoice moves the amount from wip to billed: see Issuance and auto-send.

Clean up after yourself

The customer and the product you made are real records in the accounting system. Delete the invoice first, then the customer and the product: a customer or a product that an invoice still names is refused with DELETE_BLOCKED_BY_REFERENCES. A draft invoice is deleted with its lines and the work registered on them. An issued invoice is never deleted.

tabba DELETE /invoices/ext:quickstart:inv-001
tabba DELETE /customers/ext:quickstart:test-corp
tabba DELETE /products/ext:quickstart:consulting

Where to go next

  • Authentication: scope, the company a key is bound to, and rotation.
  • external_id and idempotency: the upsert contract you just relied on, in full.
  • Work registration: totals, the entry month, and registering on an occurrence of a recurring series.
  • Webhooks: how you hear of changes your integration did not make.
  • Connect an AI client instead: the same module over MCP, where every write is a plan first and a separate commit applies it.

Getting started

Base URL, your first call, what a key reaches, and the no-sandbox note.

Authentication

Key format, scope, the company a key is bound to, rotation.

På denne side

1. Mint a key2. Read the configuration3. Write the client once4. Create a customer5. Create a product6. Find who does the work7. Draft an invoice8. Register the work9. Read the revenue backWhere to go next

Denne side

Vis som MarkdownÅbn i Claude