Skip to main content
A proposal is an engagement. The id you store is engagementKey. Every later get, PDF, duplicate, and renew call uses that key. There are two ways to create one. Both need canWrite: true, a userKey from GET /api/v1/users/list, and an entityKey for an existing client. Leads are not proposal clients. Convert them first. See Leads and deals.

Send versus draft

sendToClient is on every create, duplicate, and renew.
  • false saves a draft. Nothing is emailed. You can open it in OnboardMe and send later.
  • true sends after the record is saved, or submits it for internal approval when the practice requires approval.
If sending fails, the proposal is left as a draft. The response still includes engagementKey, and sentToClient is false. Store the key, tell your user it was not sent, and do not create a second proposal to “retry” until you know why the first send failed.

From a template

GET /api/v1/proposals/templates returns the practice templates. Use templateId from that list.
The response is engagementKey. Load GET /api/v1/proposals/{key} if you need the services and prices that the template copied in.

Built without a template

POST /api/v1/proposals requires at least one service. Look up serviceID from GET /api/v1/proposals/services. Optional overrides on a line are quantity, price, taxAmount, packageNumber, isOptional, discountPerc, discountAmount, and serviceDetail.
Add only the blocks you need. Each one has its own lookup. One-off invoices (invoices). These are proposal billings, not POST /api/v1/bills. Each invoice needs billDate, dueDate, and lineItems with amount, narration, and either taxAmount or taxPercent. billNow is 0 (neither), 1 (upfront), or 2 (on completion). Optional ledger ids are extAccountID, extItemID, and extTaxID. Recurring schedules (recurringBillings). Each needs frequency (daily, weekly, fortnightly, monthly, quarterly, annually), startDate, and lineItems in the same shape. nextRunDate defaults to startDate. billNow here is 0 or 2 only. XPM jobs (xpmJobs). XPM must be connected. Each job needs name and templateUuid from GET /api/v1/proposals/xpm-job-templates. Optional: categoryId from the categories list, partnerUuid and managerUuid from GET /api/v1/proposals/xpm-staff, dates, description, refNo, budget. FYI jobs (fyiJobs). FYI Elite must be connected. Each job needs jobName, managerEmail, and partnerEmail. Optional fyiTemplateId and state come from the FYI template and state lists. Payment details. requestPaymentDetails: true collects a payment method on acceptance. paymentDetailsType is 0 all (default), 1 card, or 2 bank account. Identity verification. Set identityVerification: true only when you are not also sending an onboarding block. Then verificationMethod is required: Basic, Standard, Enhanced, Comprehensive, Annature Identity Only, or Annature Identity + AML. Individuals and sole traders only. Extra people go in identityPeople (existing entityKey, or name plus surname). Onboarding after acceptance. The onboarding object uses the same client rows as Onboarding jobs. It is created to send after the client accepts, not immediately. If clients is empty, the proposal client is added for you. Put identity verification on onboarding.clients when this block is present. Optional header fields: note, startDate, endDate, startOnAcceptance, currencyID, lengthID, terms, refNo, expiryDate, sendSms.

Read, price, and PDF

  • GET /api/v1/proposals/list pages with pageNumber. Filter with entityKey, searchFilter, statusID, userID, or lastUpdated (UTC). excludeCompleted=true drops finished proposals. Use lastUpdated the same way as the client sync watermark.
  • GET /api/v1/proposals/{key} is one proposal: services, billings, jobs, and status.
  • GET /api/v1/proposals/{key}/pricing is the calculated totals. Use this when you display a price, rather than summing line items yourself.
  • GET /api/v1/proposals/{key}/pdf returns the PDF the client would see.

Duplicate

POST /api/v1/proposals/{key}/duplicate copies services and prices, invoices, recurring schedules, XPM and FYI jobs, packages, terms, and clients into a new proposal. The original is unchanged. sourceMarkedRenewed is always false.
Store the new engagementKey. sourceEngagementKey is the one you copied.

Renew

Renew is duplicate plus marking the original Renewed. Preview the signatories first.
  1. GET /api/v1/proposals/{key}/renew-preview compares the proposal’s signatories with the client’s current contacts. When signatoriesDiffer is true, decide which list the renewal should use.
  2. POST /api/v1/proposals/{key}/renew with userKey, sendToClient, and signatorySource.
signatorySource is Proposal (default, keep the previous signatories) or ClientContacts (use the live contact list). The response matches duplicate, except sourceMarkedRenewed is true when the original was marked Renewed.

When the client accepts

Subscribe to proposal.accepted. It fires when every required signature is complete, not when the proposal is sent. Verify the signature and dedupe on X-Om-Event-Id. See Webhooks.

Proposal reference

Create, from-template, duplicate, renew, pricing, PDF, and the service and job lookups.