# Create journal entry

`POST /v1/journal-entries`

Creates a draft journal entry for the authenticated company. The entry is created with status 'open' and does not affect balances until it is booked, in the Numina UI or with POST /v1/journal-entries/{id}/book. VAT calculations (if vat_code provided) happen automatically during booking.

An account, VAT code or contact that does not exist returns 400 `invalid_request`.

Send an `Idempotency-Key` header (for example a UUID generated per request) to retry safely after a timeout or network error. A retry with the same key and the same body returns the original response with the header `Idempotent-Replayed: true`. Keys are scoped to the company and kept for 24 hours after a successful request. A failed request (4xx/5xx) frees the key, so a retry with that key runs again. Reusing a key with a different body returns 422; retrying while the first request is still processing returns 409.

**Scopes:** any of `journal:write`, `write:draft`, `write:ledger`

## Request

```bash
curl -X POST https://api.numina.app/v1/journal-entries \
  -H "Authorization: Bearer numi_YOUR_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "date": "2024-01-15",
    "text": "Cash sale",
    "contact_id": "550e8400-e29b-41d4-a716-446655440000",
    "lines": [
      {
        "account_number": 1010,
        "amount": -1250,
        "vat_code": "U25"
      },
      {
        "account_number": 6480,
        "amount": 1250
      }
    ],
    "external_reference": "payroll-run-2026-01"
  }'
```

## Headers

- `Idempotency-Key` (string, optional): Unique key for this request (max 255 characters). Retrying with the same key and body returns the original response instead of doing the work again. (max 255 chars)

## Body (`application/json`)

- `date` (string, required): Transaction date in YYYY-MM-DD format (YYYY-MM-DD)
- `text` (string, required): Transaction description (max 500 chars)
- `contact_id` (string (uuid), optional): Optional contact (customer/supplier) ID to associate with this transaction
- `lines` (array of objects, required): Journal entry line items (minimum 2 lines required for balanced entry)
  - `account_number` (integer, required): Account number in chart of accounts
  - `amount` (number, required): Signed amount in DKK: debit is positive, credit is negative. The lines must sum to exactly zero. With a `vat_code`, the amount includes VAT and the VAT is split out when the entry is booked.
  - `vat_code` (string, optional): VAT code (e.g., 'U25' for 25% sales VAT). Stored for later calculation during booking.
- `external_reference` (string, optional): Your own id for this entry (max 255 characters), unique per company for your app. A second entry with the same reference returns 409 with the existing entry's `id`. Available to apps registered as a Numina integration. (max 255 chars)

## Response `201`

Journal entry created successfully

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "date": "2026-01-15",
  "text": "Invoice payment from customer",
  "status": "open",
  "contact_id": "550e8400-e29b-41d4-a716-446655440000",
  "created_at": "2026-01-15T10:30:00Z",
  "external_reference": "payroll-run-2026-01",
  "lines": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "account_number": 3000,
      "account_id": "550e8400-e29b-41d4-a716-446655440001",
      "account_name": "Revenue",
      "amount": -1000,
      "text": "Invoice payment",
      "vat_code": "U25",
      "currency_code": "DKK",
      "currency_rate": 1
    }
  ]
}
```

## Errors

- `400` Bad Request - Invalid input or validation failed
- `401` Unauthorized - Invalid or missing authentication
- `403` Forbidden - Missing scope, or the company is archived
- `409` Conflict - An entry with this `external_reference` already exists (`id` is that entry), or a request with the same Idempotency-Key is still being processed
- `422` Unprocessable Entity - The Idempotency-Key was already used with a different request
- `429` Too Many Requests - Rate limit exceeded
- `500` Internal Server Error

See https://developers.numina.app/errors.md for the error format.
