Create an offer
const url = 'https://apisandbox.puurk.com/offers';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"oid":"example","type":"template","lead_id":"example","title":"Starter Plan","services":[{"service_id":"svc_7Hk2LqRm","quantity":1,"merge_contract_id":"example"}],"location_id":"example","merge_contract_id":"example","notes":"example"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://apisandbox.puurk.com/offers \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "oid": "example", "type": "template", "lead_id": "example", "title": "Starter Plan", "services": [ { "service_id": "svc_7Hk2LqRm", "quantity": 1, "merge_contract_id": "example" } ], "location_id": "example", "merge_contract_id": "example", "notes": "example" }'Create a new offer/plan. A client offer (type: client, or type omitted) requires lead_id. A template offer (type: template) is the shareable, client-less plan the provider app calls a “Template”; a lead_id sent with one is ignored, not rejected — the service stores lead_id, lead_name and uid as explicit nulls whatever the payload said.
This endpoint does not price the offer: caller-supplied totals are stored verbatim, so it has no merchant-account precondition.
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”Request body
Body for POST /offers. Only oid is required. id and plan_number are server-assigned and ignored if sent, and this endpoint does not price — any totals supplied are stored verbatim.
object
The organization the plan belongs to.
client — a plan for a specific client; requires lead_id. template — a shareable, client-less plan. Omitted or null is treated as client, so intent always comes from this field rather than from the absence of a lead_id.
Example
templateRequired when type is client or omitted, otherwise the request fails with 400 Missing lead_id. Sent alongside type: template it is ignored — the service stores lead_id, lead_name and uid as explicit nulls.
Defaults to Plan <plan_number> when omitted. Must be non-blank if supplied.
Example
Starter PlanService lines to create the plan with, in one call. This is the path that works today for a brand-new plan — use POST /offers/{offerId}/services to add lines to an existing one.
object
Example
svc_7Hk2LqRmGreater than 0. Fractional quantities are allowed.
Example
1Responses
Section titled “Responses”Offer created
The created offer, plus the two links POST /offers returns alongside it.
object
Unique identifier for the offer
client — a plan for a specific client; requires lead_id. template — a shareable, client-less plan (the provider app calls these “Templates”); lead_id, lead_name and uid are always null on one.
Null on offers created before the type was recorded, until the backfill stamps them. Newly created offers are never null — an offer created with a lead_id and no type is stored as client.
The client this plan is for. Required when type is client or omitted. On a template it is always null in the response: one sent in the request is ignored rather than rejected.
Always null on a template offer.
Always null on a template offer.
Deprecated
Deprecated
object
Offer these terms belong to
User the terms were generated for
Down payment in cents
Amount financed in cents
Provider’s share of the premium as a fraction between 0 and 1
Plan length in months
Premium charged per month in cents
Provider share of the monthly premium in cents
Puurk share of the monthly premium in cents
Principal per month in cents
Fees included in each monthly payment in cents
Payout fees per month in cents
Provider fees per month in cents
Monthly payment in cents
Monthly opportunity for the provider in cents
Total paid over the plan in cents
Total premium over the plan in cents
Provider share of the total premium in cents
Puurk share of the total premium in cents
Total principal (down payment plus financed amount) in cents
Total fees over the plan in cents
Total payout fees over the plan in cents
Total provider fees over the plan in cents
Total opportunity for the provider in cents
object
User id of the salesperson
object
Opens this plan in the provider app, for the organization’s own staff. Do not send it to the client; share client_url instead.
The shareable link to this plan. For a template offer this is the template link — the URL an integrator hands out, since a template has no client of its own. For a client offer it is that client’s plan link.
Example
{ "type": "template", "lead_id": "fLp5zssaLLRcKDbh0DpP", "lead_name": "John Doe", "uid": "l8zcL5Va8ei6QQlmhe8n", "location_id": "g9WWX7DJ0ev6WZ2rOhdQ", "merge_contract_id": null, "oid": "iQyjWJf77rrVUx8HJshp", "organization_name": "Acme Medical", "provider_id": null, "provider_name": null, "providers": [ "4NoHg9a7Yf0QODl1IMcE", "WKVlP1g5arHJdDa2yBir" ], "title": "Plan 1234", "payment_option": "finance", "postpone_reason": "expensive", "status": "pending", "provider_url": "https://provider.puurk.com/plans/plan/abc123", "client_url": "https://app.puurk.com/offers/offer/abc123"}Validation failed (invalid_params). Returned when oid is absent (Missing oid), when lead_id is absent for a client offer (Missing lead_id), when type is not one of template or client, or when any optional string field is present but empty or whitespace-only. A lead_id sent with type: template is not an error — it is ignored.
The standard error body. error is a stable, machine-readable code — branch on it rather than on message, which is for humans and may change.
object
Machine-readable error code.
Human-readable detail.
Example
{ "error": "invalid_params", "message": "Missing lead_id"}Unauthorized
forbidden — the token lacks the write action on offers for this organization. Body validation runs first, so an invalid payload answers 400 before this is reached.
The standard error body. error is a stable, machine-readable code — branch on it rather than on message, which is for humans and may change.
object
Machine-readable error code.
Human-readable detail.
Example
{ "error": "forbidden", "message": "API client lacks 'write' on 'offers' for organization 'org_123'"}lead_not_found when lead_id does not resolve.
The standard error body. error is a stable, machine-readable code — branch on it rather than on message, which is for humans and may change.
object
Machine-readable error code.
Human-readable detail.
Example
{ "error": "lead_not_found", "message": "No client exists for the supplied lead_id"}create_offer_failed — the offer could not be saved.
The standard error body. error is a stable, machine-readable code — branch on it rather than on message, which is for humans and may change.
object
Machine-readable error code.
Human-readable detail.
Example
{ "error": "create_offer_failed", "message": "Failed to create offer"}