> ## Documentation Index
> Fetch the complete documentation index at: https://docs.onboardme.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Replace proposal details

> Replace a proposal's details section.

export function DensityStyles() {
  return <style>{`#page-title{font-size:1.5rem!important;line-height:1.25!important;letter-spacing:-.02em;margin-bottom:.4rem!important}#content-area{font-size:.9375rem;line-height:1.55}#content-area p{margin-top:.55em;margin-bottom:.55em}#api-playground-input,#request-example,#response-example{font-size:.8125rem}api-section-heading-title{font-size:1.5rem!important;line-height:1.25!important}api-section-heading{margin-bottom:.5rem}field{padding-top:.35rem;padding-bottom:.35rem;font-size:.875rem}tryit-button{font-size:.8rem}`}</style>;
}

<DensityStyles />


## OpenAPI

````yaml PUT /api/v1/proposals/{key}/details
openapi: 3.0.4
info:
  title: OnboardMe External API
  description: "Partner integration API for OnboardMe. All routes are under `/api/v1/…` on your **regional** API host.\r\n\r\n---\r\n\r\n## Regional base URLs\r\n\r\nUse the host that matches where the practice is hosted (a practice is tied to one region).\r\n\r\n| Region | Base URL |\r\n| ------ | -------- |\r\n| Australia & New Zealand | `https://anzapi.onboardme.app` |\r\n| United Kingdom | `https://ukapi.onboardme.app` |\r\n| South Africa | `https://zaapi.onboardme.app` |\r\n\r\nIn your HTTP client, pick the matching **Server** / base URL (or set `baseUrl` to one of the URLs above).\r\n\r\nExample: `GET https://ukapi.onboardme.app/api/v1/auth/validate`\r\n\r\n---\r\n\r\n## Authentication\r\n\r\n**Production integrations (recommended):** send both headers on every request:\r\n\r\n- **`X-OM-Auth-ID`** — Client ID (GUID from OnboardMe practice settings)\r\n- **`X-OM-Auth-Key`** — Client secret paired with that Client ID\r\n\r\n**Postman / Swagger Try it out:** use **Authorize** (HTTP Basic) with **Client ID** as the username and **Client secret** as the password. In Postman, set Basic Auth **once on the collection** so every request inherits it — you do not need auth on each request or two separate header fields.\r\n\r\nHTTPS only. Credentials are scoped to one practice; this API does not use end-user passwords.\r\n\r\n---\r\n\r\n## Partner onboarding\r\n\r\n1. Obtain **Client ID** and **secret** from the practice administrator (OnboardMe).\r\n2. Select the **correct regional base URL** (ANZ, UK, or ZA); practices do not span regions.\r\n3. Call `GET /api/v1/auth/validate` to confirm access and read `canWrite` before using any write endpoint.\r\n4. Run an initial **full** import, then use `lastUpdated` on list endpoints for **incremental** sync.\r\n5. On **429 Too Many Requests**, honour `Retry-After` and backoff; avoid polling faster than you need.\r\n\r\n---\r\n\r\n## Pagination and sync\r\n\r\nList endpoints support optional `pageNumber` (1-based, up to 10000) and `lastUpdated` (UTC). Use `lastUpdated` for incremental sync: only rows changed on or after that instant. `lastUpdated` must not be in the future.\r\n\r\n---\r\n\r\n## Rate limits\r\n\r\nPer Client ID (default: 60/minute, 1000/hour, 10000/day). **429** responses include JSON details and headers: `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`.\r\n\r\n---\r\n\r\n## Write access\r\n\r\nCreating or updating data requires `canWrite: true` from `/api/v1/auth/validate`. Read-only clients get **403** on write routes.\r\n\r\nLead endpoints under **`/api/v1/leads`** support listing, detail, notes, create, and **convert-to-client** (promote a lead to an Active client).\r\n\r\n**Contacts** (`/api/v1/contacts/list` and `/api/v1/entities/{entityKey}/contacts`) expose people linked to clients — list, create, and update.\r\n\r\n**ID verifications** — **`POST /api/v1/id-verifications/send`** starts identity verification (optionally with AML via **`includeAml`**). **`POST /api/v1/id-verifications/aml-checks`** runs a standalone **standard** or **extensive** AML screening on an existing client (`entityKey`). Download a verification certificate PDF with **`GET /api/v1/id-verifications/entities/{entityKey}/certificate`**.\r\n\r\n**Onboarding documents** — **`GET /api/v1/onboardings/{key}/documents`** lists requirements per client entity (`isUploaded` / `isBypassed`). Download one file via a short-lived signed S3 URL: **`GET …/entities/{entityOnboardingId}/documents/{documentId}`**. Download all uploaded files as a zip: **`GET …/entities/{entityOnboardingId}/documents/zip`**.\r\n\r\n**Bills** — **`GET /api/v1/bills/list`** lists invoices by **bill date** range (`startDate` / `endDate`). **`GET /api/v1/bills/{billingId}`** returns detail including ledger fields (`externalId`, `externalNumber`, `externalUrl`). **`GET /api/v1/bills/{billingId}/pdf`** downloads the OnboardMe invoice PDF. **`POST /api/v1/bills`** creates an **adhoc** bill for an Active client entity only (no engagement / onboarding entity link; requires `canWrite`; does **not** push to Xero — stores `sendBill` / `syncNow`). When Xero is connected, lines need a revenue account (`extAccountID`) or sale item plus tax (`extTaxID`); use **`GET /api/v1/bills/ledger/status|accounts|tax-rates|branding-themes`**.\r\n\r\n**Proposals** — List a client's proposals with **`GET /api/v1/proposals/list?entityKey=`**. Load previous **services, invoices, recurring billings, and jobs** via **`GET /api/v1/proposals/{key}/pricing`** (or full **`GET /api/v1/proposals/{key}`**). **`POST /api/v1/proposals/{key}/duplicate`** copies pricing, billings, and jobs into a new draft. **`POST /api/v1/proposals/{key}/renew`** does the same and marks the original Renewed (**`GET …/renew-preview`** first for signatory choice). **`POST /api/v1/proposals/from-template`** creates from a saved template. Edit an existing proposal's services with **`PATCH /api/v1/proposals/{key}/services`** (line edits) or **`PUT /api/v1/proposals/{key}/services`** (replace the list).**`POST /api/v1/proposals`** creates an **adhoc** proposal with caller-supplied **services**, optional **XPM jobs**, **FYI jobs**, and **billings**. Catalogs: **`GET /api/v1/proposals/services`**, **`…/xpm-job-templates`**, **`…/xpm-job-categories`**, **`…/xpm-staff`**, **`…/fyi-job-templates`**, **`…/fyi-job-states`**.\r\n\r\n**Recurring billing** — **`GET /api/v1/recurring-billing/list`**, **`GET /api/v1/recurring-billing/{recurringBillingId}`**, and **`GET …/runs`** for schedule list, detail, and generated-run history. Ledger invoice numbers/URLs appear on generated bills, not on the schedule itself.\r\n\r\n**Payments** — **`GET /api/v1/payments/list`** lists payments by payment-date range, filterable by `paymentStatus` (`processing`, `completed`, `refunded`, `failed`), `paymentMethod` (`card`, `bank`, `external`, `xero`, `quickbooks`) and `entityKey`. **`GET /api/v1/payments/{paymentId}`** loads one payment (including refund details); **`GET /api/v1/bills/{billingId}/payments`** lists every payment against a bill. Saved payment sources (card or bank account details) are never returned.\r\n\r\n**Debtors** — **`GET /api/v1/debtors/ageing`** returns outstanding receivables as at a date (practice totals plus per-client buckets: current, 1–30, 31–60, 61–90, 90+ days overdue). **`GET /api/v1/debtors/invoices`** lists outstanding invoices with days overdue, collection status and payment-plan flags. **`GET /api/v1/debtors/movement`** reconciles opening → billed → paid → closing balance over a period.\r\n\r\n**Statements** — **`GET /api/v1/statements/list?entityKey=`** lists statements sent to a client; **`GET /api/v1/statements/{statementId}`** returns the statement with its lines; **`GET /api/v1/statements/{statementId}/pdf`** downloads the statement PDF.\r\n\r\n---\r\n\r\n## Outbound webhooks\r\n\r\nRegister a subscriber URL with **`POST /api/v1/webhooks`** (requires `canWrite`). Supported event keys are listed at **`GET /api/v1/webhooks/events`** (e.g. `proposal.accepted`, `eform.submitted`). One subscription per event; repeat subscribe with the same URL for multiple events.\r\n\r\n**Subscribe response (`201`):** `{ \"id\", \"webhookSecret\" }` — store `webhookSecret` immediately; it is **not** returned again on list/load.\r\n\r\n**Deliveries:** OnboardMe `POST`s JSON to your URL. Each request includes:\r\n\r\n| Header | Description |\r\n| ------ | ------------- |\r\n| `X-Om-Event` | Event key (e.g. `proposal.accepted`) |\r\n| `X-Om-Event-Id` | Unique idempotency key for this event |\r\n| `X-Om-Timestamp` | Unix time (seconds, UTC) used when signing |\r\n| `X-Om-Signature` | `sha256=<hex>` HMAC (see below) |\r\n\r\n**Signature verification (recommended on your server):**\r\n\r\n1. Read the **raw** request body bytes (do not re-serialize JSON).\r\n2. Build `canonical = {X-Om-Timestamp} + \".\" + {rawBody}`.\r\n3. Compute `expected = \"sha256=\" + HMAC_SHA256_UTF8(webhookSecret, canonical)` (hex **lowercase**).\r\n4. Compare `expected` to `X-Om-Signature` using a constant-time comparison; reject on mismatch.\r\n\r\nThe delivery JSON schema is documented as **`ApiWebhookDeliveryEventDTO`** in Schemas (see **Webhooks** operations). Test deliveries: **`POST /api/v1/webhooks/{id}/test`**.\r\n\r\n---\r\n\r\n## Errors\r\n\r\n- **400** — validation (`message` in body).\r\n- **401** — bad or missing credentials.\r\n- **404** — resource not in the practice (`message`).\r\n\r\nSchemas below include **illustrative** example values; live responses use your data."
  contact:
    name: OnboardMe Support
    email: support@onboardme.app
  version: v1
servers:
  - url: https://anzapi.onboardme.app
    description: Australia & New Zealand — ANZ-hosted practices
  - url: https://ukapi.onboardme.app
    description: United Kingdom — UK-hosted practices
  - url: https://zaapi.onboardme.app
    description: South Africa — ZA-hosted practices
security:
  - ApiClientCredentials: []
paths:
  /api/v1/proposals/{key}/details:
    put:
      tags:
        - Proposal updates
      summary: Replace proposal details
      description: "Replaces the proposal's settings: `name` (required), `startDate`, `endDate`, `startOnAcceptance`, `lengthID`, `currencyID`, `expiryDate`, `terms` + `termsDetailsOverride`, `stpID` + `stpDetailsOverride`, `documents` (up to 4), `attachmentID`, `coverPageID`, `note` or `welcomeMessageID`, `emailSubject` + `emailContent`, `packages`, `popularPackage`, `showServiceMenu`, `serviceGrouping`, `sendSms`, `requestPaymentDetails` + `paymentDetailsType`, `businessSignatoryUserKey`, `requiresApproval` + `approverUserKey`, `includeCdd`.\r\n\r\n**Fields you do not send go back to their defaults** (practice default terms, no documents, default email, length 12 months, and so on), exactly as on create. `payerID` only changes when sent. Use PATCH to change a few fields.\r\n**Rules for every update route**\r\n\r\n- **`userKey`** — staff member making the change (**GET `/api/v1/users/list`**).\r\n- Field names, lookups and validation are the same as **POST `/api/v1/proposals`**.\r\n- Accepted, completed, due-for-renewal and renewed proposals cannot be edited.\r\n- Editing a proposal that has already been sent changes what the client sees.\r\n- Items already pushed to another system (XPM, FYI, Karbon, GreatSoft, FreeAgent), signed signatories, and invoices that are synced, paid or cancelled cannot be changed or removed. In a PUT, include them as `{ \"id\": … }` to keep them.\r\n- **Response**: `pricing` (services, invoices, recurring billing, jobs) and `section` (this step as saved, with ids).\r\n\r\nWrite access is required. **429** — rate limit exceeded."
      parameters:
        - name: key
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: >-
                #/components/schemas/Application.APIData.DataObjects.ProposalDetailsUpdateRequestDTO
          text/json:
            schema:
              $ref: >-
                #/components/schemas/Application.APIData.DataObjects.ProposalDetailsUpdateRequestDTO
          application/*+json:
            schema:
              $ref: >-
                #/components/schemas/Application.APIData.DataObjects.ProposalDetailsUpdateRequestDTO
      responses:
        '200':
          description: OK
          content:
            text/plain:
              schema:
                $ref: >-
                  #/components/schemas/Application.APIData.DataObjects.ProposalUpdateResponseDTO
            application/json:
              schema:
                $ref: >-
                  #/components/schemas/Application.APIData.DataObjects.ProposalUpdateResponseDTO
            text/json:
              schema:
                $ref: >-
                  #/components/schemas/Application.APIData.DataObjects.ProposalUpdateResponseDTO
        '400':
          description: Bad Request
          content:
            text/plain:
              schema:
                $ref: '#/components/schemas/Microsoft.AspNetCore.Mvc.ProblemDetails'
            application/json:
              schema:
                $ref: '#/components/schemas/Microsoft.AspNetCore.Mvc.ProblemDetails'
            text/json:
              schema:
                $ref: '#/components/schemas/Microsoft.AspNetCore.Mvc.ProblemDetails'
        '404':
          description: Not Found
          content:
            text/plain:
              schema:
                $ref: '#/components/schemas/Microsoft.AspNetCore.Mvc.ProblemDetails'
            application/json:
              schema:
                $ref: '#/components/schemas/Microsoft.AspNetCore.Mvc.ProblemDetails'
            text/json:
              schema:
                $ref: '#/components/schemas/Microsoft.AspNetCore.Mvc.ProblemDetails'
components:
  schemas:
    Application.APIData.DataObjects.ProposalDetailsUpdateRequestDTO:
      type: object
      properties:
        userKey:
          type: string
          format: uuid
        name:
          type: string
          nullable: true
        note:
          type: string
          nullable: true
        welcomeMessageID:
          type: integer
          format: int32
          nullable: true
        startDate:
          type: string
          format: date-time
          nullable: true
        endDate:
          type: string
          format: date-time
          nullable: true
        startOnAcceptance:
          type: boolean
          nullable: true
        currencyID:
          type: integer
          format: int32
          nullable: true
        lengthID:
          type: integer
          format: int32
          nullable: true
        expiryDate:
          type: string
          format: date-time
          nullable: true
        terms:
          type: integer
          format: int32
          nullable: true
        termsDetailsOverride:
          type: string
          nullable: true
        stpID:
          type: integer
          format: int32
          nullable: true
        stpDetailsOverride:
          type: string
          nullable: true
        documents:
          type: array
          items:
            $ref: >-
              #/components/schemas/Application.APIData.DataObjects.ProposalCreateDocumentDTO
          nullable: true
        attachmentID:
          type: integer
          format: int32
          nullable: true
        coverPageID:
          type: integer
          format: int32
          nullable: true
        emailSubject:
          type: string
          nullable: true
        emailContent:
          type: string
          nullable: true
        packages:
          type: array
          items:
            $ref: >-
              #/components/schemas/Application.APIData.DataObjects.ProposalCreatePackageDTO
          nullable: true
        popularPackage:
          type: integer
          format: int32
          nullable: true
        showServiceMenu:
          type: boolean
          nullable: true
        serviceGrouping:
          type: integer
          format: int32
          nullable: true
        sendSms:
          type: boolean
          nullable: true
        requestPaymentDetails:
          type: boolean
          nullable: true
        paymentDetailsType:
          type: integer
          format: int32
          nullable: true
        payerID:
          type: string
          nullable: true
        businessSignatoryUserKey:
          type: string
          format: uuid
          nullable: true
        requiresApproval:
          type: boolean
          nullable: true
        approverUserKey:
          type: string
          format: uuid
          nullable: true
        includeCdd:
          type: boolean
          nullable: true
      additionalProperties: false
    Application.APIData.DataObjects.ProposalUpdateResponseDTO:
      type: object
      properties:
        engagementKey:
          type: string
          format: uuid
        statusName:
          type: string
          nullable: true
        billingNeedsReview:
          type: boolean
        pricing:
          $ref: >-
            #/components/schemas/Application.APIData.DataObjects.ProposalPricingSnapshotDTO
        section:
          nullable: true
      additionalProperties: false
    Microsoft.AspNetCore.Mvc.ProblemDetails:
      type: object
      properties:
        type:
          type: string
          nullable: true
        title:
          type: string
          nullable: true
        status:
          type: integer
          format: int32
          nullable: true
        detail:
          type: string
          nullable: true
        instance:
          type: string
          nullable: true
      additionalProperties: {}
    Application.APIData.DataObjects.ProposalCreateDocumentDTO:
      type: object
      properties:
        documentID:
          type: integer
          format: int64
        detailsOverride:
          type: string
          nullable: true
      additionalProperties: false
    Application.APIData.DataObjects.ProposalCreatePackageDTO:
      type: object
      properties:
        packageNumber:
          type: integer
          format: int32
        name:
          type: string
          nullable: true
        price:
          type: number
          format: double
          nullable: true
        text:
          type: string
          nullable: true
      additionalProperties: false
    Application.APIData.DataObjects.ProposalPricingSnapshotDTO:
      type: object
      properties:
        engagementKey:
          type: string
          format: uuid
        name:
          type: string
          nullable: true
        statusID:
          type: integer
          format: int32
        statusName:
          type: string
          nullable: true
        startDate:
          type: string
          format: date-time
        endDate:
          type: string
          format: date-time
          nullable: true
        currencyID:
          type: integer
          format: int32
        currencyName:
          type: string
          nullable: true
        createBills:
          type: boolean
        recurringBilling:
          type: integer
          format: int32
        createXpmJobs:
          type: boolean
        createFyiJobs:
          type: boolean
        servicesTotal:
          type: number
          format: double
        invoicesTotal:
          type: number
          format: double
        services:
          type: array
          items:
            $ref: >-
              #/components/schemas/Application.APIData.DataObjects.ProposalPricingServiceDTO
          nullable: true
        invoices:
          type: array
          items:
            $ref: >-
              #/components/schemas/Application.APIData.DataObjects.ProposalPricingInvoiceDTO
          nullable: true
        recurringBillings:
          type: array
          items:
            $ref: >-
              #/components/schemas/Application.APIData.DataObjects.ProposalPricingRecurringDTO
          nullable: true
        xpmJobs:
          type: array
          items:
            $ref: >-
              #/components/schemas/Application.APIData.DataObjects.ProposalPricingXpmJobDTO
          nullable: true
        fyiJobs:
          type: array
          items:
            $ref: >-
              #/components/schemas/Application.APIData.DataObjects.ProposalPricingFyiJobDTO
          nullable: true
      additionalProperties: false
      example:
        engagementKey: 8f9e2b1c-3d4a-5e6f-7890-abcdef012345
        name: FY25 tax engagement – Acme Pty Ltd
        statusName: Accepted
        createBills: true
        servicesTotal: 2695
        invoicesTotal: 2695
        services:
          - serviceID: 12040
            serviceName: Annual compliance — company
            price: 2450
            quantity: 1
            taxAmount: 245
            lineTotal: 2695
    Application.APIData.DataObjects.ProposalPricingServiceDTO:
      type: object
      properties:
        id:
          type: integer
          format: int32
        serviceID:
          type: integer
          format: int32
        parentServiceID:
          type: integer
          format: int32
          nullable: true
        serviceName:
          type: string
          nullable: true
        serviceDetail:
          type: string
          nullable: true
        price:
          type: number
          format: double
        quantity:
          type: number
          format: double
          nullable: true
        taxAmount:
          type: number
          format: double
        discountPerc:
          type: number
          format: double
        discountAmount:
          type: number
          format: double
        packageNumber:
          type: integer
          format: int32
        pricingTypeName:
          type: string
          nullable: true
        frequency:
          type: string
          nullable: true
        isOptional:
          type: boolean
        lineTotal:
          type: number
          format: double
      additionalProperties: false
    Application.APIData.DataObjects.ProposalPricingInvoiceDTO:
      type: object
      properties:
        billingID:
          type: integer
          format: int32
        billDate:
          type: string
          format: date
        dueDate:
          type: string
          format: date
        billDetails:
          type: string
          nullable: true
        refNo:
          type: string
          nullable: true
        billNow:
          type: integer
          format: int32
        syncNow:
          type: boolean
        sendBill:
          type: boolean
        billAmount:
          type: number
          format: double
        billTax:
          type: number
          format: double
        billTotal:
          type: number
          format: double
        packageNumber:
          type: integer
          format: int32
          nullable: true
        lineItems:
          type: array
          items:
            $ref: >-
              #/components/schemas/Application.APIData.DataObjects.ProposalPricingInvoiceLineDTO
          nullable: true
      additionalProperties: false
    Application.APIData.DataObjects.ProposalPricingRecurringDTO:
      type: object
      properties:
        recurringBillingID:
          type: integer
          format: int64
        frequency:
          type: string
          nullable: true
        startDate:
          type: string
          format: date
        endDate:
          type: string
          format: date
          nullable: true
        nextRunDate:
          type: string
          format: date
        billAmount:
          type: number
          format: double
        billTax:
          type: number
          format: double
        billTotal:
          type: number
          format: double
        billDetails:
          type: string
          nullable: true
        refNo:
          type: string
          nullable: true
        dueDays:
          type: integer
          format: int32
        isActive:
          type: boolean
        billNow:
          type: integer
          format: int32
        lineItems:
          type: array
          items:
            $ref: >-
              #/components/schemas/Application.APIData.DataObjects.ProposalPricingInvoiceLineDTO
          nullable: true
      additionalProperties: false
    Application.APIData.DataObjects.ProposalPricingXpmJobDTO:
      type: object
      properties:
        name:
          type: string
          nullable: true
        templateUuid:
          type: string
          format: uuid
          nullable: true
        categoryId:
          type: string
          format: uuid
          nullable: true
        startDate:
          type: string
          format: date-time
        dueDate:
          type: string
          format: date-time
        description:
          type: string
          nullable: true
        refNo:
          type: string
          nullable: true
        budget:
          type: number
          format: double
        partnerUuid:
          type: string
          format: uuid
          nullable: true
        managerUuid:
          type: string
          format: uuid
          nullable: true
      additionalProperties: false
    Application.APIData.DataObjects.ProposalPricingFyiJobDTO:
      type: object
      properties:
        jobName:
          type: string
          nullable: true
        fyiTemplateId:
          type: string
          nullable: true
        state:
          type: string
          nullable: true
        type:
          type: string
          nullable: true
        startDate:
          type: string
          format: date-time
          nullable: true
        dueDate:
          type: string
          format: date-time
          nullable: true
        managerEmail:
          type: string
          nullable: true
        partnerEmail:
          type: string
          nullable: true
        sourceID:
          type: string
          nullable: true
      additionalProperties: false
    Application.APIData.DataObjects.ProposalPricingInvoiceLineDTO:
      type: object
      properties:
        amount:
          type: number
          format: double
        taxAmount:
          type: number
          format: double
        total:
          type: number
          format: double
        narration:
          type: string
          nullable: true
        extAccountID:
          type: integer
          format: int64
          nullable: true
        extItemID:
          type: integer
          format: int32
          nullable: true
        extTaxID:
          type: integer
          format: int32
          nullable: true
        serviceID:
          type: integer
          format: int32
          nullable: true
        orderID:
          type: integer
          format: int32
      additionalProperties: false
  securitySchemes:
    ApiClientCredentials:
      type: http
      description: >-
        Swagger / Postman only: **Client ID** as username, **Client secret** as
        password. In Postman, set this once on the **collection** (Authorization
        → Basic Auth) so all requests inherit. For production server-to-server
        code, prefer **`X-OM-Auth-ID`** and **`X-OM-Auth-Key`** headers.
      scheme: basic

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.