---
title: "Create a quote"
method: POST
path: "/quotes"
tags: ["Quotes"]
---

# Create a quote

`POST /quotes`

## Request

- Base URL: `https://joinways.app/api/v1`
- URL: `https://joinways.app/api/v1/quotes`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://joinways.app/oauth/authorize`, token URL `https://joinways.app/api/oauth/token`, scopes: `mcp.write`)

Creates a draft quote header (`source: joinways`) on an event, with the billed party of the event frozen on it and a timeline entry. Add line items with `POST /quotes/{id}/items`. Fires `quote.created`. **Role permission:** `quotes.manage`.

## Headers

- `Idempotency-Key` string

## Request body

- QuoteCreate: Creates a draft quote header (`source: joinways`). Add line items with `POST /quotes/{id}/items`.
  - `event_id` string, uuid, required
  - `title` string, required
  - `currency` 'EUR' | 'USD' | 'GBP' | 'CHF' | 'CAD' | 'AUD' | 'JPY' | 'CNY' | 'INR' | 'KRW' | 'SEK' | 'NOK' | 'DKK' | 'PLN' | 'CZK' | 'HUF' | 'RON' | 'RUB' | 'MXN' | 'BRL' | 'ARS' | 'CLP' | 'COP' | 'PEN' | 'SGD' | 'THB' | 'PHP' | 'IDR' | 'MYR' | 'VND' | 'AED' | 'SAR' | 'ILS' | 'TRY' | 'NZD' | 'ZAR' | 'NGN' | 'KES' | 'MAD' | 'TND' | 'DZD': ISO 4217 currency code.

## Response `201`

The created quote.

- Quote: A quote as listed (`GET /quotes`).
  - `id` string, uuid
  - `event_id` string, uuid
  - `quote_number` string, nullable
  - `title` string
  - `status` 'draft' | 'pending' | 'accepted' | 'signed' | 'rejected' | 'invoiced' | 'paid' | 'cancelled': `draft` = not sent yet; `pending` = sent, awaiting the client; `accepted` / `signed` = client agreement (signed = signature recorded); `rejected` = refused; `invoiced` = at least one invoice issued (a deposit included); `paid` = fully settled; `cancelled` = withdrawn. accepted, signed, invoiced and paid are live contracts counted in the event's confirmed amount (unless replaced, see `superseded_by_quote_id`).
  - `currency` string
  - `total_excl_tax` number
  - `total_tax` number
  - `total_incl_tax` number
  - `is_signed` boolean, nullable
  - `signed_at` string, date-time, nullable
  - `signature_mode` 'manual' | 'public' | 'null', nullable
  - `signer_name` string, nullable
  - `signer_email` string, nullable
  - `is_invoiced` boolean, nullable
  - `invoice_number` string, nullable
  - `invoice_date` string, date-time, nullable
  - `due_date` string, date-time, nullable
  - `payment_date` string, date-time, nullable
  - `is_reverse_charge` boolean, nullable
  - `sent_at` string, date-time, nullable: When the quote was sent (for a platform quote, when the platform transmitted it).
  - `viewed_at` string, date-time, nullable
  - `last_viewed_at` string, date-time, nullable
  - `view_count` integer
  - `expires_at` string, date-time, nullable: Validity end of the offer.
  - `created_at` string, date-time
  - `author_id` string, uuid
  - `billing_profile_id` string, uuid, nullable
  - `source` 'joinways' | 'platform': `joinways` = written in Joinways. `platform` = a booking platform's own document recorded in Joinways, mostly read from the platform's emails (quote transmitted, with its PDF), or imported once signed. A platform quote is read-only: its amounts, discounts, currency and lines are never edited, it is never deleted nor duplicated, and its status only takes `accepted` or `rejected` through the API; its PDF is the platform's.
  - `source_platform` string, nullable: Booking platform the document comes from (platform quote), or the one a Joinways quote was deposited to (e.g. Kactus).
  - `external_reference` string, nullable: The platform's own quote number (a platform that renumbers each revision gives each version its own). Unique per event.
  - `platform_pushed_at` string, date-time, nullable: When a Joinways quote was deposited on the platform (first deposit only).
  - `superseded_by_quote_id` string, uuid, nullable: Set when this quote was replaced: by a later version the booking platform sent, or by a signed amendment. A replaced quote keeps its status but never counts in the event's confirmed amount; only quotes where this is null are live. Set by Joinways only.
  - `superseded_at` string, date-time, nullable: When the quote was replaced (null while it is live).
  - `account_id` string, uuid
  - `totals_updated_at` string, date-time, nullable
  - `discount_percent` number, nullable
  - `discount_amount` number, nullable
  - `invoiced_at` string, date-time, nullable
  - `invoiced_by` string, uuid, nullable
  - `invoice_pdf_path` string, nullable
  - `invoice_snapshot` object, nullable
  - `payment_terms` string, nullable
  - `cancellation_terms` string, nullable
  - `postponement_terms` string, nullable
  - `purchase_order` string, nullable
  - `client_first_name` string, nullable
  - `client_last_name` string, nullable
  - `client_email` string, nullable
  - `client_phone` string, nullable
  - `client_is_individual` boolean, nullable
  - `client_company_name` string, nullable
  - `client_address` string, nullable
  - `client_postal_code` string, nullable
  - `client_city` string, nullable
  - `client_state` string, nullable
  - `client_country` string, nullable
  - `client_vat_number` string, nullable
  - `client_registration_number` string, nullable
  - `client_purchase_order` string, nullable
  - `parent_quote_id` string, uuid, nullable: The quote this one amends (amendment).
  - `amendment_sequence` integer
  - `platform_request_token` string, nullable
  - `translations` object
  - `space_plan_snapshot` object, nullable
  - `space_plan_snapshot_at` string, date-time, nullable
  - `ai_generated` boolean
  - `ai_source_lead_id` string, uuid, nullable
  - `ai_model` string, nullable
  - `ai_confidence` number, nullable
  - `ai_reasoning` string, nullable
  - `ai_warnings` string[]
  - `ai_generated_at` string, date-time, nullable
  - `document_lines` QuoteDocumentLine[], nullable: Platform quotes only: the lines read out of the PDF the platform sent, read-only. They never count in the totals (the quote keeps one line carrying the printed amount). Null on a Joinways quote; empty until read (see `document_lines_status`).
    - `id` string, uuid
    - `title` string
    - `description` string, nullable
    - `section` string, nullable: Heading printed above the line on the document.
    - `quantity` number, nullable
    - `unit` string, nullable
    - `day` string, date, nullable: Event day the line belongs to, when the document says.
    - `amount_excl_tax` number, nullable
    - `amount_incl_tax` number, nullable
    - `is_option` boolean
    - `is_discount` boolean
  - `document_lines_status` 'pending' | 'done' | 'failed' | 'no_pdf' | 'skipped' | 'null', nullable: Where the reading of the platform PDF stands. Null = not asked (only won platform quotes are read automatically).
  - `document_lines_extracted_at` string, date-time, nullable
  - `rejected_with_event` boolean
  - `is_demo` boolean

## Other responses

- `400`: Validation failed (`invalid_request`). For a body that fails the schema, `details` holds `formErrors` and `fieldErrors`.
- `401`: Missing, invalid, expired or revoked bearer token (`unauthorized`).
- `402`: Quotes are not included in the workspace plan (`payment_required`, `details.reason` `quotes_plan_required`).
- `403`: `forbidden`: the token lacks the scope, its owner's role lacks the permission named by the operation, or the workspace has neither the Pro plan nor a trial (`details.reason` `plan_not_eligible`).
- `409`: The quote's state refuses the operation (`conflict`); retrying the same call fails the same way until that state changes (except `amendment_in_flight`, transient). `error.details.reason` (see `QuoteErrorReason`): `quote_locked` (signed, invoiced or paid: amend or cancel instead), `quote_signed`, `quote_cancelled`, `quote_not_cancellable`, `quote_has_active_invoice` (credit the invoice first), `quote_has_amendments` (a quote with a live amendment is frozen), `quote_superseded` (a replaced version is history), `amendment_in_flight` (one amendment at a time), `amendment_nested`, `parent_not_active`, `parent_not_signed`, `parent_fully_invoiced`, `quote_changed` (the status changed meanwhile), `invalid_status`, `not_a_platform_version`, `event_closed` (event lost or cancelled), `event_archived`, `platform_quote_read_only` (a platform quote, `source: platform`, is the platform's document: amounts, discounts, currency and lines are read-only, its status only takes `accepted` or `rejected`), `platform_quote_delete_forbidden` (cancel or refuse it instead), `platform_version_signed` (another version is signed), `quote_conflict` (another live contract must be cancelled or refused first).
- `429`: Too many requests (`rate_limited`). `details.reason`: `per-ip`, `per-account-minute`, `per-account-day`, or `email_rate_limited` (email budget). Always carries `Retry-After`: wait that many seconds.

---

[API](https://skmtc.dev/joinways/apis/joinways-public-api.md) · [All operations](https://skmtc.dev/joinways/apis/joinways-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/joinways/apis/joinways-public-api/revisions/39e84309953e?raw)
