# Create a Quote

`POST /v1/quotes`

Creates a draft Quote. Supply `patient_id` for a Patient Quote, or omit it and supply `alternative_payor_id` to quote another organisation. Supply `booking_ids` to generate line items from those Bookings, or supply `line_items` to quote custom charges. Bookings require a Patient. Sending, accepting, and converting remain in the Carebit staff and Patient Portal interfaces.

**Required API scopes:** `quotes.create`

## Parameters

- `Idempotency-Key` (header, `string`) (required) - Client-generated idempotency key. Required for every POST/PATCH write. Replay of the same key with the same body returns the stored response with an `Idempotency-Replayed: true` header. Same key + different body returns `422 idempotency_key_reused`. A duplicate that arrives while the first request is still in flight returns `409 idempotency_conflict` with `Retry-After: 1`.

## Request body (`application/json`)

- `object`
  - `alternative_payor_id` (`string | null`) - format: `uuid`; Required when `payor_type` is `alternative_payor`, and when `patient_id` is omitted. For Patient Quotes it must be available through a Patient Payor. For Quotes without a Patient it identifies the organisation being quoted.
  - `booking_ids` (`array`) - Optional unique Booking IDs in the Organization that belong to the Quote's Patient. Omit this field when the Quote has no Patient.
    - `items` (`string`) - format: `uuid`
  - `clinician_id` (`string | null`) - format: `uuid`; The optional Clinician attributed to the Quote. When omitted, Carebit infers it only if every supplied Booking shares one Clinician.
  - `expires_on` (`string`) - format: `date`; The date the Quote remains valid through. Defaults to the Organization's default quote validity, which is 28 days unless changed.
  - `line_items` (`array`) - Optional custom charges. When omitted and `booking_ids` is supplied, Carebit generates line items from those Bookings. Amounts are minor currency units.
    - `items` (`object`)
      - `booking_id` (`string | null`) - format: `uuid`; The optional Booking associated with this line item. The Booking must be included in `booking_ids`.
      - `discount_amount` (`integer`) - The discount in minor currency units.
      - `external_provider_name` (`string | null`) - The provider that collects an external fee.
      - `is_billed_by_another_provider` (`boolean`) - Whether another provider bills this line instead of the practice that issued the Quote.
      - `quantity` (`number`) - The quantity quoted. Defaults to 1.
      - `tax_treatment` (`string | null`) - enum: `exempt`, `no_vat`, `standard`, `zero_rated`; The tax treatment for the line item.
      - `title` (`string`) - The line item description.
      - `unit_price` (`integer`) - The unit price in minor currency units, excluding tax.
  - `notes` (`string | null`) - HTML notes shown on the Quote.
  - `patient_id` (`string | null`) - format: `uuid`; The Patient with an active connection to the Organization. Omit this field to quote another organisation, and supply `alternative_payor_id` instead.
  - `payor_type` (`string`) - enum: `patient`, `alternative_payor`; The party the Quote is issued to. Defaults from linked Bookings, to `patient` for a standalone Patient Quote, or to `alternative_payor` when `patient_id` is omitted.
  - `quoted_on` (`string`) - format: `date`; The Quote date. Defaults to today.
  - `terms` (`string | null`) - HTML terms. When omitted, Carebit copies the Clinician or Organization default.
  - `title` (`string | null`) - The Quote title.

### Example

```json
{
  "booking_ids": [
    "00000000-0000-4000-8000-000000000001"
  ],
  "clinician_id": "00000000-0000-4000-8000-000000000002",
  "patient_id": "00000000-0000-4000-8000-000000000004",
  "payor_type": "patient",
  "title": "Initial consultation quote"
}
```

## Response `201`

The requested `Quote`.

- `object`
  - `accepted_at` (`string | null`) - format: `date-time`; An ISO 8601 timestamp in UTC, with a `Z` suffix. For example, `2026-07-01T09:00:00Z`.
  - `alternative_payor_id` (`string | null`) - format: `uuid`; The AlternativePayor for an alternative-payor Quote.
  - `booking_ids` (`array`) - The Bookings linked to the Quote.
    - `items` (`string`) - format: `uuid`
  - `cancel_reason` (`string | null`) - enum: `staff`, `superseded`, `null`; Why the Quote was canceled. `staff` is a staff cancellation. `superseded` is set when a revised Quote replaces it.
  - `canceled_at` (`string | null`) - format: `date-time`; An ISO 8601 timestamp in UTC, with a `Z` suffix. For example, `2026-07-01T09:00:00Z`.
  - `clinician_id` (`string | null`) - format: `uuid`; The Clinician attributed to the Quote.
  - `created_at` (`string`) - format: `date-time`; An ISO 8601 timestamp in UTC, with a `Z` suffix. For example, `2026-07-01T09:00:00Z`.
  - `creation_source` (`string | null`) - enum: `staff_member`, `api`, `null`; How the Quote was created.
  - `currency` (`string | null`) - The ISO 4217 currency code.
  - `current_invoice_id` (`string | null`) - format: `uuid`; The current non-cancelled Invoice generated from the Quote.
  - `declined_at` (`string | null`) - format: `date-time`; An ISO 8601 timestamp in UTC, with a `Z` suffix. For example, `2026-07-01T09:00:00Z`.
  - `expired_at` (`string | null`) - format: `date-time`; An ISO 8601 timestamp in UTC, with a `Z` suffix. For example, `2026-07-01T09:00:00Z`.
  - `expires_on` (`string | null`) - format: `date`; The date the Quote remains valid through.
  - `id` (`string`) - format: `uuid`; The resource's unique identifier, formatted as an RFC 4122 version 4 UUID.
  - `invoice_id` (`string | null`) - format: `uuid`; The Invoice created by the first conversion or acceptance.
  - `invoice_ids` (`array`) - Every Invoice generated from the Quote, including cancelled generations.
    - `items` (`string`) - format: `uuid`
  - `invoiced_at` (`string | null`) - format: `date-time`; An ISO 8601 timestamp in UTC, with a `Z` suffix. For example, `2026-07-01T09:00:00Z`.
  - `line_items` (`array`) - The proposed charges on the Quote.
    - `items` (`object`)
      - `booking_id` (`string | null`) - format: `uuid`; The Booking associated with the line item.
      - `created_at` (`string`) - format: `date-time`; An ISO 8601 timestamp in UTC, with a `Z` suffix. For example, `2026-07-01T09:00:00Z`.
      - `currency` (`string | null`) - The ISO 4217 currency code.
      - `discount_amount` (`integer | null`) - The discount in minor currency units.
      - `external_provider_name` (`string | null`) - The provider that collects an external fee.
      - `id` (`string`) - format: `uuid`; The resource's unique identifier, formatted as an RFC 4122 version 4 UUID.
      - `is_billed_by_another_provider` (`boolean`) - Whether another provider bills this line instead of the practice that issued the Quote.
      - `object` (`any`) - Discriminator value emitted at `object`.
      - `quantity` (`number | null`) - The quantity quoted.
      - `title` (`string | null`) - The line item description.
      - `total` (`integer | null`) - The line total in minor currency units.
      - `unit_price` (`integer | null`) - The unit price in minor currency units.
      - `updated_at` (`string`) - format: `date-time`; An ISO 8601 timestamp in UTC, with a `Z` suffix. For example, `2026-07-01T09:00:00Z`.
  - `links` (`object`) - URLs to related resources.
    - `alternative_payor` (`string | null`) - format: `uri`; The full URL of a related resource.
    - `bookings` (`array`) - Developer Platform URLs for linked Bookings.
      - `items` (`string`) - format: `uri`; The full URL of a related resource.
    - `clinician` (`string | null`) - format: `uri`; The full URL of a related resource.
    - `current_invoice` (`string | null`) - format: `uri`; The full URL of a related resource.
    - `invoice` (`string | null`) - format: `uri`; The full URL of a related resource.
    - `invoices` (`array`) - Developer Platform URLs for every generated Invoice.
      - `items` (`string`) - format: `uri`; The full URL of a related resource.
    - `patient` (`string | null`) - format: `uri`; The full URL of a related resource.
  - `notes` (`string | null`) - Sanitized HTML notes on the Quote.
  - `object` (`any`) - Discriminator value emitted at `object`.
  - `patient_id` (`string | null`) - format: `uuid`; The Patient associated with the Quote.
  - `payor_type` (`string | null`) - enum: `patient`, `alternative_payor`, `null`; The party the Quote is issued to.
  - `quote_number` (`string | null`) - The Quote number, unique within the Organization.
  - `quoted_on` (`string | null`) - format: `date`; The date the Quote was issued or drafted.
  - `recipient_display_name` (`string | null`) - The display name of the live Patient or AlternativePayor the Quote is sent to.
  - `sent_at` (`string | null`) - format: `date-time`; An ISO 8601 timestamp in UTC, with a `Z` suffix. For example, `2026-07-01T09:00:00Z`.
  - `status` (`string | null`) - enum: `draft`, `sent`, `accepted`, `declined`, `expired`, `canceled`, `invoiced`, `null`; The Quote's current status.
  - `terms` (`string | null`) - Sanitized HTML terms copied onto the Quote.
  - `title` (`string | null`) - The Quote title.
  - `total` (`integer`) - The Quote total in minor currency units, including lines billed by another provider.
  - `total_collectible_by_issuing_organization` (`integer`) - The amount the issuing Organization will collect, in minor currency units. Excludes lines billed by another provider.
  - `updated_at` (`string`) - format: `date-time`; An ISO 8601 timestamp in UTC, with a `Z` suffix. For example, `2026-07-01T09:00:00Z`.

### Example

```json
{
  "id": "f72cd09f-9bdc-4341-8184-bf2f900a8669",
  "object": "quote",
  "accepted_at": "2026-01-01T09:00:00Z",
  "alternative_payor_id": null,
  "booking_ids": [
    "00000000-0000-4000-8000-000000000001"
  ],
  "cancel_reason": "staff",
  "canceled_at": "2026-01-01T09:00:00Z",
  "clinician_id": "00000000-0000-4000-8000-000000000002",
  "created_at": "2026-01-01T09:00:00Z",
  "creation_source": "staff_member",
  "currency": "GBP",
  "current_invoice_id": "b478fd43-d3a3-4599-8444-e55e805e96fe",
  "declined_at": "2026-01-01T09:00:00Z",
  "expired_at": "2026-01-01T09:00:00Z",
  "expires_on": "2026-01-01",
  "invoice_id": "00000000-0000-4000-8000-000000000027",
  "invoice_ids": [
    "00000000-0000-4000-8000-000000000027"
  ],
  "invoiced_at": "2026-01-01T09:00:00Z",
  "line_items": [
    {
      "id": "a2811c16-a221-45cd-8c38-176432273234",
      "object": "quote_line_item",
      "booking_id": "00000000-0000-4000-8000-000000000001",
      "created_at": "2026-01-01T09:00:00Z",
      "currency": "GBP",
      "discount_amount": 1,
      "external_provider_name": "London Bridge Hospital",
      "is_billed_by_another_provider": true,
      "quantity": 1.5,
      "title": "Dr",
      "total": 1,
      "unit_price": 1,
      "updated_at": "2026-01-01T09:00:00Z"
    }
  ],
  "links": {
    "alternative_payor": "https://api.carebit.co/v1/alternative_payors/815dfc1e-6eaf-4b71-8a94-ec2a13224fca",
    "bookings": [
      "https://api.carebit.co/v1/bookings/00000000-0000-4000-8000-000000000001"
    ],
    "clinician": "https://api.carebit.co/v1/clinicians/00000000-0000-4000-8000-000000000002",
    "current_invoice": "https://api.carebit.co/v1/current_invoices/b478fd43-d3a3-4599-8444-e55e805e96fe",
    "invoice": "https://api.carebit.co/v1/invoices/00000000-0000-4000-8000-000000000027",
    "invoices": [
      "https://api.carebit.co/v1/invoices/00000000-0000-4000-8000-000000000027"
    ],
    "patient": "https://api.carebit.co/v1/patients/00000000-0000-4000-8000-000000000004"
  },
  "notes": "Please confirm the appointment by email.",
  "patient_id": "00000000-0000-4000-8000-000000000004",
  "payor_type": "insurance_company",
  "quote_number": "QUO-1001",
  "quoted_on": "2026-01-01",
  "recipient_display_name": "Alex Morgan",
  "sent_at": "2026-01-01T09:00:00Z",
  "status": "draft",
  "terms": "<p>This quote is valid until the expiry date shown.</p>",
  "title": "Initial consultation quote",
  "total": 1,
  "total_collectible_by_issuing_organization": 1,
  "updated_at": "2026-01-01T09:00:00Z"
}
```

## Response `400`

The `Idempotency-Key` header is missing (`idempotency_key_required`) or exceeds 255 characters (`idempotency_key_too_long`).

- `object`
  - `error` (`object`) - The structured details that describe why the request failed.
    - `code` (`string`) - The machine-readable error code.
    - `errors` (`array | null`) - Additional errors from a failed validation.
      - `items` (`object`)
        - `code` (`string`) - The machine-readable code for this validation error.
        - `message` (`string`) - A message that explains this validation error.
        - `param` (`string | null`) - The name of the parameter that caused this validation error, when known.
    - `message` (`string`) - A message that explains the error and how to resolve it.
    - `param` (`string | null`) - The name of the parameter that caused the error, when known.
    - `type` (`string`) - enum: `authentication_error`, `permission_error`, `invalid_request_error`, `rate_limit_error`, `api_error`; The high-level category of the error.

### Example

```json
{
  "error": {
    "code": "resource_missing",
    "errors": [
      {
        "code": "resource_missing",
        "message": "The requested resource was not found.",
        "param": "patient_id"
      }
    ],
    "message": "The requested resource was not found.",
    "param": "patient_id",
    "type": "authentication_error"
  }
}
```

## Response `401`

The access token is missing, invalid, expired, or revoked.

- `object`
  - `error` (`object`) - The structured details that describe why the request failed.
    - `code` (`string`) - The machine-readable error code.
    - `errors` (`array | null`) - Additional errors from a failed validation.
      - `items` (`object`)
        - `code` (`string`) - The machine-readable code for this validation error.
        - `message` (`string`) - A message that explains this validation error.
        - `param` (`string | null`) - The name of the parameter that caused this validation error, when known.
    - `message` (`string`) - A message that explains the error and how to resolve it.
    - `param` (`string | null`) - The name of the parameter that caused the error, when known.
    - `type` (`string`) - enum: `authentication_error`, `permission_error`, `invalid_request_error`, `rate_limit_error`, `api_error`; The high-level category of the error.

### Example

```json
{
  "error": {
    "code": "resource_missing",
    "errors": [
      {
        "code": "resource_missing",
        "message": "The requested resource was not found.",
        "param": "patient_id"
      }
    ],
    "message": "The requested resource was not found.",
    "param": "patient_id",
    "type": "authentication_error"
  }
}
```

## Response `403`

The access token lacks the required scope, or the project is disabled.

- `object`
  - `error` (`object`) - The structured details that describe why the request failed.
    - `code` (`string`) - The machine-readable error code.
    - `errors` (`array | null`) - Additional errors from a failed validation.
      - `items` (`object`)
        - `code` (`string`) - The machine-readable code for this validation error.
        - `message` (`string`) - A message that explains this validation error.
        - `param` (`string | null`) - The name of the parameter that caused this validation error, when known.
    - `message` (`string`) - A message that explains the error and how to resolve it.
    - `param` (`string | null`) - The name of the parameter that caused the error, when known.
    - `type` (`string`) - enum: `authentication_error`, `permission_error`, `invalid_request_error`, `rate_limit_error`, `api_error`; The high-level category of the error.

### Example

```json
{
  "error": {
    "code": "resource_missing",
    "errors": [
      {
        "code": "resource_missing",
        "message": "The requested resource was not found.",
        "param": "patient_id"
      }
    ],
    "message": "The requested resource was not found.",
    "param": "patient_id",
    "type": "authentication_error"
  }
}
```

## Response `404`

The Patient, Clinician, Booking, or AlternativePayor was not found in the Organization.

- `object`
  - `error` (`object`) - The structured details that describe why the request failed.
    - `code` (`string`) - The machine-readable error code.
    - `errors` (`array | null`) - Additional errors from a failed validation.
      - `items` (`object`)
        - `code` (`string`) - The machine-readable code for this validation error.
        - `message` (`string`) - A message that explains this validation error.
        - `param` (`string | null`) - The name of the parameter that caused this validation error, when known.
    - `message` (`string`) - A message that explains the error and how to resolve it.
    - `param` (`string | null`) - The name of the parameter that caused the error, when known.
    - `type` (`string`) - enum: `authentication_error`, `permission_error`, `invalid_request_error`, `rate_limit_error`, `api_error`; The high-level category of the error.

### Example

```json
{
  "error": {
    "code": "resource_missing",
    "errors": [
      {
        "code": "resource_missing",
        "message": "The requested resource was not found.",
        "param": "patient_id"
      }
    ],
    "message": "The requested resource was not found.",
    "param": "patient_id",
    "type": "authentication_error"
  }
}
```

## Response `409`

A concurrent request holds the idempotency lease (`idempotency_conflict`). Retry after the delay indicated by `Retry-After`.

- `object`
  - `error` (`object`) - The structured details that describe why the request failed.
    - `code` (`string`) - The machine-readable error code.
    - `errors` (`array | null`) - Additional errors from a failed validation.
      - `items` (`object`)
        - `code` (`string`) - The machine-readable code for this validation error.
        - `message` (`string`) - A message that explains this validation error.
        - `param` (`string | null`) - The name of the parameter that caused this validation error, when known.
    - `message` (`string`) - A message that explains the error and how to resolve it.
    - `param` (`string | null`) - The name of the parameter that caused the error, when known.
    - `type` (`string`) - enum: `authentication_error`, `permission_error`, `invalid_request_error`, `rate_limit_error`, `api_error`; The high-level category of the error.

### Example

```json
{
  "error": {
    "code": "resource_missing",
    "errors": [
      {
        "code": "resource_missing",
        "message": "The requested resource was not found.",
        "param": "patient_id"
      }
    ],
    "message": "The requested resource was not found.",
    "param": "patient_id",
    "type": "authentication_error"
  }
}
```

## Response `422`

The Quote cannot be created. `payor_type` must be `patient` or `alternative_payor`.

- `object`
  - `error` (`object`) - The structured details that describe why the request failed.
    - `code` (`string`) - The machine-readable error code.
    - `errors` (`array | null`) - Additional errors from a failed validation.
      - `items` (`object`)
        - `code` (`string`) - The machine-readable code for this validation error.
        - `message` (`string`) - A message that explains this validation error.
        - `param` (`string | null`) - The name of the parameter that caused this validation error, when known.
    - `message` (`string`) - A message that explains the error and how to resolve it.
    - `param` (`string | null`) - The name of the parameter that caused the error, when known.
    - `type` (`string`) - enum: `authentication_error`, `permission_error`, `invalid_request_error`, `rate_limit_error`, `api_error`; The high-level category of the error.

### Example

```json
{
  "error": {
    "code": "resource_missing",
    "errors": [
      {
        "code": "resource_missing",
        "message": "The requested resource was not found.",
        "param": "patient_id"
      }
    ],
    "message": "The requested resource was not found.",
    "param": "patient_id",
    "type": "authentication_error"
  }
}
```

## Response `429`

Too many requests. Retry after the delay indicated by `Retry-After`.

- `object`
  - `error` (`object`) - The structured details that describe why the request failed.
    - `code` (`string`) - The machine-readable error code.
    - `errors` (`array | null`) - Additional errors from a failed validation.
      - `items` (`object`)
        - `code` (`string`) - The machine-readable code for this validation error.
        - `message` (`string`) - A message that explains this validation error.
        - `param` (`string | null`) - The name of the parameter that caused this validation error, when known.
    - `message` (`string`) - A message that explains the error and how to resolve it.
    - `param` (`string | null`) - The name of the parameter that caused the error, when known.
    - `type` (`string`) - enum: `authentication_error`, `permission_error`, `invalid_request_error`, `rate_limit_error`, `api_error`; The high-level category of the error.

### Example

```json
{
  "error": {
    "code": "resource_missing",
    "errors": [
      {
        "code": "resource_missing",
        "message": "The requested resource was not found.",
        "param": "patient_id"
      }
    ],
    "message": "The requested resource was not found.",
    "param": "patient_id",
    "type": "authentication_error"
  }
}
```

## Code samples

```bash
curl -X POST "https://api.carebit.co/v1/quotes" \
  -H "Authorization: Bearer $CAREBIT_ACCESS_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "booking_ids": [
    "00000000-0000-4000-8000-000000000001"
  ],
  "clinician_id": "00000000-0000-4000-8000-000000000002",
  "patient_id": "00000000-0000-4000-8000-000000000004",
  "payor_type": "patient",
  "title": "Initial consultation quote"
}'
```

```javascript
const response = await fetch("https://api.carebit.co/v1/quotes", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CAREBIT_ACCESS_TOKEN}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
  "booking_ids": [
    "00000000-0000-4000-8000-000000000001"
  ],
  "clinician_id": "00000000-0000-4000-8000-000000000002",
  "patient_id": "00000000-0000-4000-8000-000000000004",
  "payor_type": "patient",
  "title": "Initial consultation quote"
}),
});

if (!response.ok) {
  throw new Error(`Carebit API error: ${response.status}`);
}

const data = await response.json();
```

```python
import os
import requests
import uuid

response = requests.post(
    "https://api.carebit.co/v1/quotes",
    headers={
        "Authorization": f"Bearer {os.environ['CAREBIT_ACCESS_TOKEN']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "booking_ids": [
            "00000000-0000-4000-8000-000000000001"
        ],
        "clinician_id": "00000000-0000-4000-8000-000000000002",
        "patient_id": "00000000-0000-4000-8000-000000000004",
        "payor_type": "patient",
        "title": "Initial consultation quote"
    }
)
response.raise_for_status()
data = response.json()
```

```ruby
require "httparty"
require "json"
require "securerandom"

response = HTTParty.post(
  "https://api.carebit.co/v1/quotes",
  headers: {
    "Authorization" => "Bearer #{ENV.fetch("CAREBIT_ACCESS_TOKEN")}",
    "Idempotency-Key" => SecureRandom.uuid,
    "Content-Type" => "application/json"
  },
  body: {
    "booking_ids" => [
      "00000000-0000-4000-8000-000000000001"
    ],
    "clinician_id" => "00000000-0000-4000-8000-000000000002",
    "patient_id" => "00000000-0000-4000-8000-000000000004",
    "payor_type" => "patient",
    "title" => "Initial consultation quote"
  }.to_json
)
raise "Carebit API error: #{response.code}" unless response.success?
data = response.parsed_response
```

```php
<?php

$client = new GuzzleHttp\Client();

$response = $client->post("https://api.carebit.co/v1/quotes", [
    "headers" => [
      "Authorization" => "Bearer " . getenv("CAREBIT_ACCESS_TOKEN"),
      "Idempotency-Key" => bin2hex(random_bytes(16)),
    ],
    "json" => [
      "booking_ids" => [
        "00000000-0000-4000-8000-000000000001"
      ],
      "clinician_id" => "00000000-0000-4000-8000-000000000002",
      "patient_id" => "00000000-0000-4000-8000-000000000004",
      "payor_type" => "patient",
      "title" => "Initial consultation quote"
    ]
]);
$data = json_decode((string) $response->getBody(), true, flags: JSON_THROW_ON_ERROR);
```
