---
title: Companies, customers, and invoices
description: Create legal sender companies, remember company-scoped customers, and prepare idempotent draft invoices through Billtyme's agent-first API.
lastModified: 2026-08-12
search:
  tags: [companies, customers, invoices, idempotency]
sidebar:
  label: Invoices
---

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.

> **A draft is not an issued invoice**
>
> A draft has no legal invoice number, has not been approved, and must not be
> sent to the customer as an issued invoice.

## 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.

```bash
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:

```bash
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.

**Find**

Filter the company's customer collection. An absent customer produces an
    empty `data` array rather than a `404`.

```bash
curl "$BILLTYME_API_URL/v1/companies/$COMPANY_ID/customers?external_reference=crm_acme" \
  --header "Authorization: Bearer $BILLTYME_ACCESS_TOKEN"
```

**Create**

Create the customer once when the filtered collection is empty.

```bash
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%.

| Prop | Type | Default | Description |
| - | - | - | - |
| `quantity` | `decimal string` | - | Positive quantity with up to three decimal places. |
| `unit_amount_minor` | `integer` | - | Unit price in minor currency units; 15000 means EUR 150.00. |
| `tax_rate_bps` | `integer` | - | Tax rate from 0 to 10000 basis points; 1900 means 19%. |

```bash
curl "$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`.

> **Persisted review state**
>
> Billtyme snapshots both the issuer company and customer billing data into the
> draft. Later profile edits cannot silently rewrite the identity shown on an
> existing invoice.

## 4. Retrieve the invoice

List invoices with cursor pagination and optional `customer_id`, `external_reference`, or `status` filters:

```bash
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:

```bash
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.

**First submission**

Billtyme performs the write and stores the result for the idempotency key.

**Same key and request**

Billtyme returns the earlier result with `Idempotency-Replayed: true`.

**Same key, different request**

Billtyme rejects the request with `409 Conflict`.

## What comes next

> **Issuance remains separate**
>
> PDF preview, approval, numbering, issuance, voiding, and sending will be
> explicit commands. This prevents conversational ambiguity or a retry from
> accidentally issuing or emailing a legal invoice.
