Companies, customers, and invoices
Create legal sender companies, remember company-scoped customers, and prepare idempotent draft invoices through Billtyme's agent-first API.
Every invoice belongs to a legal sender company. An invoice starts as a persisted draft with exact totals and frozen issuer and customer snapshots, but no legal invoice number.
1. Select or create the sender
List the companies available to the authenticated WorkOS account. Use external_reference when the calling system already has a stable sender identifier.
curl "$BILLTYME_API_URL/v1/companies?external_reference=legal_entity_de" \
--header "Authorization: Bearer $BILLTYME_ACCESS_TOKEN"
Create the company when the filtered data array is empty:
curl "$BILLTYME_API_URL/v1/companies" \
--request POST \
--header "Authorization: Bearer $BILLTYME_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: company-legal-entity-de-v1" \
--data '{
"legal_name": "Example Consulting GmbH",
"external_reference": "legal_entity_de",
"identifiers": [
{ "country_code": "DE", "scheme": "vat", "value": "DE123456789" }
],
"address": {
"line_1": "Example Street 1",
"postal_code": "10115",
"city": "Berlin",
"country_code": "DE"
},
"default_currency": "EUR",
"default_language": "de",
"default_payment_terms_days": 14
}'
The returned company UUID is explicit in every customer and invoice path. The authenticated account remains implicit and cannot be selected by request input.
2. Find or create the customer
Customers are scoped to a sender company because recipient details can differ between legal entities. The same external reference may exist under two companies without mixing their records.
Filter the company’s customer collection. An absent customer produces an
empty data array rather than a 404.
curl "$BILLTYME_API_URL/v1/companies/$COMPANY_ID/customers?external_reference=crm_acme" \
--header "Authorization: Bearer $BILLTYME_ACCESS_TOKEN"Create the customer once when the filtered collection is empty.
curl "$BILLTYME_API_URL/v1/companies/$COMPANY_ID/customers" \
--request POST \
--header "Authorization: Bearer $BILLTYME_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: customer-acme-2026-08" \
--data '{
"name": "Acme GmbH",
"external_reference": "crm_acme",
"email": "billing@example.com",
"address": {
"line_1": "Customer Avenue 2",
"postal_code": "20095",
"city": "Hamburg",
"country_code": "DE"
}
}'3. Create the draft invoice
Amounts use minor currency units. For EUR, 15000 means EUR 150.00. Tax rates use basis points, so 1900 means 19%.
quantitydecimal string
Positive quantity with up to three decimal places.
decimal stringunit_amount_minorinteger
Unit price in minor currency units; 15000 means EUR 150.00.
integertax_rate_bpsinteger
Tax rate from 0 to 10000 basis points; 1900 means 19%.
integercurl "$BILLTYME_API_URL/v1/companies/$COMPANY_ID/invoices" \
--request POST \
--header "Authorization: Bearer $BILLTYME_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: invoice-acme-2026-08" \
--data '{
"customer_id": "01989f61-2a4b-7c8d-8e9f-000000000003",
"external_reference": "erp_invoice_123",
"issue_date": "2026-08-12",
"lines": [
{
"description": "Consulting",
"quantity": "8",
"unit_amount_minor": 15000,
"tax_rate_bps": 1900
}
]
}'
If omitted, currency, language, and due_date come from the company defaults. The response has status: "draft" and number: null.
4. Retrieve the invoice
List invoices with cursor pagination and optional customer_id, external_reference, or status filters:
curl "$BILLTYME_API_URL/v1/companies/$COMPANY_ID/invoices?status=draft" \
--header "Authorization: Bearer $BILLTYME_ACCESS_TOKEN"
Retrieve one full invoice, including lines and snapshots, with its UUID:
curl "$BILLTYME_API_URL/v1/companies/$COMPANY_ID/invoices/$INVOICE_ID" \
--header "Authorization: Bearer $BILLTYME_ACCESS_TOKEN"
Idempotency
Use one stable key for one logical write. Keys are scoped to the authenticated account, so accidental reuse across two companies is rejected rather than replaying a result from the wrong company. Completed results are retained for replay for 24 hours; after that window, the key may represent a new write.
Billtyme performs the write and stores the result for the idempotency key.
Billtyme returns the earlier result with Idempotency-Replayed: true.
Billtyme rejects the request with 409 Conflict.