ProposalsProposals

Proposals API and integrations

Beta. Integrations are switched on per team while they are in beta. If you don't see Settings → Integrations in your Proposals account, write to support@proposals.co and we'll turn it on.

Proposals integrations let you create and manage proposals from the tools you already use: ChatGPT, Claude, or your CRM through Zapier, Make or n8n. A proposal made this way goes through the same engine as one made in the panel: your brand, your price, the same quality checks, the same tracking. You see it in the panel like any other proposal, marked with where it came from.

Two things integrations deliberately don't do:

  • Proposals doesn't write or send emails through the API. You get the public link and the key facts of the proposal. The email is written by you, or by the assistant you're talking to, which knows the conversation and the client better than any template.
  • One proposal per call. You can create many proposals, each with its own request and within the rate limits. There is no bulk import and no campaign endpoint.

Guides

Guide For
ChatGPT Connecting Proposals to ChatGPT and making a proposal in a chat
Claude Connecting Proposals to Claude and making a proposal in a chat
Zapier, Make and n8n Creating proposals from a CRM deal with an API key
API reference Every endpoint with request and response examples

The live OpenAPI description is at https://api.proposals.co/api/public/v1/openapi.json.

Two ways in

REST API MCP server
For Automations and your own code ChatGPT, Claude and other assistants that support MCP connectors
Address https://api.proposals.co/api/public/v1 https://api.proposals.co/mcp
Sign-in API key (Authorization: Bearer prp_live_…) OAuth: you log in to Proposals and approve the permissions
Who sets it up A Proposals admin creates the key Any team member connects their own assistant

Both call the same service, so the rules below apply to both.

Authentication

API keys (REST)

An admin creates keys in Settings → Integrations → API keys. The secret starts with prp_live_ and is shown once, right after you create it. Store it in your automation tool's secret storage, never in a shared document.

Send it in the Authorization header on every request:

Authorization: Bearer prp_live_...

The key is never accepted in the query string. Keys are valid for 365 days by default, and you can choose a shorter period when you create one. Every team member can see the list of keys with their prefix and when each was last used; only admins can create or revoke them.

A key acts on behalf of the admin who created it and never has more rights than that person. If they leave the organization, the key stops working.

Connected apps (MCP)

ChatGPT and Claude connect through OAuth. When you add the connector, you are sent to Proposals to log in and approve the permissions. Publishing is unticked by default. You can disconnect an app at any time in Settings → Integrations → Connected apps, and its access ends immediately.

Permissions (scopes)

Scope Lets the integration
brands:read See your brands (name, language, currency), so it can pick the right sender
proposals:read Read proposals: status, questions, key facts, link, whether the client opened or accepted
proposals:write Create proposals, answer their questions, ask for revisions, change recipient and terms, mark as sent
proposals:publish Publish a proposal (make its link live) and unpublish it

A new API key gets brands:read, proposals:read and proposals:write. proposals:publish is only granted when you tick it. Without it, an integration can prepare a draft, but a person publishes it in the panel.

A call without the needed scope fails with 403 insufficient_scope, and detail.params.required names the missing scope.

What stays in your control

  • Drafts are private. Creating a proposal never publishes it.
  • Publishing needs three things at once: the proposals:publish scope, no blockers in the pre-publish check, and an explicit "confirm": true in the call. There is no way to force past a blocker through the API.
  • You see everything. Every proposal shows its source in the panel (for example "Created in ChatGPT"), each proposal has a history of what integrations did with it, and publishing through an integration sends the proposal owner a notification.
  • Your data stays yours. Other organizations' proposals and brands don't exist for your key: they answer 404, never 403.

Proposal status

Every proposal has one status, computed from its current state:

Status Meaning What to do
generating The draft is being written (usually 30–120 seconds) Poll again after next_poll_after_ms
needs_input The notes are ambiguous, for example two different prices or two company names Ask the user the questions, then send the answers
ready_for_review The draft is ready and nothing blocks publishing Review it (panel_url) or publish it
blocked The draft is ready but the pre-publish check found a blocker Read preflight.blockers and fix them, usually in the panel
failed Generation didn't finish; error.code says why Fix the cause, then retry with an empty answers call
draft The proposal exists but has no page yet (for example it was started in the panel) Continue in the panel
published The link is live Send it to the client
sent You marked it as sent, or it was sent from the panel Wait for the client
opened The client opened it Follow up while it's fresh
accepted The client accepted it –

Publishing is not sending. A proposal becomes sent only when you say so (mark-sent) or when it was sent from the panel.

Asynchronous work and polling

Writing a proposal takes time, so POST /proposals, POST /proposals/{id}/answers and POST /proposals/{id}/revise return 202 right away with the proposal in status generating. Then:

  1. Wait next_poll_after_ms milliseconds (it's only present while the status is generating).
  2. Call GET /proposals/{id}.
  3. Repeat until the status is anything other than generating.

If your tool can't loop, add ?wait_seconds=50 to POST /proposals: the call waits up to 50 seconds and returns the state at that moment. It may still be generating, so keep a fallback poll.

Idempotency

Every POST needs an Idempotency-Key header. Use a value that is unique per intended action, for example your CRM deal ID plus the action name (deal-731-create).

  • Same key, same body: you get the original response back, and nothing is done twice. This is safe after a timeout or a lost response.
  • Same key, different body: 409 idempotency_conflict, nothing happens.
  • Same key while the first request is still running: 409 idempotency_in_progress; retry a moment later.
  • A POST without the header: 422 idempotency_key_required.

Keys are remembered for 30 days and are scoped to your credential. Keys can be up to 255 characters long.

external_reference protects you a second time: if you set it (for example to your CRM deal ID), a second proposal with the same value fails with 409 external_reference_exists, and detail.params.proposal_id points to the existing one.

MCP clients don't send headers, so the MCP server deduplicates on its own: the same tool call with the same arguments within 10 minutes returns the first result.

Limits

Per API key or connected app:

  • 60 reads per minute,
  • 10 new proposals per minute,
  • 20 revisions per 10 minutes,
  • 30 other changes per minute.

Per organization, at most 3 proposals can be generating at the same time.

Over a limit, you get 429 with a Retry-After header (seconds). rate_limited means your credential sent too many requests; too_many_generations means three proposals are already being written.

Your plan applies exactly as in the panel: a proposal created through an integration counts as one created proposal, and AI actions follow the same fair use. When the plan blocks an action, you get 402 with the same codes as in the panel.

Errors

Every error has the same shape:

{
  "detail": {
    "code": "insufficient_scope",
    "message": "This key can't publish proposals.",
    "params": { "required": "proposals:publish" }
  },
  "request_id": "req_01J..."
}

message is written for a person and follows Accept-Language (pl gives Polish, anything else English). Build your logic on code, not on message. Include request_id when you write to support.

HTTP Common codes Meaning
401 invalid_credentials The key or token is missing, wrong, expired or revoked
402 trial_offer_cap, usage_limit_exceeded, fair_use_exceeded, subscription_inactive The plan doesn't allow this right now; resolve it in the panel's billing settings
403 insufficient_scope, integrations_not_enabled Missing permission, or integrations aren't switched on for your organization
404 not_found It doesn't exist, or it belongs to another organization
409 idempotency_conflict, idempotency_in_progress, external_reference_exists, site_conflict, generation_in_progress, offer_accepted Conflict with the current state; see below
422 idempotency_key_required, preflight_blockers, invalid input The request can't be carried out as sent
429 rate_limited, too_many_generations Wait for Retry-After seconds

A few codes worth handling explicitly:

  • site_conflict – someone edited the draft since the revision you sent in expected_revision. Nothing was lost: read the proposal again and decide.
  • generation_in_progress – the proposal is already being written; poll instead of starting again.
  • offer_accepted – the client has accepted, so the proposal can no longer be changed.
  • preflight_blockers – publishing was refused; the blockers are listed in the proposal's preflight.
  • brand_name_unconfirmed – the brand's name hasn't been confirmed yet; confirm it in the panel under brand settings.
  • email_verification_required – the account behind the credential hasn't verified its email address yet.

Versioning

The API is versioned in the path (/api/public/v1). Within v1 we only add things: new fields, new optional parameters, new error codes. Write clients that ignore fields they don't know. Changes that would break a client go to /v2, and v1 keeps working for at least 12 months after v2 is announced.

While integrations are in beta we avoid breaking changes. If one becomes unavoidable, it is listed in the changelog below before it ships.

Changelog

  • 2026-10-05 – MCP: direct editing of the draft (get_proposal_content, edit_proposal_content, update_proposal_settings, version history, discard changes, unpublish). REST: PATCH /proposals/{id} also takes client_name, client_website, service_type, next_step and pricing.
  • 2026-09-26 – v1 beta: brands, proposals (create, read, list, answers, revise, update, publish, unpublish, mark as sent), MCP server with OAuth.

Start today

Send your next proposal as a page your client can say yes to

Paste the notes, review the draft, send one link.

Free for 14 days · 3 proposals included · No card needed