> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dolfinai.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Quick Start - AP

> Upload and approve your first bill in four API calls

This guide walks through the end-to-end flow of processing a bill via the Dolfin API. You'll upload a bill document, optionally correct any fields the OCR scanner got wrong, submit the bill for review, and either approve or reject it.

## Prerequisites

* A Dolfin **API key**
* An **organisation** already provisioned (see [Client Integration](/guides/client-integration))
* A valid **session token** or API key for authentication
* A bill **PDF or image** file to upload

<Note>
  All requests below require the `x-dolfin-api-key` and `x-dolfin-organisation-id` headers. See [Authentication](/guides/authentication) for details.
</Note>

## Overview

<Steps>
  <Step title="Upload a bill">
    Upload the bill document. Dolfin queues OCR processing asynchronously.
  </Step>

  <Step title="Patch extracted fields (optional)">
    Fix any fields the OCR scanner got wrong while the bill is in `PendingReview`.
  </Step>

  <Step title="Submit for review">
    Move the bill from `PendingReview` into `NeedsApproval`.
  </Step>

  <Step title="Approve or reject">
    Approve the bill to proceed to payment scheduling, or reject it with a reason.
  </Step>
</Steps>

## Bill lifecycle

A bill moves through these states:

```
Created → OcrProcessing → PendingReview → NeedsApproval → Approved → Scheduled → Paid
                     ↘ OcrFailed                        ↘ Rejected
```

***

## Step 1: Upload a Bill

Upload the bill document as `multipart/form-data`. OCR runs asynchronously, so the response returns immediately with the new bill's `id` and initial state.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.dolfinai.co/bills \
    -H "x-dolfin-api-key: dol_live_abc123" \
    -H "x-dolfin-organisation-id: 9a658587-fe02-402e-b1ac-bfaf53274ef8" \
    -F "file=@./acme-invoice-2025-01.pdf"
  ```

  ```javascript Node.js theme={null}
  import fs from 'node:fs';

  const form = new FormData();
  form.append(
    'file',
    new Blob([fs.readFileSync('./acme-invoice-2025-01.pdf')], {
      type: 'application/pdf',
    }),
    'acme-invoice-2025-01.pdf',
  );

  const bill = await fetch('https://api.dolfinai.co/bills', {
    method: 'POST',
    headers: {
      'x-dolfin-api-key': 'dol_live_abc123',
      'x-dolfin-organisation-id': '9a658587-fe02-402e-b1ac-bfaf53274ef8',
    },
    body: form,
  }).then(r => r.json());
  ```
</CodeGroup>

**Response (`202 Accepted`):**

```json theme={null}
{
  "id": "b1234567-abcd-ef01-2345-6789abcdef01",
  "state": "OcrProcessing"
}
```

<Note>
  Save the bill `id` — you'll use it in every subsequent call. The bill starts in `OcrProcessing` and automatically moves to `PendingReview` once the scanner finishes. Poll `GET /bills/{id}` to check, or subscribe to the `/hubs/bills` SignalR hub for realtime updates.
</Note>

### Poll for OCR completion

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.dolfinai.co/bills/b1234567-abcd-ef01-2345-6789abcdef01 \
    -H "x-dolfin-api-key: dol_live_abc123" \
    -H "x-dolfin-organisation-id: 9a658587-fe02-402e-b1ac-bfaf53274ef8"
  ```

  ```javascript Node.js theme={null}
  const bill = await fetch(
    'https://api.dolfinai.co/bills/b1234567-abcd-ef01-2345-6789abcdef01',
    {
      headers: {
        'x-dolfin-api-key': 'dol_live_abc123',
        'x-dolfin-organisation-id': '9a658587-fe02-402e-b1ac-bfaf53274ef8',
      },
    },
  ).then(r => r.json());
  ```
</CodeGroup>

Once the state is `PendingReview`, the OCR results are populated on the bill:

```json theme={null}
{
  "id": "b1234567-abcd-ef01-2345-6789abcdef01",
  "state": "PendingReview",
  "supplierName": "Acme Supplies Ltd",
  "supplierEmail": "billing@acme.co.uk",
  "invoiceNumber": "ACM-2025-0142",
  "invoiceDate": "2025-01-15T00:00:00Z",
  "dueDate": "2025-02-14T00:00:00Z",
  "currency": "GBP",
  "subTotal": "480.00",
  "taxAmount": "96.00",
  "totalAmount": "576.00",
  "ocrConfidence": 0.94,
  "lineItems": [
    {
      "id": "li-0001",
      "description": "Bulk stationery order",
      "quantity": "10",
      "unitPrice": "48.00",
      "taxRate": "20",
      "taxAmount": "96.00",
      "subTotal": "480.00"
    }
  ]
}
```

<Note>
  If the state becomes `OcrFailed`, the document couldn't be parsed. Call `POST /bills/{id}/retry-ocr` to try again.
</Note>

***

## Step 2: Patch Extracted Fields (Optional)

OCR isn't perfect. If the scanner got any fields wrong — the supplier name, invoice number, amounts, line items, bank details — patch them while the bill is in `PendingReview`.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH https://api.dolfinai.co/bills/b1234567-abcd-ef01-2345-6789abcdef01 \
    -H "x-dolfin-api-key: dol_live_abc123" \
    -H "x-dolfin-organisation-id: 9a658587-fe02-402e-b1ac-bfaf53274ef8" \
    -H "Content-Type: application/json" \
    -d '{
      "invoiceNumber": "ACM-2025-00142",
      "totalAmount": "576.00",
      "accountHolderName": "Acme Supplies Ltd",
      "accountNumber": "12345678",
      "sortCode": "20-00-00",
      "bankCountry": "GB",
      "bankCurrency": "GBP"
    }'
  ```

  ```javascript Node.js theme={null}
  const patched = await fetch(
    'https://api.dolfinai.co/bills/b1234567-abcd-ef01-2345-6789abcdef01',
    {
      method: 'PATCH',
      headers: {
        'x-dolfin-api-key': 'dol_live_abc123',
        'x-dolfin-organisation-id': '9a658587-fe02-402e-b1ac-bfaf53274ef8',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        invoiceNumber: 'ACM-2025-00142',
        totalAmount: '576.00',
        accountHolderName: 'Acme Supplies Ltd',
        accountNumber: '12345678',
        sortCode: '20-00-00',
        bankCountry: 'GB',
        bankCurrency: 'GBP',
      }),
    },
  ).then(r => r.json());
  ```
</CodeGroup>

Only the fields you include are updated — everything else stays as OCR extracted it. You can only patch bills in `PendingReview`; attempts against any other state return `409 Conflict`.

<Note>
  To link the bill to an existing supplier record, set `supplierId` to the supplier's UUID. Otherwise the free-text `supplierName`, `supplierEmail`, etc. are kept as-is and used for supplier resolution when you submit for review (see Step 3).
</Note>

***

## Step 3: Submit for Review

Once the extracted data is correct, submit the bill for approval. This transitions the state from `PendingReview` to `NeedsApproval`.

### Supplier resolution

When you submit a bill for review, Dolfin resolves the bill's supplier in this order:

1. If you set `supplierId` explicitly (via patch), that supplier is used.
2. Otherwise, Dolfin looks for an existing supplier on the organisation whose **tax ID** or **email** matches the OCR-extracted value and links to it.
3. If no match is found, a **new supplier is created** from the OCR data. This requires at minimum `supplierName` and `supplierEmail` — fill these via `PATCH /bills/{id}` in Step 2 if OCR didn't capture them. Any other extracted details (phone, address, website, tax ID, bank details) are copied onto the new supplier too.

If the bill has no linked supplier and not enough data to create one, submit-review returns `400 Bad Request`.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.dolfinai.co/bills/b1234567-abcd-ef01-2345-6789abcdef01/submit-review \
    -H "x-dolfin-api-key: dol_live_abc123" \
    -H "x-dolfin-organisation-id: 9a658587-fe02-402e-b1ac-bfaf53274ef8"
  ```

  ```javascript Node.js theme={null}
  const submitted = await fetch(
    'https://api.dolfinai.co/bills/b1234567-abcd-ef01-2345-6789abcdef01/submit-review',
    {
      method: 'POST',
      headers: {
        'x-dolfin-api-key': 'dol_live_abc123',
        'x-dolfin-organisation-id': '9a658587-fe02-402e-b1ac-bfaf53274ef8',
      },
    },
  ).then(r => r.json());
  ```
</CodeGroup>

**Response:**

```json theme={null}
{
  "id": "b1234567-abcd-ef01-2345-6789abcdef01",
  "state": "NeedsApproval",
  "invoiceNumber": "ACM-2025-00142",
  "totalAmount": "576.00",
  "currency": "GBP"
}
```

***

## Step 4: Approve or Reject

With the bill in `NeedsApproval`, an approver takes the final call.

### Approve

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.dolfinai.co/bills/b1234567-abcd-ef01-2345-6789abcdef01/approve \
    -H "x-dolfin-api-key: dol_live_abc123" \
    -H "x-dolfin-organisation-id: 9a658587-fe02-402e-b1ac-bfaf53274ef8"
  ```

  ```javascript Node.js theme={null}
  const approved = await fetch(
    'https://api.dolfinai.co/bills/b1234567-abcd-ef01-2345-6789abcdef01/approve',
    {
      method: 'POST',
      headers: {
        'x-dolfin-api-key': 'dol_live_abc123',
        'x-dolfin-organisation-id': '9a658587-fe02-402e-b1ac-bfaf53274ef8',
      },
    },
  ).then(r => r.json());
  ```
</CodeGroup>

**Response:**

```json theme={null}
{
  "id": "b1234567-abcd-ef01-2345-6789abcdef01",
  "state": "Approved",
  "approvedBy": "u5678901-abcd-ef01-2345-6789abcdef01",
  "approvedAt": "2025-01-20T09:10:00Z"
}
```

An approved bill is ready for payment scheduling via `POST /bills/{id}/schedule-payment`.

### Reject

If something's off, reject the bill with an optional `reason`.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.dolfinai.co/bills/b1234567-abcd-ef01-2345-6789abcdef01/reject \
    -H "x-dolfin-api-key: dol_live_abc123" \
    -H "x-dolfin-organisation-id: 9a658587-fe02-402e-b1ac-bfaf53274ef8" \
    -H "Content-Type: application/json" \
    -d '{
      "reason": "Duplicate of ACM-2025-00141"
    }'
  ```

  ```javascript Node.js theme={null}
  const rejected = await fetch(
    'https://api.dolfinai.co/bills/b1234567-abcd-ef01-2345-6789abcdef01/reject',
    {
      method: 'POST',
      headers: {
        'x-dolfin-api-key': 'dol_live_abc123',
        'x-dolfin-organisation-id': '9a658587-fe02-402e-b1ac-bfaf53274ef8',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        reason: 'Duplicate of ACM-2025-00141',
      }),
    },
  ).then(r => r.json());
  ```
</CodeGroup>

**Response:**

```json theme={null}
{
  "id": "b1234567-abcd-ef01-2345-6789abcdef01",
  "state": "Rejected",
  "rejectedBy": "u5678901-abcd-ef01-2345-6789abcdef01",
  "rejectedAt": "2025-01-20T09:10:00Z",
  "rejectionReason": "Duplicate of ACM-2025-00141"
}
```

<Note>
  A rejected bill can be re-opened for editing with `POST /bills/{id}/reopen`, which returns it to `PendingReview`.
</Note>

***

## Summary

| Step | Endpoint                         | What it does                                       |
| ---- | -------------------------------- | -------------------------------------------------- |
| 1    | `POST /bills`                    | Upload a bill document; OCR runs asynchronously    |
| 2    | `PATCH /bills/{id}`              | Correct any fields OCR got wrong (optional)        |
| 3    | `POST /bills/{id}/submit-review` | Transition from `PendingReview` to `NeedsApproval` |
| 4a   | `POST /bills/{id}/approve`       | Approve the bill for payment                       |
| 4b   | `POST /bills/{id}/reject`        | Reject with an optional reason                     |

## Next Steps

<CardGroup cols={2}>
  <Card title="API Reference" icon="code" href="/api-reference/introduction">
    Explore the full Bills API, including payment scheduling and file URLs.
  </Card>

  <Card title="Client Integration" icon="rocket" href="/guides/client-integration">
    Set up organisation provisioning and user authentication.
  </Card>
</CardGroup>
