Help

Use the API

Keys in Settings, the limits and errors, safe retries with Idempotency-Key, and curl examples to read, create and send a proposal.

Updated

On this page
  1. Make a key
  2. Call the API
  3. Limits
  4. Paging and changes since
  5. Safe retries with Idempotency-Key
  6. Read, create and send a proposal

The API lets your own tools read your proposals, clients, products and templates, create a proposal, publish it, send it and get its link. It is on every plan, and Zapier, Make and your own scripts can call it with a key. Every endpoint and field is in the OpenAPI file. To get events the other way, see Use webhooks.

Make a key

Only owners and admins can make keys.

  1. Select Settings, then API and webhooks.
  2. Under API keys, select Create key.
  3. Give it a name, such as Zapier, and choose Read only or Read and write.
  4. Select Create key, then Copy. The key is shown once, so copy it now.

A key looks like pz_live_Ab3dE9xQ_…. You can have up to 25.

A key acts as the person who made it. It follows their rights, and the activity shows "Ann via API (Zapier)". It stops working at once if they leave or are no longer an owner or admin. To stop a key yourself, select Revoke.

Call the API

The base URL is https://propozit.com/api/v1. Send the key in the Authorization header, and send JSON with Content-Type: application/json:

curl https://propozit.com/api/v1/me \
  -H "Authorization: Bearer pz_live_…"

Every answer is JSON. An error looks like this:

{ "error": { "code": "invalid", "message": "Check the highlighted fields.", "fields": { "title": "Enter a title" } } }
Status Code What it means
400 bad_request The JSON, a parameter or the cursor is not valid.
401 unauthorized No key, a wrong key, or a key that was revoked or stopped.
402 plan_limit Your plan's limit was reached. The answer has an upgrade_url.
403 forbidden A read-only key tried to write.
404 not_found Not in your organization, or no such path.
409 conflict A stale revision, a client that exists, or an Idempotency-Key still running.
413, 415 too_large, unsupported_media_type The body is over 1 MB, or not JSON.
422 invalid A field is wrong. fields says which.
429 rate_limited or send_limit Too many requests or emails. Wait for Retry-After seconds.
500 internal Something went wrong on our side.

Limits

Each organization has a number of requests a day, reset at midnight UTC: 200 on Free, 2,000 on Solo, 5,000 on Team and 20,000 on Agency, and 10,000 during early access, before plans start. Each key can also make up to 60 requests a minute. Every answer has X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Settings → API and webhooks shows today's count.

Your plan's other limits apply as they do in the app. On Free, the first send or link of a proposal counts toward 3 sent proposals a month. Each person can send up to 50 emails an hour, and a key shares them with what its maker sends in the app.

Paging and changes since

Lists answer { "data": [...], "next_cursor": "..." }, newest change first. Ask for up to 100 rows with limit (25 by default), and pass next_cursor back as cursor; it is null on the last page. For only what changed, pass updated_after, such as 2026-10-26T09:00:00Z.

Safe retries with Idempotency-Key

Every POST takes an Idempotency-Key header: any string of 1 to 255 visible characters, such as a UUID. Use a new one for each request, and the same one to retry:

  • the same key and body within 24 hours gives the first answer again, with Idempotent-Replayed: true;
  • the same key with a different body gives 422 invalid;
  • the same key while the first request is still running gives 409 conflict: wait and retry.

Answers with 500, 409 or 429 are not kept, so a retry runs again. After a rare crash on our side while a proposal is being made, a retry can make a second draft: move the extra one to Trash in the app.

Read, create and send a proposal

List proposals, filtered by status, client_id, kind or updated_after. /clients, /products and /templates work the same way. Decks and clients in Trash are left out of lists, and a call on one answers 404.

curl "https://propozit.com/api/v1/decks?status=sent&limit=50" \
  -H "Authorization: Bearer pz_live_…"

Create a draft from a template. engagement is a key from GET /templates. Pass client_id for a client you have, or client_company for a new one. The draft gets your standard products and prices for that type, so check it before you send:

curl -X POST https://propozit.com/api/v1/decks \
  -H "Authorization: Bearer pz_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2a9e-0b7d-4c1e-9a55-2d0f3e1b7c44" \
  -d '{"engagement":"fixed_project","title":"Halden webshop","language":"en","currency":"EUR","client_company":"Halden Coffee"}'

Send it by email to up to 20 addresses; it publishes first if needed. /publish only publishes, and /link gives the link to share yourself:

curl -X POST https://propozit.com/api/v1/decks/DECK_ID/send \
  -H "Authorization: Bearer pz_live_…" \
  -H "Content-Type: application/json" \
  -d '{"to":["jo@halden.example"],"subject":"Our proposal for {company}","message":"Hi Jo,\n\nHere is our proposal.\n\n{link}"}'

Placeholders such as {link}, {company} and {title} work as in the app. When your message has no {link}, the email still gets the Open the proposal button at the end. A signed proposal can't be sent again.

POST /clients adds a client, and PATCH /clients/CLIENT_ID changes only the fields you send. The API never writes prices: sending discount_pct or prices gives 422 "This field is not accepted". Change them in the app.

Proposals your clients actually say yes to

Propozit is free to start, with no card needed. Create your account today and see the plans on the pricing page.

Create your account