A proposal is one API call from your CRM.

You wire the trigger and we design the proposal, then signed callbacks tell your systems what happened.

Request a proposalIllustrative values
POST /api/decks/proposals/requests
Authorization: Bearer $INTAKE_KEY

{
  "request_id": "crm:deal-20931:renewal",
  "source": "crm",
  "template": "group-renewal",
  "recipient": { "name": "Fernhill Manufacturing" },
  "owner": { "name": "Maya Ortiz", "email": "maya@northgate.example" },
  "viewers": ["cfo@fernhill.example"],
  "callback_url": "https://hooks.northgate.example/proposals",
  "inputs": { "census": { "eligible": 412, "locations": 3 } }
}
202 Accepted

{
  "job_id": "8f3c1e2a-5b7d-4c19",
  "request_id": "crm:deal-20931:renewal",
  "status": "queued",
  "version": null,
  "live_version": null
}

// The same request_id again
// returns this job, not a new one.

Signed callbacks

Every status change arrives at your endpoint, signed.

  • HMAC-SHA256 over the raw body
  • Retried with backoff, resendable from the log
  • Ids, versions and links, never the inputs
Callback to your endpointIllustrative values
POST https://hooks.northgate.example/proposals
Unfairly-Signature: t=1792591572,v2=5f1c0a…e94a

{
  "type": "proposal.in_review",
  "job_id": "8f3c1e2a-5b7d-4c19",
  "request_id": "crm:deal-20931:renewal",
  "status": "in_review",
  "version": 1,
  "live_version": null,
  "review_url": "https://…/fernhill?review",
  "occurred_at": "2026-10-21T14:06:12Z"
}

The review_url

What your team opens about a minute later is a finished proposal.

  • Inputs checked against the template's JSON Schema
  • A dry run checks everything and builds nothing
  • Promote by API, or approve on the page

Engagement events

Opens, new readers and clicks post back while the buyer is still reading.

  • Verified readers arrive by email
  • Colleagues they add arrive as new viewers
  • A click is reported as a click, nothing more

EventsFernhill renewal

proposal.promotedlive_version 2
proposal.vieweddana@fernhill.example, verified
proposal.viewer_addedadded by dana@fernhill.example
proposal.cta_clickedcfo@fernhill.example, book_meeting

MCP server

Reps can request and revise proposals from Claude or ChatGPT.

Draft the Fernhill renewal from today's call notes, and make sure their CFO can read it.

proposal_request · checks passed

The draft is ready to review. Comments go right on the page, and nothing reaches Fernhill until someone on your team approves it.

What GTM engineers ask us.

Something else? Email bob@unfairly.ai.

How do requests authenticate?

With your workspace's intake key as a bearer token. Callbacks are signed with a separate signing secret, so a leaked intake key can't forge an event, and a new intake key overlaps the old one so you can rotate without downtime.

What goes in the inputs?

Whatever your template needs, like headcount by location or the renewal date. Each template publishes its inputs as a JSON Schema, requests are checked against it before anything is built, and fields that identify a person are refused.

Does it work with our CRM?

If your CRM can send an HTTP request on a stage change and receive a webhook, yes. The API is published as OpenAPI 3.1, and we'll walk through the wiring with you on a technical call.

Can a system promote a version without a person?

Yes. A headless integration can review from the in-review callback and promote that version with the intake key. Most teams keep a person in the loop for high-stakes deals.

Can we withdraw a proposal?

Yes. Withdrawing takes the client's link down for good and sends one withdrawn event to your endpoint.

Wire one deal stage to a proposal and watch the callbacks arrive.

Book a technical call and we'll set up a workspace, an intake key and a test endpoint with you.