Skip to main content
New business is the journey from “a prospect wants cover” to “a contract is in force”. It runs through five statuses; most transitions are a call you make, and the last one is not: POLICY_CREATED → the contract exists, unpriced. POLICY_QUOTED → the risk has been priced. POLICY_CONFIRMED → the terms are locked and the paperwork can be produced. POLICY_SIGNED → the policyholder has committed. POLICY_STARTED → cover has begun, which on some products waits for the first premium as well as the start date. Statuses carry their entity prefix in every response, and assets and customers use ASSET_* and CUSTOMER_*. This page walks the whole journey. It assumes you have credentials and can call the API — see Authentication — and that you have read Read the product configuration, because which fields you must send at each step comes from the product, not from this page.

The journey at a glance

One example run. The product’s own configuration decides parts of it — whether a payment method is needed before signature, which fields are required at which step, whether cover starts on signature — so read yours rather than assuming this shape.

Step by step

1

Read the product configuration

This call is public — no token, just the tenant header. Every step after it needs one.Keep the response. Its six field lists tell you what to send in the next four steps, and its payment, billing and signature sections tell you whether this product needs a payment method before signature and how signing is delivered.If you are building a form on top of this, read Should your form be config-driven? first — the short version is that the config drives which fields you ask for and how they behave, while labels, ordering and grouping are yours to write, because the config holds none of them.
2

Initialize a customer, and with it a policy

One call creates the policyholder, the policy, and the branch you will work on. The body is optional — send {} for a direct sale, or name the distributing firm with brokerageFirmId:
Response
Keep initializedWith.branch. Confirmation requires a branchId query parameter, and a new-business policy carries more than one branch. If you lose it, list the open branches with GET /policies/{policyId}/branches.
A branch is a working copy of the contract. New business happens on its own branch; mid-term adjustments and renewals each get theirs. Reads and writes are branch-scoped, which is why querying a policy without the right branchId can look as though your data never landed.
3

Fill in the customer

Custom fields go in customFields; the customer’s aggregate fields (name, role, siret) are top-level:
branchId is marked optional in the reference, but omit it here and any field whose requiresOnChange is QUOTE — which on most products is every field you need — is refused:
The error names the fields, not the missing parameter, which sends you looking in the wrong place. Pass the branchId from step 2 on every write.
Field keys are per-product — read them from the configuration rather than copying these. Note the birth date: a DATE field wants 1985-04-12, and rejects a full ISO datetime.
For a company product, expect the company block you read back to differ from what you sent. Supplying the SIRET triggers a registry lookup that overwrites company name, address, postcode, city, activity code and legal statuses, and nulls legal statuses the registry does not return. This is intended behaviour, explained in Fields the platform owns.
4

Create the insured items

startedAt is an aggregate field on most products — a top-level property, not a custom field:
That startedAt is Paris-local midnight, not UTC midnight — 22:00:00Z the previous day in summer, 23:00:00Z in winter. UTC midnight silently buys 364.92 days of cover for a full year’s premium, with no error anywhere. Why, and how to convert: Dates and times.
Check the product’s assetAggregateFields rather than assuming: if startedAt is declared there with requiredFor: QUOTE, quoting fails without it. Use POST /assets/batch/create when a contract covers many items.
5

Set the contract-level terms

The policy’s aggregate fields — how often the customer is invoiced, and who pays — are top-level properties of PATCH /policies/{policyId}. Two different kinds of data live there, and they are not collected at the same moment:
  • invoicingConfig is a term you set. It is required before pricing, so it belongs in a call your code makes as it prepares the quote.
  • Policy custom fields are usually answers a person gives. They can be sent any time before the action their requiredFor names — often confirmation, not quote.
This example sends both in one call because a script can. If you are building an interface, do not copy that shape into the step that opens your pricing screen: the custom fields would be sent before the customer has typed anything, and never sent again. On a product where no policy field is priced or printed, nothing fails — the answers are simply lost.
Take invoicingConfig straight from the product’s billing.defaultConfig — that is the shape it expects, and there is nothing for you to invent:
Send every aggregate field the product declares, at the step its requiredFor demands. On the demo products that means invoicingConfig, required at QUOTE — so this call comes before pricing, not after it.Some products additionally declare billingAssignment ({ configId, defaultPayerId }), required at confirmation; none of the demo products do. Read policyAggregateFields instead of assuming either way, because the error names the field rather than the step it was needed for.
6

Price the risk

Two endpoints price a contract, and the difference matters:POST /policies/{policyId}/calculate-quote computes a premium and persists nothing. Use it to show a price, to compare options, or to try overrides — it accepts customFieldsToOverride, brokerageFeesOverride, periodDurationOverride and assetIds for exactly that. It also tolerates assets without a startedAt.POST /policies/{policyId}/quote computes the premium and writes it to the policy. This is the call that moves the contract forward; confirmation works from the quote it stored.Both are covered in detail in Quotes.options is an array with one entry per asset, each naming the coverage the customer chose. The permitted values come from the config’s quote object — quote.availableTiers, quote.availableExcesses and quote.availablePerils. Not publicQuoteConfig, which is a smaller summary carrying only productId, defaultTier and tiers:
Add perils to an option to sell optional cover, where the product offers any. Check quote.availablePerils and look at each peril’s options array first: a peril with a single permitted option is not a choice, and sending another value is rejected with INVALID_QUOTE_OPTIONS_INVALID_PERIL_OPTION.
POST /policies/{policyId}/quote returns an empty body. It records the quote rather than handing it back, so read the result afterwards — or use calculate-quote, which does return the full breakdown.Read it from the right place: the premium lands on the policy, but the chosen tier is on the asset (GET /assets/{assetId}quote.options.tier), because cover is chosen per insured item. See Read a contract back.
Pricing can fail on underwriting grounds rather than on your payload — an ineligible risk, a value outside the carrier’s appetite. Those failures are recorded against the policy, so a rejected quote is an outcome to handle, not necessarily a bug in your request.
7

Confirm the contract

Confirmation locks the terms:
Every field whose requiredFor is CREATION, QUOTE or CONFIRMATION must be present, across the customer, the policy and every asset. This is where an integration that treated aggregate fields as custom fields — or never sent them at all — discovers the gap.
8

Collect a payment method, if the product needs one first

Whether payment comes before signature is a product decision, and it is in the configuration. See Payments for the mechanics, the Stripe hand-off, and the mandate.
9

Send it for signature

Pass branchId here too, even though the reference marks it optional. The documents offered for signature are the ones on the branch you name, so omitting it looks for the paperwork on the base contract and fails with 425 NO_UNIQUE_DOCUMENT_TYPE_READY_TO_BE_SIGNED and documentNumber: 0 — while GET /policies/{policyId}/documents?branchId=<branchId> plainly lists the document.
The response lists the signature requests and their signers. Signing happens outside your system — the policyholder receives it by email, or you place them in front of the signer link. Either way, completion is something you observe rather than receive: see Asynchronous operations.
10

Watch it come into force

Activation is not a call you normally make. A signed contract comes into force when its start date arrives and, on products that require it, when the first premium has been collected. Poll GET /policies/{policyId} and read the status.A contract that is signed but not yet in force is the normal state for a future-dated start or an uncollected first premium — not a failure. There is nothing to call: wait for the start date, or collect the premium.
POST /policies/{policyId}/activate is not the escape hatch its name suggests. It only brings a suspended contract back into force, and rejects anything else with 409 CONFLICTING_POLICY_STATUS. On a signed-but-not-yet-in-force contract — the case above — it always fails. Nothing in the new-business journey calls it.

Where integrations get stuck

  • Fields rejected as “cannot be modified with this context”. You omitted branchId on a write.
  • A required field you believe you sent. Check which of the six configuration lists it came from. Aggregate fields are top-level properties; custom fields go in customFields.
  • Quoting refuses fields you treated as read-only. An integrationKey does not mean the platform fills the field; only the registry family is filled for you. See Fields mapped onto an integration.
  • PAYMENT_CUSTOMER_NOT_FOUND when asking for a setup secret. The payment customer is created when the policy is quoted. Quote first.
  • The contract is two hours short. startedAt needs to be Paris-local midnight.
  • Confirmation rejected on a field the customer never sees. invoicingConfig and billingAssignment are contract terms you set, not customer input.
  • A value you sent came back different. A field with an integrationKey is owned by its integration; company identity comes from the registry.
  • Nothing appears to have been saved. You are probably reading a different branch than you wrote.
  • The contract is signed but not in force. Check the start date, then the first premium.