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
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.
- Select Settings, then API and webhooks.
- Under API keys, select Create key.
- Give it a name, such as Zapier, and choose Read only or Read and write.
- 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.
Related
- Use webhooks Add an endpoint in Settings, every event and its fields, how to check a signature in Node, and how retries, the delivery log and Resend work.
- Send a proposal and get it signed Send with your own message, unlock with an email code, signing drawn or typed, the signed PDF, and the activity panel.
- Invite your team Send an invitation by email, pick a role, and manage who has access from the Team tab.
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.