{
  "openapi": "3.0.3",
  "info": {
    "title": "AdvisoryAI API",
    "version": "1.0.0",
    "contact": {
      "name": "AdvisoryAI Support",
      "email": "support@advisoryai.com"
    },
    "description": "The AdvisoryAI API gives approved partners access to an AdvisoryAI\norganisation: its clients and their households, meetings and transcripts, documents,\nreports and report templates, its users, the organisation itself, and the CRM data\nAdvisoryAI holds for its clients from PlannrCRM, Intelliflo, Curo, Curo Power Platform\nand Iress Xplan. The reference also shows how each CRM field maps onto the AdvisoryAI\ndata model.\n\nThe API is read-only with one exception: you can\n[request a report](#operation/createReportGeneration) from a template and your own\nsource documents, then download it when it's ready.\n\nAccess is by request and credentials are only issued to approved partners. See\n[Getting access](#section/Getting-access).\n\n# Getting access\n\nAccess to the AdvisoryAI API is by request. Email\n**[support@advisoryai.com](mailto:support@advisoryai.com?subject=AdvisoryAI%20API%20access%20request)**\nwith:\n\n- your company name and, where relevant, your FCA firm reference number\n- the AdvisoryAI organisation you want to connect (if you are already a customer)\n- a short description of your use case and the CRM(s) involved (for example PlannrCRM)\n- a technical contact for the integration\n\nRequests must come from a user of the AdvisoryAI organisation. Access to organisation\ndetails (the `organisation:read` scope) is only granted when the request comes from an\norganisation **admin**. Say in your request if you need to generate reports (the\n`reports:write` scope). It isn't included by default.\n\nOnce approved, we issue a `client_id` and `client_secret` for a sandbox organisation,\nfollowed by production credentials when your integration is ready. Keep the secret on\nyour server. Never ship it in a browser or mobile app.\n\n# Environments\n\n| Environment | Base URL |\n|---|---|\n| Production | `https://api.advisoryai.com/v1` |\n| Sandbox | `https://sandbox-api.advisoryai.com/v1` |\n\nSandbox organisations contain synthetic clients linked to a PlannrCRM demo account.\n\n# Conventions\n\n- The API is read-only, except for [report generation](#operation/createReportGeneration)\n  (`POST /report-generations`), which can also upload source documents to the client.\n  Nothing is ever written to a CRM.\n- Responses are JSON (`Content-Type: application/json`) encoded in UTF-8.\n- Field names are `snake_case`.\n- Dates are ISO 8601 calendar dates (`2026-04-06`). Timestamps are ISO 8601 in UTC\n  (`2026-11-02T09:30:00Z`).\n- Money is a decimal number in pounds sterling (GBP). PlannrCRM stores amounts in\n  pennies, and AdvisoryAI converts them to pounds for you.\n- Identifiers are opaque strings (for example `21GYLb9n`). Don't parse them.\n- File links (`file_url`, `pdf_url`, `docx_url`) are signed URLs that expire after\n  **1 hour**. Fetch the resource again for a fresh link instead of storing it.\n- Values that aren't recorded are `null`. They are never omitted.\n\n# Pagination\n\nList endpoints are page-based. Pass `page` (1-based) and `per_page` (default `25`,\nmax `100`). Every list response includes a `meta` object:\n\n```json\n{ \"current_page\": 2, \"next_page\": 3, \"prev_page\": 1, \"total_pages\": 8, \"total_count\": 187 }\n```\n\n# Errors\n\nAdvisoryAI uses conventional HTTP status codes. Error bodies always have the same shape:\n\n```json\n{\n  \"error\": \"not_found\",\n  \"message\": \"Client not found.\",\n  \"details\": null\n}\n```\n\n| Status | `error` | Meaning |\n|---|---|---|\n| 400 | `bad_request` | A query parameter is invalid. |\n| 401 | `unauthorized` | The access token is missing, expired or revoked. |\n| 403 | `forbidden` | The token lacks the required scope, for example `organisation:read` on credentials not issued to an admin. |\n| 404 | `not_found` | The resource doesn't exist in your organisation. |\n| 409 | `crm_not_linked` | The client isn't linked to that CRM. |\n| 413 | `payload_too_large` | An uploaded file, or the whole request, is over the size limit. |\n| 415 | `unsupported_media_type` | An uploaded file's type isn't supported. |\n| 422 | `validation_failed` | The request is well formed but can't be processed, for example a report request with no sources. |\n| 429 | `rate_limited` | Too many requests. Retry after the `Retry-After` header. |\n\n# Rate limits\n\nEach set of credentials gets a request budget, which we confirm when we issue them.\nResponses include `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`.\nWhen you exceed the budget the API answers `429` with a `Retry-After` header in seconds.\n\n# Versioning\n\nThe major version is part of the URL (`/v1`). Additive changes (new endpoints, new\nfields, new enum values) can ship without notice, so make your client tolerate unknown\nfields. We announce breaking changes to registered technical contacts at least 90 days\nahead.\n"
  },
  "servers": [
    {
      "url": "https://api.advisoryai.com/v1",
      "description": "Production"
    },
    {
      "url": "https://sandbox-api.advisoryai.com/v1",
      "description": "Sandbox"
    }
  ],
  "security": [
    {
      "oauth2": []
    }
  ],
  "tags": [
    {
      "name": "Authentication",
      "description": "The API uses **OAuth 2.0 with the client credentials grant**. Exchange your\n`client_id` and `client_secret` for a short-lived bearer token, then send it on every\nrequest:\n\n```\nAuthorization: Bearer eyJhbGciOiJIUzI1NiJ9...\n```\n\n- Access tokens are signed JWTs and expire after **1 hour** (`expires_in: 3600`).\n  Request a new one when the current token expires. Client credentials tokens don't\n  come with a refresh token.\n- A token is scoped to the single AdvisoryAI organisation your credentials were\n  issued for. It can never read another organisation's data.\n- Revoke a token as soon as you stop using it, for example when you rotate secrets.\n\n### Scopes\n\nRequest only the scopes you need, separated by spaces. If you omit `scope`, the token\ngets every scope your credentials allow.\n\n| Scope | Grants |\n|---|---|\n| `clients:read` | List and retrieve clients, their relationships and their activity. |\n| `meetings:read` | List and retrieve meetings. |\n| `transcripts:read` | Retrieve meeting transcripts, adviser notes and recording links. |\n| `documents:read` | List and retrieve documents, including their extracted text and file links. |\n| `reports:read` | List and retrieve reports, including their content and file links, and check report generation status. |\n| `reports:write` | Request report generation and upload source documents for it. |\n| `templates:read` | List and retrieve the organisation's report templates. |\n| `users:read` | List and retrieve the organisation's users and their roles. |\n| `organisation:read` | Retrieve the organisation's details. **Admin only:** granted only when an organisation admin requests the credentials. |\n| `crm:read` | Retrieve CRM data (PlannrCRM, Intelliflo, Curo, Curo Power Platform, Xplan) and CRM summaries. |\n"
    },
    {
      "name": "Clients",
      "description": "A **client** is a person an adviser in the organisation looks after.\n\n`crm_links` lists the CRM records the client is linked to. For each link, the\nmatching endpoint in the [CRM integrations](#tag/CRM-overview) returns that CRM's\ndata for the client.\n\n### Households and timeline\n\n- `GET /clients/{client_id}/relationships`: the client's spouse or partner, parents\n  and children, where they're also clients.\n- `GET /clients/{client_id}/activity`: the client's timeline of meetings, reports and\n  changes to their details.\n"
    },
    {
      "name": "Meetings",
      "description": "A **meeting** is a client meeting AdvisoryAI recorded, either from an adviser's Google\nor Outlook calendar or in person through the AdvisoryAI app.\n\nAfter the meeting ends, AdvisoryAI transcribes the recording and the meeting moves\nthrough `ended` and `transcribing` to `completed`. The\n[transcript](#operation/getMeetingTranscript) needs the separate `transcripts:read`\nscope, so a firm can share meeting schedules without sharing what was said.\n\nReports generated from a meeting carry its `meeting_id`.\n"
    },
    {
      "name": "Documents",
      "description": "A **document** is a file attached to a client: an uploaded file, a meeting recording,\nor a file imported from a CRM or SharePoint. AdvisoryAI extracts each document's text\nand uses it as source material for reports.\n\n`type` tells files (`document`) apart from meeting recordings (`audio`). For\nrecordings, `text` holds the transcript.\n"
    },
    {
      "name": "Reports",
      "description": "A **report** is a document AdvisoryAI generates for a client from its documents and\nmeetings, such as a meeting note, suitability letter or annual review.\n\nReports are generated in the background. `status` is `generating` until the report is\nready, then `completed` (or `failed`). The content and file links are only available on\ncompleted reports.\n\n### Generating a report\n\nReport generation takes a few minutes, so it works in two steps:\n\n1. **Request the report.** `POST /report-generations` with the client, a\n   [template](#tag/Templates) and the sources to use: a meeting, documents already on\n   the client and new files to upload. The response is `202 Accepted` with a\n   generation `id`.\n2. **Check its status.** Poll `GET /report-generations/{generation_id}` until\n   `status` is `completed`. The response then holds `report_id` and signed `pdf_url`\n   and `docx_url` links to download the report.\n\n```\nPOST /report-generations           → 202 { \"id\": \"Gn4hJ8kP\", \"status\": \"queued\" }\nGET  /report-generations/Gn4hJ8kP  → 200 { \"status\": \"processing_documents\" }\nGET  /report-generations/Gn4hJ8kP  → 200 { \"status\": \"generating\" }\nGET  /report-generations/Gn4hJ8kP  → 200 { \"status\": \"completed\", \"download\": { … } }\n```\n\nGenerated reports also appear in `GET /reports` and in AdvisoryAI, where the adviser\ncan review and edit them. Uploaded files are added to the client's\n[documents](#tag/Documents) with `source: upload`.\n"
    },
    {
      "name": "Templates",
      "description": "A **template** defines the structure of a report. The organisation's own templates\nand AdvisoryAI's standard templates are both listed. Every report names the template\nit was generated from.\n\nOnly template metadata is available. Template content and generation instructions\naren't returned.\n"
    },
    {
      "name": "Users",
      "description": "**Users** are the people in the organisation who use AdvisoryAI.\n\n### Roles\n\n| Role | Description |\n|---|---|\n| `admin` | Manages the organisation, its users and settings. Also works with clients. |\n| `advisor` | Adviser who looks after clients. |\n| `paraplanner` | Prepares reports and client work for advisers. |\n| `paraplanner_plus` | Paraplanner with access across more of the organisation's advisers. |\n| `supervisor` | Oversees advisers' work. |\n| `support` | Administrative support for advisers. |\n"
    },
    {
      "name": "Organisation",
      "description": "The AdvisoryAI organisation (firm) your credentials belong to.\n\n**Admin only.** This endpoint needs the `organisation:read` scope, which is only\ngranted when an organisation admin requests the credentials. Other tokens get `403`.\n"
    },
    {
      "name": "CRM overview",
      "description": "AdvisoryAI retrieves client data from the CRMs below. Each CRM has its own endpoint\nthat returns that CRM's data with AdvisoryAI field names, and a field mapping page that\nshows where every field comes from.\n\n| CRM | `source` | Connected by |\n|---|---|---|\n| [PlannrCRM](#tag/PlannrCRM) | `plannr` | Each adviser |\n| [Intelliflo Office](#tag/Intelliflo) | `intelliflo` | Each adviser |\n| [Curo](#tag/Curo-and-Xplan) | `curo` | Organisation admin, then each adviser |\n| [Curo Power Platform](#tag/Curo-and-Xplan) | `curo_pp` | Each adviser |\n| [Iress Xplan](#tag/Curo-and-Xplan) | `xplan` | Each adviser |\n\n### Linked clients\n\nA client's `crm_links` lists every CRM record the client is linked to, usually one.\nCalling a CRM endpoint for a client that isn't linked to that CRM returns `409\ncrm_not_linked`. AdvisoryAI only reads from CRMs through this API. It never writes to\nthem.\n\n### CRM summary\n\n`GET /clients/{client_id}/crm/summary` returns the same set of figures for every CRM,\nsuch as portfolio value, ISA usage and review status, so you can compare clients\nacross CRMs. Not every CRM holds the data behind every figure. Figures a CRM can't\nsupply are always `null`:\n\n| Field | PlannrCRM | Intelliflo | Curo, Curo PP, Xplan |\n|---|---|---|---|\n| `portfolio_value`, `cash_position`, `isa_portfolio_value`, `wrapper_types` | ✓ | ✓ | ✓ |\n| `aum_value` | ✓ | ✓ | `null` |\n| `isa_contribution_ytd`, `pension_contribution_ytd` | ✓ | ✓ | `null` |\n| `vulnerability_flag`, `vulnerability_notes` | ✓ | ✓ | `null` |\n| `next_review_date`, `last_review_date` | From the CRM | From AdvisoryAI annual reviews | From AdvisoryAI annual reviews |\n| `review_status`, `review_frequency_months`, `data_quality_score` | ✓ | ✓ | ✓ |\n\nEach CRM's field mapping page explains exactly how its figures are derived.\n"
    },
    {
      "name": "PlannrCRM",
      "description": "AdvisoryAI integrates with [PlannrCRM](https://plannrcrm.com) at adviser level. Each\nadviser connects their own Plannr account from **Settings → Integrations** in\nAdvisoryAI. AdvisoryAI then retrieves that adviser's Plannr clients with their\npermissions. API partners never handle Plannr credentials.\n\n### Data freshness\n\nThe API serves AdvisoryAI's copy of the Plannr data, so it stays fast even when Plannr\nis slow. AdvisoryAI refreshes a client's Plannr data whenever an adviser works on that\nclient, and refreshes every connected adviser's clients nightly. Each response\nincludes `last_synced_at` so you can see how fresh the data is.\n\n### Resources\n\n- `GET /clients/{client_id}/plannr`: the client's Plannr data, grouped into sections\n  (personal details, addresses, employment, health & vulnerability, review dates,\n  plans, expenditure).\n- `GET /clients/{client_id}/crm/summary?source=plannr`: figures AdvisoryAI derives\n  from the Plannr data (portfolio value, ISA usage, review status and so on).\n"
    },
    {
      "name": "PlannrCRM field mapping",
      "x-traitTag": true,
      "description": "These tables show the PlannrCRM API field behind every field this API returns.\n\n### Personal details\n\n| AdvisoryAI field | PlannrCRM field | Notes |\n|---|---|---|\n| `title` | `title` | e.g. `Mr`, `Mrs`, `Dr`. |\n| `first_name` | `first_name` | |\n| `middle_name` | `middle_name` | |\n| `last_name` | `last_name` | |\n| `maiden_name` | `maiden_name` | |\n| `salutation` | `nickname` | The name the client prefers to be called. |\n| `email` | Contact details → email | The client's primary email. |\n| `phone` | `mobile` | |\n| `date_of_birth` | `date_of_birth` | |\n| `gender` | `gender` | |\n| `marital_status` | `marital_status` | |\n| `married_since` | `date_of_marriage` | |\n| `national_insurance_number` | `ni_number` | |\n| `nationalities` | `nationalities` | |\n| `has_will` | `has_will` | |\n| `will_up_to_date` | `is_will_accurate` | |\n\n### Addresses\n\n| AdvisoryAI field | PlannrCRM field | Notes |\n|---|---|---|\n| `id` | `uuid` | |\n| `is_primary` | `primary` | |\n| `label` | `name` | e.g. `Home`. |\n| `line_1` | `address_1` | |\n| `line_2` | `address_2` | |\n| `line_3` | `address_3` | |\n| `line_4` | `address_4` | |\n| `city` | `town_city` | |\n| `county` | `county` | |\n| `postcode` | `postcode` | |\n| `country` | `country` | |\n\n### Employment\n\nThe client's most recent Plannr employment record.\n\n| AdvisoryAI field | PlannrCRM field | Notes |\n|---|---|---|\n| `employment_status` | `status` | |\n| `occupation` | `occupation_code` | |\n| `job_title` | `job_title` | |\n| `employer` | `employer` | |\n| `employer_address` | `address` | Same fields as [Addresses](#tag/PlannrCRM-field-mapping), without `id`. |\n| `started_at` | `started_at` | |\n| `ended_at` | `ended_at` | |\n| `work_email` | `email_address.value` | |\n| `work_phone` | `phone_number.value` | |\n| `is_business_owner` | `is_business_owner` | |\n| `percentage_of_business_owned` | `percentage_of_business_owned` | |\n| `business_year_end` | `business_year_end` | |\n| `business_value` | `business_value.amount` | Pounds. |\n| `profit` | `profit.amount` | Pounds. |\n| `turnover_history[].year` | `turnover_history[].year` | |\n| `turnover_history[].value` | `turnover_history[].value.amount` | Pounds. |\n| `accountant.company_name` | `accountant_company_name` | |\n| `accountant.email` | `accountant_email` | |\n| `accountant.phone` | `accountant_telephone_number` | |\n| `accountant.address` | `accountant_address` | |\n\n### Health & vulnerability\n\n| AdvisoryAI field | PlannrCRM field | Notes |\n|---|---|---|\n| `vulnerable` | `is_vulnerable` | Plannr's value: `yes`, `possibly` or `no`. |\n| `vulnerability_details` | `vulnerability_details` | |\n| `procedure_adaptation` | `procedure_adaptation` | |\n| `adaptation_notes` | `adaption_details_notes` | Plannr spells the key `adaption`. |\n| `client_preferences` | `client_preferences` | |\n| `current_health` | `current_state_of_health` | |\n| `health_explanation` | `state_of_health_explanation` | |\n| `smoker_status` | `smoker_status` | |\n| `smoker_since` | `smoker_since` | |\n| `cigarettes_daily` | `cigarettes_daily` | |\n| `alcohol_units_weekly` | `units_of_alcohol_weekly` | |\n| `height` | `height.value` | |\n| `weight` | `weight.value` | |\n| `health_notes` | `health_notes` | |\n\n### Review dates\n\n| AdvisoryAI field | PlannrCRM field | Notes |\n|---|---|---|\n| `next_review_date` | `next_review_date` | If Plannr has none, AdvisoryAI uses `last_review_date` + 12 months. |\n| `last_review_date` | `previous_review_date` | If Plannr has none, AdvisoryAI uses the date of the client's latest completed AdvisoryAI annual review. |\n\n### Plans\n\n| AdvisoryAI field | PlannrCRM field | Notes |\n|---|---|---|\n| `id` | `uuid` | |\n| `name` | `name` | |\n| `type` | `type` | e.g. `pension`, `investment`, `cash_account`, `annuity`, `general_insurance`, `fixed_asset`. |\n| `sub_type` | `sub_type` | e.g. `isa_stocks_shares`, `lifetime_isa_cash`. |\n| `sub_type_label` | `sub_type_label` | Readable version of `sub_type`. |\n| `abstract_type` | `abstract_type` | `asset` for plans that count towards portfolio value. |\n| `provider` | `provider` | |\n| `policy_number` | `policy_number` | |\n| `status` | `status` | e.g. `active`, `inactive`. |\n| `value` | `latest_valuations[0].value.amount` | Latest valuation, in pounds. |\n| `valued_at` | `latest_valuations[0].valued_at` | |\n| `opened_at` | `opened_at` | |\n| `inactive_at` | `inactive_at` | |\n| `inactive_reason` | `inactive_reason` | |\n| `review_date` | `review_date` | |\n| `advice_area` | `advice_area` | |\n| `include_in_valuation` | `include_in_client_valuation` | |\n| `under_advice` | `under_advice` | |\n| `is_wrap_product` | `is_wrap_product` | |\n\n### Expenditure\n\n| AdvisoryAI field | PlannrCRM field | Notes |\n|---|---|---|\n| `id` | `uuid` | |\n| `section` | `section` | Plannr expenditure section, e.g. `housekeeping`, `motoring`. |\n| `name` | `name` | e.g. `Mortgage Repayments`. |\n| `amount` | `amount.amount` | Pounds. |\n\n### Derived summary fields\n\nAdvisoryAI calculates these from the Plannr data. They're returned by\n`GET /clients/{client_id}/crm/summary?source=plannr`.\n\n| Field | How it is derived |\n|---|---|\n| `portfolio_value` | Sum of the values of **active** plans with `abstract_type: asset`. |\n| `aum_value` | `portfolio_value`, excluding non-investable wrappers (fixed assets, insurance, mortgages, loans, equity release). |\n| `cash_position` | Sum of active plans with `type: cash_account`. |\n| `isa_portfolio_value` | Sum of active ISA plans (see *Wrapper classification*). |\n| `isa_contribution_ytd` | Contributions into active ISA plans in the current UK tax year (6 April to 5 April). |\n| `pension_contribution_ytd` | Contributions into active `pension` plans in the current UK tax year. |\n| `wrapper_types` | Distinct wrapper labels across active plans. |\n| `next_review_date` / `last_review_date` | See *Review dates*. |\n| `review_status` | `overdue` if `next_review_date` has passed, `due` if it is within 30 days, otherwise `on_track`. |\n| `review_frequency_months` | Always `12` for PlannrCRM clients. |\n| `vulnerability_flag` | `true` when Plannr `is_vulnerable` is `yes` or `possibly`, to stay on the side of caution under FCA Consumer Duty. |\n| `vulnerability_notes` | Plannr `vulnerability_details`. |\n| `data_quality_score` | 0–100: how complete the client's Plannr data is. |\n\n### Wrapper classification\n\nAn active plan is counted as an ISA when its `sub_type` is one of the ISA sub-types\nbelow, or when its name contains the word `ISA` or `TESSA`.\n\n| Wrapper | PlannrCRM `sub_type` / `type` |\n|---|---|\n| `ISA` | `isa_mini_cash`, `isa_tessa_only`, `isa_maxi`, `isa_mini_equity`, `isa_stocks_shares`, `isa_aim`, `isa_help_to_buy`, `isa_nisa`, `isa_maxi_mini_transfer`, `junior_isa_cash`, `junior_isa_nisa`, `junior_isa_stocks_shares`, `lifetime_isa_cash`, `lifetime_isa_stocks_shares`, `structured_plan_cash_isa`, `structured_plan_stocks_shares_isa` |\n| `Cash` | sub-type `cash_deposit`, `cash_account`, or type `cash_account` |\n| `Annuity` | sub-type `compulsory_purchase_life_annuity`, `deferred_annuity`, `annuity_non_pension`, or type `annuity` |\n| `Insurance` | type `general_insurance` |\n| `FixedAsset` | type `fixed_asset` |\n| `Investment` | type `investment` |\n| *Title-cased type* | Any other type, e.g. `pension` → `Pension` |\n"
    },
    {
      "name": "Intelliflo",
      "description": "AdvisoryAI integrates with [Intelliflo Office](https://www.intelliflo.com) at adviser\nlevel. Each adviser connects their own Intelliflo account from **Settings →\nIntegrations** in AdvisoryAI (OAuth sign-in to Intelliflo), then links their\nAdvisoryAI clients to Intelliflo clients. Only linked clients are retrieved. API\npartners never handle Intelliflo credentials.\n\n### Data freshness\n\nAdvisoryAI refreshes a client's Intelliflo data when an adviser syncs or opens the\nclient in AdvisoryAI. If you request data that is more than 24 hours old, AdvisoryAI\nqueues a background refresh and returns the copy it holds. Check `last_synced_at`.\n\n### Resources\n\n- `GET /clients/{client_id}/intelliflo`: the client's Intelliflo data, grouped into\n  sections (personal details, contact, addresses, employment, incomes, health &\n  vulnerability, plans, assets, expenditure).\n- `GET /clients/{client_id}/crm/summary?source=intelliflo`: figures AdvisoryAI derives\n  from the Intelliflo data.\n"
    },
    {
      "name": "Intelliflo field mapping",
      "x-traitTag": true,
      "description": "These tables show the Intelliflo API (`/v2`) field behind every field this API\nreturns. Amounts are in pounds, as Intelliflo returns them.\n\n### Personal details\n\nSource: `GET /v2/clients/{id}`.\n\n| AdvisoryAI field | Intelliflo field | Notes |\n|---|---|---|\n| `title` | `person.title` | |\n| `first_name` | `person.firstName` | |\n| `middle_name` | `person.middleName` | |\n| `last_name` | `person.lastName` | |\n| `maiden_name` | `person.maidenName` | |\n| `salutation` | `person.salutation` | |\n| `date_of_birth` | `person.dateOfBirth` | |\n| `gender` | `person.gender` | |\n| `marital_status` | `person.maritalStatus` | |\n| `marital_status_since` | `person.maritalStatusSince` | |\n| `nationality` | `person.nationalityCountry.name` | |\n| `place_of_birth` | `person.territorialProfile.placeOfBirth` | |\n| `national_insurance_number` | `person.niNumber` | |\n| `has_will` | `person.hasWill` | |\n| `will_up_to_date` | `person.isWillUptoDate` | |\n| `power_of_attorney_granted` | `person.isPowerOfAttorneyGranted` | |\n| `external_reference` | `externalReference` | |\n| `residency.uk_resident` | `person.territorialProfile.ukResident` | |\n| `residency.uk_domicile` | `person.territorialProfile.ukDomicile` | |\n| `residency.expatriate` | `person.territorialProfile.expatriate` | |\n| `residency.country_of_residence` | `person.territorialProfile.countryOfResidence.name` | |\n| `residency.country_of_domicile` | `person.territorialProfile.countryOfDomicile.name` | |\n\n### Contact\n\nSource: `GET /v2/clients/{id}/contactdetails`.\n\n| AdvisoryAI field | Intelliflo field | Notes |\n|---|---|---|\n| `email` | `items[].value` where `type` is `Email` | First email. |\n| `mobile` | `items[].value` where `type` is `Mobile` | |\n\n### Addresses\n\nSource: `GET /v2/clients/{id}/addresses`.\n\n| AdvisoryAI field | Intelliflo field | Notes |\n|---|---|---|\n| `id` | `id` | |\n| `type` | `type` | e.g. `Home`. |\n| `line_1` | `address.line1` | |\n| `line_2` | `address.line2` | |\n| `line_3` | `address.line3` | |\n| `city` | `address.locality` | |\n| `county` | `address.county.code` | Intelliflo county code. |\n| `postcode` | `address.postalCode` | |\n| `country` | `address.country.code` | e.g. `GB`. |\n\n### Employment\n\nSource: `GET /v2/clients/{id}/employments`. The client's most recent employment.\n\n| AdvisoryAI field | Intelliflo field | Notes |\n|---|---|---|\n| `employment_status` | `employmentStatus` | |\n| `occupation` | `occupation` | |\n| `intended_retirement_age` | `intendedRetirementAge` | |\n\n### Incomes\n\nSource: `GET /v2/clients/{id}/incomes`.\n\n| AdvisoryAI field | Intelliflo field | Notes |\n|---|---|---|\n| `id` | `id` | |\n| `category` | `category` | e.g. `BasicAnnualIncome`. |\n| `description` | `description` | |\n| `gross_amount` | `gross.amount` | Pounds. |\n| `net_amount` | `net.amount` | Pounds. |\n| `currency` | `gross.currency` | |\n| `frequency` | `frequency` | e.g. `Monthly`, `Annually`. |\n| `starts_on` | `startsOn` | |\n\n### Health & vulnerability\n\nHealth comes from `GET /v2/clients/{id}`, vulnerability from\n`GET /v2/clients/{id}/vulnerabilities/current`.\n\n| AdvisoryAI field | Intelliflo field | Notes |\n|---|---|---|\n| `health.is_smoker` | `person.healthProfile.isSmoker` | |\n| `health.smoked_in_last_12_months` | `person.healthProfile.smokedInLast12Months` | |\n| `health.in_good_health` | `person.healthProfile.inGoodHealth` | |\n| `vulnerability.has_vulnerability` | `hasVulnerability` | `Yes`, `No` or `Potential`. |\n| `vulnerability.type` | `type` | `Temporary` or `Permanent`. |\n| `vulnerability.categories` | `categories` | `Health`, `LifeEvent`, `Resilience`, `Capability`. |\n| `vulnerability.notes` | `notes` | |\n| `vulnerability.assessed_on` | `assesedOn` | Intelliflo spells the key `assesed`. |\n| `vulnerability.review_on` | `reviewOn` | |\n| `vulnerability.client_portal_suitable` | `isClientPortalSuitable` | |\n\n### Plans\n\nSource: `GET /v2/clients/{id}/plans`, and each plan's contributions.\n\n| AdvisoryAI field | Intelliflo field | Notes |\n|---|---|---|\n| `id` | `id` | |\n| `plan_type` | `planType.name` | e.g. `Stocks & Shares ISA`, `Personal Pension`. |\n| `category` | `discriminator` | e.g. `InvestmentPlan`, `PensionContributionDrawdownPlan`. |\n| `wrapper` | derived | See *Wrapper classification*. |\n| `status` | `currentStatus` | e.g. `In force`. |\n| `active` | derived | `true` when `currentStatus` is in force, active or live in force. |\n| `provider` | `productProvider.name` | |\n| `policy_number` | `policyNumber` | |\n| `starts_on` | `startsOn` | |\n| `value` | `latestValuation.value.amount` | Pounds. |\n| `currency` | `latestValuation.value.currency` | |\n| `valued_on` | `latestValuation.valuedOn` | |\n| `contributions[].date` | contributions `items[].date` | Only for cash, investment and pension plans. |\n| `contributions[].amount` | contributions `items[].value.amount` | Pounds. |\n\n### Assets\n\nSource: `GET /v2/clients/{id}/assets`.\n\n| AdvisoryAI field | Intelliflo field | Notes |\n|---|---|---|\n| `id` | `id` | |\n| `asset_type` | `assetType` | e.g. `MainResidence`, `Cash`, `Investments`, `BuyToLetProperty`, `OwnBusiness`. |\n| `description` | `description` | |\n| `owner` | `owner` | |\n| `percent_ownership` | `owners[0].percentOwnership` | |\n| `value` | `currentValue.value.amount` | Pounds, the whole asset before ownership share. |\n| `currency` | `currency` | |\n| `valued_on` | `valuedOn` | |\n| `purchased_on` | `purchasedOn` | |\n| `purchase_price` | `purchasePrice.amount` | Pounds. |\n| `is_loan` | `isLoanAmount` | |\n| `is_investment_property` | `isInvestmentProperty` | |\n| `address` | `address` | Same fields as *Addresses*, without `id` and `type`. |\n\n### Expenditure\n\nSource: `GET /v2/clients/{id}/expenditures`.\n\n| AdvisoryAI field | Intelliflo field | Notes |\n|---|---|---|\n| `id` | `id` | |\n| `section` | derived from `category` | `basic_essential`, `quality_of_living`, `non_essential` or `liability`. |\n| `category` | `category` | e.g. `CouncilTax`, `Mortgage`, `Holidays`. |\n| `description` | `description` | |\n| `net_amount` | `net.amount` | Pounds. |\n| `currency` | `net.currency` | |\n| `frequency` | `frequency` | e.g. `Monthly`, `Annually`. |\n| `starts_on` | `startsOn` | |\n\n| `section` | Intelliflo categories |\n|---|---|\n| `basic_essential` | `Rent`, `CouncilTax`, `Gas`, `Electricity`, `Water`, `Telephone`, `FoodPersonalCare`, `CarTravellingExpenses`, `Housekeeping`, `GroundRentServicecharge` |\n| `quality_of_living` | `Clothing`, `FurnitureAppliancesRepairs`, `TvSatelliteInternetBasicRecreation`, `Pension`, `SchoolFeeChildcare` |\n| `non_essential` | `Gym`, `Holidays`, `Entertainment`, `LifeGeneralAssurancePremium`, `Investments` |\n| `liability` | `PersonalLoans`, `CreditCards`, `Mortgage`, `OtherLiability` |\n\n### Derived summary fields\n\n| Field | How it is derived |\n|---|---|\n| `portfolio_value` | Assets (value × ownership share, loans excluded) plus the latest valuation of active plans. |\n| `aum_value` | `portfolio_value`, excluding main residence, rental property and own business assets, and non-investable wrappers (insurance, mortgages, loans, equity release). |\n| `cash_position` | Assets with `asset_type: Cash`. |\n| `isa_portfolio_value` | Active plans whose plan type is an ISA or TESSA. |\n| `isa_contribution_ytd` | Contributions into active ISA plans in the current UK tax year (6 April to 5 April). |\n| `pension_contribution_ytd` | Contributions into active pension plans in the current UK tax year. |\n| `wrapper_types` | Distinct wrappers across active plans. |\n| `last_review_date` | The client's latest completed AdvisoryAI annual review. Intelliflo review dates aren't read. |\n| `next_review_date` | `last_review_date` + 12 months, or 12 months after the client was created. |\n| `review_status` | `overdue` if `next_review_date` has passed, `due` if it is within 30 days, otherwise `on_track`. |\n| `review_frequency_months` | Always `12`. |\n| `vulnerability_flag` | `true` when `has_vulnerability` is `Yes`. |\n| `vulnerability_notes` | Vulnerability `notes`. |\n| `data_quality_score` | 0–100: how complete the client's Intelliflo data is. |\n\n### Wrapper classification\n\nA plan whose plan type name contains `ISA` or `TESSA` is an `ISA`. Otherwise:\n\n| Wrapper | Intelliflo `discriminator` |\n|---|---|\n| `Cash` | `CashBankAccountPlan` |\n| `Investment` | `InvestmentPlan`, `LifeAssuredInvestmentPlan` |\n| `Pension` | `PensionContributionDrawdownPlan`, `PensionDefinedBenefitPlan` |\n| `Annuity` | `AnnuityPlan` |\n| `Insurance` | `GeneralMedicalInsurancePlan`, `PersonalProtectionPlan`, `GroupProtectionPlan` |\n| `Mortgage` | `MortgagePlan` |\n| `EquityRelease` | `EquityReleasePlan` |\n| `Loan` | `LoanCreditPlan` |\n| `null` | Any other plan type |\n"
    },
    {
      "name": "Curo and Xplan",
      "description": "AdvisoryAI integrates with **Curo**, **Curo Power Platform** and **Iress Xplan**. Data\nfrom all three reaches AdvisoryAI in one common format, so the three endpoints return\nthe same shape and share one [field mapping](#tag/Curo-and-Xplan-field-mapping).\n\n### Connecting\n\n- **Curo:** an organisation admin connects the firm's Curo account first. Each adviser\n  then connects their own Curo user.\n- **Curo Power Platform** and **Xplan:** each adviser connects their own account from\n  **Settings → Integrations**.\n\nAdvisoryAI always retrieves a client's data with the connection of the adviser who\nlooks after the client. API partners never handle CRM credentials.\n\n### Data freshness\n\nAdvisoryAI refreshes a client's data when an adviser syncs or opens the client in\nAdvisoryAI. If you request data that is more than 24 hours old, AdvisoryAI queues a\nbackground refresh and returns the copy it holds. Check `last_synced_at`.\n\n### Resources\n\n- `GET /clients/{client_id}/curo`\n- `GET /clients/{client_id}/curo-pp`\n- `GET /clients/{client_id}/xplan`\n- `GET /clients/{client_id}/crm/summary?source=curo` (or `curo_pp`, `xplan`): figures\n  AdvisoryAI derives from the data.\n"
    },
    {
      "name": "Curo and Xplan field mapping",
      "x-traitTag": true,
      "description": "These tables show the CRM field behind every field the Curo, Curo Power Platform and\nXplan endpoints return. Where the CRMs differ, the table says so.\n\n- Amounts are in pounds.\n- `currency` is the currency **name** as the CRM holds it, for example\n  `Pound Sterling`, not an ISO code.\n\n### Personal details\n\n| AdvisoryAI field | CRM field | Notes |\n|---|---|---|\n| `title` | `title` | |\n| `first_name` | `first_name` | |\n| `middle_names` | `middle_names` | |\n| `last_name` | `last_name` | |\n| `preferred_name` | `preferred_name` | |\n| `date_of_birth` | `date_of_birth` | |\n| `legal_sex` | `legal_sex` | `male` or `female`. |\n| `gender` | `gender` | |\n| `marital_status` | `marital_status` | e.g. `single`, `married`, `divorced`, `widowed`, `civil_partnership`, `unmarried_partner`. |\n| `marital_status_since` | `marital_status_since` | |\n| `national_insurance_number` | Curo: `ni_number`. Curo Power Platform and Xplan: `national_insurance` | |\n| `nationality` | `nationality` | |\n| `additional_nationalities` | `additional_nationalities` | |\n| `country_of_residence` | `country_of_residence` | |\n| `country_of_domicile` | `country_of_domicile` | |\n| `country_of_birth` | `country_of_birth` | |\n| `place_of_birth` | `place_of_birth` | |\n\n### Contact\n\n| AdvisoryAI field | CRM field | Notes |\n|---|---|---|\n| `email` | contact detail with `type: email` | The primary email, otherwise the first. |\n| `phone` | contact detail with `type: phone` | The primary phone. |\n\n### Addresses\n\n| AdvisoryAI field | CRM field | Notes |\n|---|---|---|\n| `id` | `id` | |\n| `is_primary` | `primary` | |\n| `line_1` … `line_4` | `line1` … `line4` | |\n| `city` | `town_city` | |\n| `county` | `county` | |\n| `postcode` | `postcode` | |\n| `country` | `country` | |\n| `move_in_date` | `move_in_date` | |\n\n### Employment\n\nThe client's first employment record.\n\n| AdvisoryAI field | CRM field | Notes |\n|---|---|---|\n| `employment_status` | `employment_status` | |\n| `occupation` | Curo: `role`. Curo Power Platform and Xplan: `occupation` | |\n| `job_title` | `job_title` | |\n| `employer` | `employer_name` | |\n| `started_at` | `start_date` | |\n| `ended_at` | `end_date` | |\n| `employer_address` | `employer_address` | Same fields as *Addresses*, without `id`, `is_primary` and `move_in_date`. |\n\n### Incomes\n\n| AdvisoryAI field | CRM field | Notes |\n|---|---|---|\n| `id` | `id` | |\n| `category` | `category` | |\n| `description` | `description` | |\n| `gross_amount` | `gross_amount` | Pounds. |\n| `net_amount` | `net_amount` | Pounds. |\n| `currency` | `currency` | |\n| `frequency` | `frequency` | |\n| `starts_at` | `starts_at` | |\n| `ends_at` | `ends_at` | |\n\n### Expenses\n\n| AdvisoryAI field | CRM field | Notes |\n|---|---|---|\n| `category` | `category` | |\n| `description` | `description` | |\n| `amount` | `amount` | Pounds. |\n| `currency` | `currency` | |\n| `frequency` | `frequency` | |\n\n### Savings & investments\n\n| AdvisoryAI field | CRM field | Notes |\n|---|---|---|\n| `id` | `id` | |\n| `category` | `category`, or `type` | e.g. `Stocks and Shares ISA`, `SIPP`, `Savings Account`. |\n| `product_name` | `product_name` | |\n| `description` | `description` | |\n| `wrapper` | derived | See *Wrapper classification*. |\n| `provider` | `provider_name`, or `provider` | |\n| `policy_number` | `policy_number`, or `account_number` | |\n| `status` | `status` | |\n| `value` | `latest_valuation.value` | Pounds. |\n| `valued_on` | `latest_valuation.date` | |\n| `currency` | `currency` | |\n| `owners[]` | `owners[]` (`id`, `name`) | Joint holdings list each owner. |\n\n### Pensions\n\n| AdvisoryAI field | CRM field | Notes |\n|---|---|---|\n| `value` | defined contribution pension `latest_valuation.value` | Pounds. |\n\n### Assets\n\n| AdvisoryAI field | CRM field | Notes |\n|---|---|---|\n| `id` | `id` | |\n| `asset_type` | `type` | |\n| `description` | `description` | |\n| `value` | `current_value` | Pounds. |\n| `valued_on` | `current_value_date` | |\n| `original_value` | `original_value` | Pounds. |\n| `original_value_date` | `original_value_date` | |\n| `currency` | `currency` | |\n\n### Liabilities\n\n| AdvisoryAI field | CRM field | Notes |\n|---|---|---|\n| `id` | `id` | |\n| `liability_type` | `type` | e.g. `mortgage`. |\n| `provider` | `provider` | |\n| `policy_number` | `policy_number` | |\n| `outstanding_balance` | `outstanding_balance` | Pounds. |\n| `balance_date` | `outstanding_balance_date` | |\n| `interest_rate` | `interest_rate` | Percent. |\n| `repayment_amount` | `repayment_amount` | Pounds. |\n| `repayment_frequency` | `repayment_frequency` | |\n| `start_date` | `start_date` | |\n| `end_date` | `end_date` | |\n| `currency` | `currency` | |\n| `owners[]` | `owners[]` (`id`, `name`) | |\n\n### Dependants\n\nCuro and Xplan only.\n\n| AdvisoryAI field | CRM field | Notes |\n|---|---|---|\n| `name` | `name` | |\n| `relationship` | `relationship_type` | e.g. `Spouse`, `Son`, `Daughter`. |\n| `date_of_birth` | `date_of_birth` | |\n\n### Derived summary fields\n\nThe same rules apply to all three CRMs.\n\n| Field | How it is derived |\n|---|---|\n| `portfolio_value` | Assets' `value` plus savings & investments and pension valuations. Liabilities aren't subtracted. |\n| `cash_position` | Savings & investments whose category or product name is cash (`Cash`, `Savings Account`, `Cash Deposit`, `Cash Account`). |\n| `isa_portfolio_value` | Savings & investments whose category or product name is an ISA. |\n| `wrapper_types` | Distinct wrappers across savings & investments, plus `Pension` when the client has a pension. |\n| `last_review_date` | The client's latest completed AdvisoryAI annual review. CRM review dates aren't read. |\n| `next_review_date` | `last_review_date` + 12 months, or 12 months after the client was created. |\n| `review_status` | `overdue` if `next_review_date` has passed, `due` if it is within 30 days, otherwise `on_track`. |\n| `review_frequency_months` | Always `12`. |\n| `data_quality_score` | 0–100: how complete the client's CRM data is. |\n\n`aum_value`, `isa_contribution_ytd`, `pension_contribution_ytd`, `vulnerability_flag`\nand `vulnerability_notes` are always `null` for these CRMs.\n\n### Wrapper classification\n\nA record whose category or product name contains `ISA`, `LISA`, `Lifetime ISA` or\n`Junior ISA` is an `ISA`. Otherwise the category decides:\n\n| Wrapper | CRM `category` |\n|---|---|\n| `Cash` | `Savings Account`, `Cash Deposit` |\n| `ISA` | `Cash ISA` |\n| `Investment` | `General Investment Account`, `Investment Bond`, `Offshore Bond`, `Unit Trust`, `OEIC` |\n| `Pension` | `Personal Pension`, `SIPP`, `Workplace Pension`, `Group Personal Pension`, `Stakeholder Pension` |\n| `Annuity` | `Annuity`, `Purchased Life Annuity` |\n| `Insurance` | `Life Assurance`, `Critical Illness`, `Income Protection`, `Whole of Life`, `Whole of Life Death cover only (Investment)` |\n| *Title-cased category* | Any other category |\n"
    }
  ],
  "x-tagGroups": [
    {
      "name": "Getting started",
      "tags": [
        "Authentication"
      ]
    },
    {
      "name": "Resources",
      "tags": [
        "Clients",
        "Meetings",
        "Documents",
        "Reports",
        "Templates",
        "Users",
        "Organisation"
      ]
    },
    {
      "name": "CRM integrations",
      "tags": [
        "CRM overview",
        "PlannrCRM",
        "PlannrCRM field mapping",
        "Intelliflo",
        "Intelliflo field mapping",
        "Curo and Xplan",
        "Curo and Xplan field mapping"
      ]
    }
  ],
  "paths": {
    "/oauth/token": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "createAccessToken",
        "summary": "Get an access token",
        "description": "Exchange your client credentials for a bearer access token. Send the credentials\neither in the form body (as below) or with HTTP Basic authentication\n(`Authorization: Basic base64(client_id:client_secret)`).\n",
        "security": [],
        "servers": [
          {
            "url": "https://api.advisoryai.com",
            "description": "Production"
          },
          {
            "url": "https://sandbox-api.advisoryai.com",
            "description": "Sandbox"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token issued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, for example an unsupported `grant_type` or unknown scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                },
                "example": {
                  "error": "invalid_scope",
                  "error_description": "The requested scope is invalid, unknown, or malformed."
                }
              }
            }
          },
          "401": {
            "description": "The client credentials are wrong or have been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                },
                "example": {
                  "error": "invalid_client",
                  "error_description": "Client authentication failed."
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl -X POST https://api.advisoryai.com/oauth/token \\\n  -d grant_type=client_credentials \\\n  -d client_id=$AAI_CLIENT_ID \\\n  -d client_secret=$AAI_CLIENT_SECRET \\\n  -d scope=\"clients:read crm:read\"\n"
          },
          {
            "lang": "Node.js",
            "source": "const res = await fetch(\"https://api.advisoryai.com/oauth/token\", {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n  body: new URLSearchParams({\n    grant_type: \"client_credentials\",\n    client_id: process.env.AAI_CLIENT_ID,\n    client_secret: process.env.AAI_CLIENT_SECRET,\n    scope: \"clients:read crm:read\",\n  }),\n});\nconst { access_token, expires_in } = await res.json();\n"
          },
          {
            "lang": "Python",
            "source": "import os, requests\n\nres = requests.post(\n    \"https://api.advisoryai.com/oauth/token\",\n    data={\n        \"grant_type\": \"client_credentials\",\n        \"client_id\": os.environ[\"AAI_CLIENT_ID\"],\n        \"client_secret\": os.environ[\"AAI_CLIENT_SECRET\"],\n        \"scope\": \"clients:read crm:read\",\n    },\n)\ntoken = res.json()[\"access_token\"]\n"
          }
        ]
      }
    },
    "/oauth/revoke": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "revokeAccessToken",
        "summary": "Revoke an access token",
        "description": "Invalidate an access token before it expires. The endpoint always answers `200`,\neven for a token that is unknown or already revoked, so it reveals nothing about\nwhether a token was valid.\n",
        "security": [],
        "servers": [
          {
            "url": "https://api.advisoryai.com",
            "description": "Production"
          },
          {
            "url": "https://sandbox-api.advisoryai.com",
            "description": "Sandbox"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/RevokeRequest"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RevokeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The token is revoked, or was never valid.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {}
              }
            }
          },
          "400": {
            "description": "The request has no `token`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                },
                "example": {
                  "error": "invalid_request",
                  "error_description": "The request is missing a required parameter."
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl -X POST https://api.advisoryai.com/oauth/revoke \\\n  -d token=$AAI_ACCESS_TOKEN \\\n  -d client_id=$AAI_CLIENT_ID \\\n  -d client_secret=$AAI_CLIENT_SECRET\n"
          }
        ]
      }
    },
    "/clients": {
      "get": {
        "tags": [
          "Clients"
        ],
        "operationId": "listClients",
        "summary": "List clients",
        "description": "Returns the organisation's clients, newest first by default.",
        "security": [
          {
            "oauth2": [
              "clients:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          },
          {
            "name": "query",
            "in": "query",
            "description": "Search by client name or email.",
            "schema": {
              "type": "string"
            },
            "example": "carl"
          },
          {
            "name": "crm",
            "in": "query",
            "description": "Only return clients linked to this CRM.",
            "schema": {
              "$ref": "#/components/schemas/CrmSource"
            }
          },
          {
            "name": "sort_field",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "updated_at",
                "full_name",
                "last_synced_at"
              ],
              "default": "created_at"
            }
          },
          {
            "name": "sort_order",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of clients.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "clients",
                    "meta"
                  ],
                  "properties": {
                    "clients": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Client"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginationMeta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl \"https://api.advisoryai.com/v1/clients?crm=plannr&per_page=50\" \\\n  -H \"Authorization: Bearer $AAI_ACCESS_TOKEN\"\n"
          }
        ]
      }
    },
    "/clients/{client_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ClientId"
        }
      ],
      "get": {
        "tags": [
          "Clients"
        ],
        "operationId": "getClient",
        "summary": "Retrieve a client",
        "security": [
          {
            "oauth2": [
              "clients:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Client"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl https://api.advisoryai.com/v1/clients/21GYLb9n \\\n  -H \"Authorization: Bearer $AAI_ACCESS_TOKEN\"\n"
          }
        ]
      }
    },
    "/documents": {
      "get": {
        "tags": [
          "Documents"
        ],
        "operationId": "listDocuments",
        "summary": "List documents",
        "description": "Returns the organisation's documents, newest first. Filter by `client_id` to get one client's documents.",
        "security": [
          {
            "oauth2": [
              "documents:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          },
          {
            "name": "client_id",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "example": "21GYLb9n"
          },
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "document",
                "audio"
              ]
            }
          },
          {
            "name": "document_type",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/DocumentType"
            }
          },
          {
            "name": "source",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/DocumentSource"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of documents.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "documents",
                    "meta"
                  ],
                  "properties": {
                    "documents": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Document"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginationMeta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl \"https://api.advisoryai.com/v1/documents?client_id=21GYLb9n\" \\\n  -H \"Authorization: Bearer $AAI_ACCESS_TOKEN\"\n"
          }
        ]
      }
    },
    "/documents/{document_id}": {
      "parameters": [
        {
          "name": "document_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "Rt5vN8wQ"
        }
      ],
      "get": {
        "tags": [
          "Documents"
        ],
        "operationId": "getDocument",
        "summary": "Retrieve a document",
        "description": "Returns the document with its extracted text (or transcript, for recordings).",
        "security": [
          {
            "oauth2": [
              "documents:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/reports": {
      "get": {
        "tags": [
          "Reports"
        ],
        "operationId": "listReports",
        "summary": "List reports",
        "description": "Returns the organisation's reports, newest first. Filter by `client_id` to get one client's reports.",
        "security": [
          {
            "oauth2": [
              "reports:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          },
          {
            "name": "client_id",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "example": "21GYLb9n"
          },
          {
            "name": "adviser_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "meeting_id",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "example": "Pw7cQ2nL"
          },
          {
            "name": "template_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "report_type",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/ReportType"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "generating",
                "completed",
                "failed"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of reports.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "reports",
                    "meta"
                  ],
                  "properties": {
                    "reports": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Report"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginationMeta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl \"https://api.advisoryai.com/v1/reports?client_id=21GYLb9n&status=completed\" \\\n  -H \"Authorization: Bearer $AAI_ACCESS_TOKEN\"\n"
          }
        ]
      }
    },
    "/reports/{report_id}": {
      "parameters": [
        {
          "name": "report_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "Mz3kW7aB"
        }
      ],
      "get": {
        "tags": [
          "Reports"
        ],
        "operationId": "getReport",
        "summary": "Retrieve a report",
        "description": "Returns the report with its generated content as HTML.",
        "security": [
          {
            "oauth2": [
              "reports:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The report.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReportDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/report-generations": {
      "post": {
        "tags": [
          "Reports"
        ],
        "operationId": "createReportGeneration",
        "summary": "Request a report",
        "description": "Starts generating a report for a client from a template. Give AdvisoryAI the\nsource material to work from in any combination:\n\n- `meeting_id`: a recorded [meeting](#tag/Meetings). Its transcript is used.\n- `document_ids`: documents already on the client (see [Documents](#tag/Documents)).\n- `files`: new documents or audio recordings to upload. They're added to the\n  client's documents and used for this report.\n\nAt least one source is required.\n\nThe request returns `202 Accepted` straight away with a **generation**. Poll\n[`GET /report-generations/{generation_id}`](#operation/getReportGeneration) until\n`status` is `completed` (or `failed`), then download the report from its links.\n\nSend `multipart/form-data` when you upload files. Without files you can send\n`application/json` instead.\n\nPass an `Idempotency-Key` header so that a retried request never starts a second\nreport. A repeated key within 24 hours returns the original generation.\n",
        "security": [
          {
            "oauth2": [
              "reports:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "A unique value for this request, such as a UUID.",
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "example": "0b9d7c2a-5e1f-4c3b-9a8d-2f6e1b7c4d90"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/ReportGenerationRequest"
              },
              "encoding": {
                "files": {
                  "contentType": "application/pdf, application/vnd.openxmlformats-officedocument.wordprocessingml.document, text/plain, text/csv, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-outlook, audio/*, video/mp4"
                }
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReportGenerationJsonRequest"
              },
              "example": {
                "client_id": "21GYLb9n",
                "template_id": "Tb6yH3sK",
                "meeting_id": "Pw7cQ2nL",
                "document_ids": [
                  "Rt5vN8wQ"
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The generation was accepted and is queued.",
            "headers": {
              "Location": {
                "description": "URL of the generation, to poll for its status.",
                "schema": {
                  "type": "string",
                  "format": "uri"
                },
                "example": "https://api.advisoryai.com/v1/report-generations/Gn4hJ8kP"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReportGeneration"
                },
                "example": {
                  "id": "Gn4hJ8kP",
                  "status": "queued",
                  "client": {
                    "id": "21GYLb9n",
                    "name": "Carl Johnson"
                  },
                  "template": {
                    "id": "Tb6yH3sK",
                    "name": "Annual Review (Example Wealth)"
                  },
                  "adviser": {
                    "id": "Xk92PqLm",
                    "name": "Jane Patel"
                  },
                  "meeting_id": "Pw7cQ2nL",
                  "documents": [
                    {
                      "id": "Rt5vN8wQ",
                      "name": "Fact find - Carl Johnson.pdf",
                      "status": "ready",
                      "failure_reason": null
                    },
                    {
                      "id": "Ux2mF6rD",
                      "name": "Aviva valuation Oct 2026.pdf",
                      "status": "processing",
                      "failure_reason": null
                    }
                  ],
                  "report_id": null,
                  "download": null,
                  "error": null,
                  "created_at": "2026-11-04T10:15:02Z",
                  "updated_at": "2026-11-04T10:15:02Z",
                  "completed_at": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "label": "Upload files",
            "source": "curl https://api.advisoryai.com/v1/report-generations \\\n  -H \"Authorization: Bearer $AAI_ACCESS_TOKEN\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -F client_id=21GYLb9n \\\n  -F template_id=Tb6yH3sK \\\n  -F meeting_id=Pw7cQ2nL \\\n  -F \"document_ids[]=Rt5vN8wQ\" \\\n  -F \"files[]=@aviva-valuation-oct-2026.pdf\" \\\n  -F \"files[]=@review-call.m4a\"\n"
          },
          {
            "lang": "cURL",
            "label": "Existing sources only",
            "source": "curl https://api.advisoryai.com/v1/report-generations \\\n  -H \"Authorization: Bearer $AAI_ACCESS_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -d '{\"client_id\":\"21GYLb9n\",\"template_id\":\"Tb6yH3sK\",\"meeting_id\":\"Pw7cQ2nL\"}'\n"
          }
        ]
      }
    },
    "/report-generations/{generation_id}": {
      "parameters": [
        {
          "name": "generation_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "Gn4hJ8kP"
        }
      ],
      "get": {
        "tags": [
          "Reports"
        ],
        "operationId": "getReportGeneration",
        "summary": "Check a report's status",
        "description": "Returns the generation's progress. When `status` is `completed`, `report_id` names\nthe new [report](#operation/getReport) and `download` holds signed links to the\nPDF and Word files.\n\nMost reports take a few minutes, longer when there are many or long documents to\nread. Poll every 10–15 seconds. While the report isn't ready the response includes a\n`Retry-After` header with the suggested wait in seconds.\n\nDownload links expire after 1 hour. Call this endpoint again, or\n[retrieve the report](#operation/getReport), for fresh links.\n",
        "security": [
          {
            "oauth2": [
              "reports:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The generation.",
            "headers": {
              "Retry-After": {
                "description": "Suggested seconds before polling again. Only sent while the report isn't ready.",
                "schema": {
                  "type": "integer"
                },
                "example": 15
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReportGeneration"
                },
                "example": {
                  "id": "Gn4hJ8kP",
                  "status": "completed",
                  "client": {
                    "id": "21GYLb9n",
                    "name": "Carl Johnson"
                  },
                  "template": {
                    "id": "Tb6yH3sK",
                    "name": "Annual Review (Example Wealth)"
                  },
                  "adviser": {
                    "id": "Xk92PqLm",
                    "name": "Jane Patel"
                  },
                  "meeting_id": "Pw7cQ2nL",
                  "documents": [
                    {
                      "id": "Rt5vN8wQ",
                      "name": "Fact find - Carl Johnson.pdf",
                      "status": "ready",
                      "failure_reason": null
                    },
                    {
                      "id": "Ux2mF6rD",
                      "name": "Aviva valuation Oct 2026.pdf",
                      "status": "ready",
                      "failure_reason": null
                    }
                  ],
                  "report_id": "Mz3kW7aB",
                  "download": {
                    "pdf_url": "https://files.advisoryai.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMi.../annual-review.pdf",
                    "docx_url": "https://files.advisoryai.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMi.../annual-review.docx",
                    "expires_at": "2026-11-04T11:19:47Z"
                  },
                  "error": null,
                  "created_at": "2026-11-04T10:15:02Z",
                  "updated_at": "2026-11-04T10:19:47Z",
                  "completed_at": "2026-11-04T10:19:47Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl https://api.advisoryai.com/v1/report-generations/Gn4hJ8kP \\\n  -H \"Authorization: Bearer $AAI_ACCESS_TOKEN\"\n"
          },
          {
            "lang": "JavaScript",
            "label": "Poll until ready",
            "source": "async function waitForReport(generationId, token) {\n  const url = `https://api.advisoryai.com/v1/report-generations/${generationId}`;\n  for (;;) {\n    const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });\n    const generation = await res.json();\n    if (generation.status === 'completed') return generation.download;\n    if (generation.status === 'failed') throw new Error(generation.error.message);\n    const wait = Number(res.headers.get('Retry-After') ?? 15);\n    await new Promise((resolve) => setTimeout(resolve, wait * 1000));\n  }\n}\n"
          }
        ]
      }
    },
    "/users": {
      "get": {
        "tags": [
          "Users"
        ],
        "operationId": "listUsers",
        "summary": "List users",
        "description": "Returns the organisation's users.",
        "security": [
          {
            "oauth2": [
              "users:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          },
          {
            "name": "role",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/UserRole"
            }
          },
          {
            "name": "query",
            "in": "query",
            "description": "Search by name or email.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of users.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "users",
                    "meta"
                  ],
                  "properties": {
                    "users": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/User"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginationMeta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl \"https://api.advisoryai.com/v1/users?role=advisor\" \\\n  -H \"Authorization: Bearer $AAI_ACCESS_TOKEN\"\n"
          }
        ]
      }
    },
    "/users/{user_id}": {
      "parameters": [
        {
          "name": "user_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "Xk92PqLm"
        }
      ],
      "get": {
        "tags": [
          "Users"
        ],
        "operationId": "getUser",
        "summary": "Retrieve a user",
        "security": [
          {
            "oauth2": [
              "users:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/organisation": {
      "get": {
        "tags": [
          "Organisation"
        ],
        "operationId": "getOrganisation",
        "summary": "Retrieve the organisation",
        "description": "Returns the organisation your credentials belong to. **Admin only:** requires the\n`organisation:read` scope.\n",
        "security": [
          {
            "oauth2": [
              "organisation:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The organisation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Organisation"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "The credentials weren't issued to an organisation admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "forbidden",
                  "message": "Organisation details are only available to organisation admins.",
                  "details": null
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl https://api.advisoryai.com/v1/organisation \\\n  -H \"Authorization: Bearer $AAI_ACCESS_TOKEN\"\n"
          }
        ]
      }
    },
    "/clients/{client_id}/plannr": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ClientId"
        }
      ],
      "get": {
        "tags": [
          "PlannrCRM"
        ],
        "operationId": "getPlannrRecord",
        "summary": "Retrieve PlannrCRM data",
        "description": "Returns the client's PlannrCRM data, using AdvisoryAI field names (see\n[field mapping](#tag/PlannrCRM-field-mapping)).\n",
        "security": [
          {
            "oauth2": [
              "crm:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "sections",
            "in": "query",
            "description": "Comma-separated list of sections to return. Defaults to all.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "personal_details",
                  "addresses",
                  "employment",
                  "health",
                  "review",
                  "plans",
                  "expenditure"
                ]
              }
            },
            "example": [
              "personal_details",
              "plans"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The PlannrCRM data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlannrRecord"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/CrmNotLinked"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl \"https://api.advisoryai.com/v1/clients/21GYLb9n/plannr?sections=personal_details,plans\" \\\n  -H \"Authorization: Bearer $AAI_ACCESS_TOKEN\"\n"
          }
        ]
      }
    },
    "/clients/{client_id}/intelliflo": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ClientId"
        }
      ],
      "get": {
        "tags": [
          "Intelliflo"
        ],
        "operationId": "getIntellifloRecord",
        "summary": "Retrieve Intelliflo data",
        "description": "Returns the client's Intelliflo data, using AdvisoryAI field names (see\n[field mapping](#tag/Intelliflo-field-mapping)).\n",
        "security": [
          {
            "oauth2": [
              "crm:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "sections",
            "in": "query",
            "description": "Comma-separated list of sections to return. Defaults to all.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "personal_details",
                  "contact",
                  "addresses",
                  "employment",
                  "incomes",
                  "health",
                  "vulnerability",
                  "plans",
                  "assets",
                  "expenditure"
                ]
              }
            },
            "example": [
              "personal_details",
              "plans"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The Intelliflo data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IntellifloRecord"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/CrmNotLinked"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl \"https://api.advisoryai.com/v1/clients/21GYLb9n/intelliflo?sections=plans,assets\" \\\n  -H \"Authorization: Bearer $AAI_ACCESS_TOKEN\"\n"
          }
        ]
      }
    },
    "/clients/{client_id}/curo": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ClientId"
        }
      ],
      "get": {
        "tags": [
          "Curo and Xplan"
        ],
        "operationId": "getCuroRecord",
        "summary": "Retrieve Curo data",
        "description": "Returns the client's Curo data, using AdvisoryAI field names (see\n[field mapping](#tag/Curo-and-Xplan-field-mapping)).\n",
        "security": [
          {
            "oauth2": [
              "crm:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CuroXplanSections"
          }
        ],
        "responses": {
          "200": {
            "description": "The Curo data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CuroXplanRecord"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/CrmNotLinked"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl https://api.advisoryai.com/v1/clients/21GYLb9n/curo \\\n  -H \"Authorization: Bearer $AAI_ACCESS_TOKEN\"\n"
          }
        ]
      }
    },
    "/clients/{client_id}/curo-pp": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ClientId"
        }
      ],
      "get": {
        "tags": [
          "Curo and Xplan"
        ],
        "operationId": "getCuroPpRecord",
        "summary": "Retrieve Curo Power Platform data",
        "description": "Returns the client's Curo Power Platform data, in the same shape as Curo.\n`dependants` is always empty.\n",
        "security": [
          {
            "oauth2": [
              "crm:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CuroXplanSections"
          }
        ],
        "responses": {
          "200": {
            "description": "The Curo Power Platform data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CuroXplanRecord"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/CrmNotLinked"
          }
        }
      }
    },
    "/clients/{client_id}/xplan": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ClientId"
        }
      ],
      "get": {
        "tags": [
          "Curo and Xplan"
        ],
        "operationId": "getXplanRecord",
        "summary": "Retrieve Xplan data",
        "description": "Returns the client's Iress Xplan data, in the same shape as Curo.",
        "security": [
          {
            "oauth2": [
              "crm:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CuroXplanSections"
          }
        ],
        "responses": {
          "200": {
            "description": "The Xplan data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CuroXplanRecord"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/CrmNotLinked"
          }
        }
      }
    },
    "/clients/{client_id}/crm/summary": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ClientId"
        }
      ],
      "get": {
        "tags": [
          "CRM overview"
        ],
        "operationId": "getCrmSummary",
        "summary": "Retrieve the CRM summary",
        "description": "Figures AdvisoryAI derives from the client's CRM data, in the same shape for every\nCRM. Figures the CRM can't supply are `null` (see [CRM summary](#tag/CRM-overview)).\nEach CRM's field mapping page explains how the values are calculated.\n",
        "security": [
          {
            "oauth2": [
              "crm:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "source",
            "in": "query",
            "description": "Which CRM link to summarise. Needed only when the client is linked to more than\none CRM. Without it, the most recently synced link is used.\n",
            "schema": {
              "$ref": "#/components/schemas/CrmSource"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The CRM summary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CrmSummary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/CrmNotLinked"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl \"https://api.advisoryai.com/v1/clients/21GYLb9n/crm/summary?source=intelliflo\" \\\n  -H \"Authorization: Bearer $AAI_ACCESS_TOKEN\"\n"
          }
        ]
      }
    },
    "/clients/{client_id}/relationships": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ClientId"
        }
      ],
      "get": {
        "tags": [
          "Clients"
        ],
        "operationId": "listClientRelationships",
        "summary": "List a client's relationships",
        "description": "Returns the other clients linked to this client as a household: spouses or partners,\nparents and children. Each entry says what the related client is **to this client**,\nso a client's spouse appears as `spouse` and their son or daughter as `child`.\n",
        "security": [
          {
            "oauth2": [
              "clients:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The client's relationships.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "relationships"
                  ],
                  "properties": {
                    "relationships": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ClientRelationship"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl https://api.advisoryai.com/v1/clients/21GYLb9n/relationships \\\n  -H \"Authorization: Bearer $AAI_ACCESS_TOKEN\"\n"
          }
        ]
      }
    },
    "/clients/{client_id}/activity": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ClientId"
        }
      ],
      "get": {
        "tags": [
          "Clients"
        ],
        "operationId": "listClientActivity",
        "summary": "List a client's activity",
        "description": "Returns the client's timeline, newest first: meetings and reports created, updated or\ndeleted, and changes to the client's details. Activity for a client's linked household\nmembers (see [relationships](#operation/listClientRelationships)) is included, and each\nentry's `client` says whose it is.\n",
        "security": [
          {
            "oauth2": [
              "clients:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          },
          {
            "name": "resource_type",
            "in": "query",
            "description": "Only return activity on this kind of record.",
            "schema": {
              "type": "string",
              "enum": [
                "client",
                "meeting",
                "report"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of activity.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "activity",
                    "meta"
                  ],
                  "properties": {
                    "activity": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Activity"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginationMeta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl \"https://api.advisoryai.com/v1/clients/21GYLb9n/activity?resource_type=report\" \\\n  -H \"Authorization: Bearer $AAI_ACCESS_TOKEN\"\n"
          }
        ]
      }
    },
    "/meetings": {
      "get": {
        "tags": [
          "Meetings"
        ],
        "operationId": "listMeetings",
        "summary": "List meetings",
        "description": "Returns the organisation's meetings, most recent start time first. Filter by\n`client_id` for one client's meetings, or by `from_date` and `to_date` for a date range.\n",
        "security": [
          {
            "oauth2": [
              "meetings:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          },
          {
            "name": "client_id",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "example": "21GYLb9n"
          },
          {
            "name": "adviser_id",
            "in": "query",
            "description": "Only return meetings owned by this [user](#tag/Users).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/MeetingStatus"
            }
          },
          {
            "name": "from_date",
            "in": "query",
            "description": "Only return meetings starting on or after this date.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-01"
          },
          {
            "name": "to_date",
            "in": "query",
            "description": "Only return meetings starting on or before this date.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-31"
          },
          {
            "name": "sort_order",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of meetings.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "meetings",
                    "meta"
                  ],
                  "properties": {
                    "meetings": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Meeting"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginationMeta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl \"https://api.advisoryai.com/v1/meetings?client_id=21GYLb9n&status=completed\" \\\n  -H \"Authorization: Bearer $AAI_ACCESS_TOKEN\"\n"
          }
        ]
      }
    },
    "/meetings/{meeting_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MeetingId"
        }
      ],
      "get": {
        "tags": [
          "Meetings"
        ],
        "operationId": "getMeeting",
        "summary": "Retrieve a meeting",
        "security": [
          {
            "oauth2": [
              "meetings:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The meeting.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Meeting"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/meetings/{meeting_id}/transcript": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MeetingId"
        }
      ],
      "get": {
        "tags": [
          "Meetings"
        ],
        "operationId": "getMeetingTranscript",
        "summary": "Retrieve a meeting's transcript",
        "description": "Returns the meeting's transcript, split into speaker turns, with the adviser's notes\nand a link to the recording.\n\nA transcript exists once the meeting's `status` is `completed` (`has_transcript` is\n`true`). Before then this endpoint returns `404`.\n",
        "security": [
          {
            "oauth2": [
              "transcripts:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The transcript.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transcript"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl https://api.advisoryai.com/v1/meetings/Pw7cQ2nL/transcript \\\n  -H \"Authorization: Bearer $AAI_ACCESS_TOKEN\"\n"
          }
        ]
      }
    },
    "/templates": {
      "get": {
        "tags": [
          "Templates"
        ],
        "operationId": "listTemplates",
        "summary": "List templates",
        "description": "Returns the report templates available to the organisation: the firm's own templates\nand the standard templates AdvisoryAI provides.\n",
        "security": [
          {
            "oauth2": [
              "templates:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          },
          {
            "name": "report_type",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/ReportType"
            }
          },
          {
            "name": "owner",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "organisation",
                "advisoryai"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of templates.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "templates",
                    "meta"
                  ],
                  "properties": {
                    "templates": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Template"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginationMeta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl \"https://api.advisoryai.com/v1/templates?report_type=annual_review\" \\\n  -H \"Authorization: Bearer $AAI_ACCESS_TOKEN\"\n"
          }
        ]
      }
    },
    "/templates/{template_id}": {
      "parameters": [
        {
          "name": "template_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "Tb6yH3sK"
        }
      ],
      "get": {
        "tags": [
          "Templates"
        ],
        "operationId": "getTemplate",
        "summary": "Retrieve a template",
        "security": [
          {
            "oauth2": [
              "templates:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The template.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Template"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "oauth2": {
        "type": "oauth2",
        "description": "OAuth 2.0 client credentials. See [Authentication](#tag/Authentication).",
        "flows": {
          "clientCredentials": {
            "tokenUrl": "https://api.advisoryai.com/oauth/token",
            "scopes": {
              "clients:read": "List and retrieve clients, their relationships and activity.",
              "meetings:read": "List and retrieve meetings.",
              "transcripts:read": "Retrieve meeting transcripts, notes and recording links.",
              "documents:read": "List and retrieve documents.",
              "reports:read": "List and retrieve reports, and check report generation status.",
              "reports:write": "Request report generation and upload source documents.",
              "templates:read": "List and retrieve report templates.",
              "users:read": "List and retrieve users and their roles.",
              "organisation:read": "Retrieve the organisation's details (admin only).",
              "crm:read": "Retrieve CRM data and CRM summaries."
            }
          }
        }
      }
    },
    "parameters": {
      "ClientId": {
        "name": "client_id",
        "in": "path",
        "required": true,
        "description": "The AdvisoryAI client ID.",
        "schema": {
          "type": "string"
        },
        "example": "21GYLb9n"
      },
      "MeetingId": {
        "name": "meeting_id",
        "in": "path",
        "required": true,
        "description": "The AdvisoryAI meeting ID.",
        "schema": {
          "type": "string"
        },
        "example": "Pw7cQ2nL"
      },
      "CuroXplanSections": {
        "name": "sections",
        "in": "query",
        "description": "Comma-separated list of sections to return. Defaults to all.",
        "style": "form",
        "explode": false,
        "schema": {
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "personal_details",
              "contact",
              "addresses",
              "employment",
              "incomes",
              "expenses",
              "savings_investments",
              "pensions",
              "assets",
              "liabilities",
              "dependants"
            ]
          }
        },
        "example": [
          "personal_details",
          "savings_investments"
        ]
      },
      "Page": {
        "name": "page",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      },
      "PerPage": {
        "name": "per_page",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 25
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "A query parameter is invalid.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "bad_request",
              "message": "Invalid sort_field [name].",
              "details": null
            }
          }
        }
      },
      "Unauthorized": {
        "description": "The access token is missing, expired or revoked.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "unauthorized",
              "message": "The access token expired.",
              "details": null
            }
          }
        }
      },
      "Forbidden": {
        "description": "The token lacks the required scope.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "forbidden",
              "message": "This request requires the crm:read scope.",
              "details": null
            }
          }
        }
      },
      "NotFound": {
        "description": "No client with this ID exists in your organisation.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "not_found",
              "message": "Client not found.",
              "details": null
            }
          }
        }
      },
      "CrmNotLinked": {
        "description": "The client isn't linked to that CRM.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "crm_not_linked",
              "message": "This client isn't linked to an Intelliflo record.",
              "details": null
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "rate_limited",
              "message": "Rate limit exceeded. Retry in 30 seconds.",
              "details": null
            }
          }
        }
      },
      "PayloadTooLarge": {
        "description": "A file, or the whole request, is over the size limit.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "payload_too_large",
              "message": "aviva-valuation-oct-2026.pdf is larger than 30 MB.",
              "details": {
                "file": "aviva-valuation-oct-2026.pdf"
              }
            }
          }
        }
      },
      "UnsupportedMediaType": {
        "description": "A file's type isn't supported.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "unsupported_media_type",
              "message": "notes.pages isn't a supported file type.",
              "details": {
                "file": "notes.pages"
              }
            }
          }
        }
      },
      "ValidationFailed": {
        "description": "The request is well formed but can't be processed.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "validation_failed",
              "message": "Provide at least one of meeting_id, document_ids or files.",
              "details": {
                "fields": [
                  "meeting_id",
                  "document_ids",
                  "files"
                ]
              }
            }
          }
        }
      }
    },
    "schemas": {
      "TokenRequest": {
        "type": "object",
        "required": [
          "grant_type",
          "client_id",
          "client_secret"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "enum": [
              "client_credentials"
            ]
          },
          "client_id": {
            "type": "string",
            "example": "aai_cli_7c1e4f0b2d"
          },
          "client_secret": {
            "type": "string",
            "format": "password",
            "example": "aai_sec_••••••••"
          },
          "scope": {
            "type": "string",
            "description": "Space-separated scopes. Omit it to get every scope your credentials allow.",
            "example": "clients:read crm:read"
          }
        }
      },
      "TokenResponse": {
        "type": "object",
        "required": [
          "access_token",
          "token_type",
          "expires_in",
          "scope",
          "created_at"
        ],
        "properties": {
          "access_token": {
            "type": "string",
            "description": "Signed JWT bearer token.",
            "example": "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI..."
          },
          "token_type": {
            "type": "string",
            "enum": [
              "Bearer"
            ]
          },
          "expires_in": {
            "type": "integer",
            "description": "Lifetime in seconds.",
            "example": 3600
          },
          "scope": {
            "type": "string",
            "example": "clients:read crm:read"
          },
          "created_at": {
            "type": "integer",
            "description": "Unix timestamp when the token was issued.",
            "example": 1793611800
          }
        }
      },
      "RevokeRequest": {
        "type": "object",
        "required": [
          "token"
        ],
        "properties": {
          "token": {
            "type": "string",
            "description": "The access token to revoke."
          },
          "client_id": {
            "type": "string"
          },
          "client_secret": {
            "type": "string",
            "format": "password"
          }
        }
      },
      "OAuthError": {
        "type": "object",
        "description": "Errors from the OAuth endpoints follow RFC 6749.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "invalid_request",
              "invalid_client",
              "invalid_grant",
              "unsupported_grant_type",
              "invalid_scope"
            ]
          },
          "error_description": {
            "type": "string"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error",
          "message",
          "details"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Machine-readable error code.",
            "example": "not_found"
          },
          "message": {
            "type": "string",
            "description": "Readable explanation."
          },
          "details": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          }
        }
      },
      "PaginationMeta": {
        "type": "object",
        "required": [
          "current_page",
          "total_pages",
          "total_count"
        ],
        "properties": {
          "current_page": {
            "type": "integer",
            "example": 1
          },
          "next_page": {
            "type": "integer",
            "nullable": true,
            "example": 2
          },
          "prev_page": {
            "type": "integer",
            "nullable": true,
            "example": null
          },
          "total_pages": {
            "type": "integer",
            "example": 8
          },
          "total_count": {
            "type": "integer",
            "example": 187
          }
        }
      },
      "Money": {
        "type": "number",
        "format": "double",
        "nullable": true,
        "description": "Amount in pounds sterling."
      },
      "Address": {
        "type": "object",
        "properties": {
          "is_primary": {
            "type": "boolean",
            "nullable": true,
            "example": true
          },
          "label": {
            "type": "string",
            "nullable": true,
            "example": "Home"
          },
          "line_1": {
            "type": "string",
            "nullable": true,
            "example": "8 Harbour View"
          },
          "line_2": {
            "type": "string",
            "nullable": true,
            "example": null
          },
          "line_3": {
            "type": "string",
            "nullable": true,
            "example": null
          },
          "line_4": {
            "type": "string",
            "nullable": true,
            "example": null
          },
          "city": {
            "type": "string",
            "nullable": true,
            "example": "Bristol"
          },
          "county": {
            "type": "string",
            "nullable": true,
            "example": null
          },
          "postcode": {
            "type": "string",
            "nullable": true,
            "example": "BS1 6QH"
          },
          "country": {
            "type": "string",
            "nullable": true,
            "example": "United Kingdom"
          }
        }
      },
      "Client": {
        "type": "object",
        "required": [
          "id",
          "full_name",
          "crm_links"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "21GYLb9n"
          },
          "title": {
            "type": "string",
            "nullable": true,
            "example": "Mr"
          },
          "first_name": {
            "type": "string",
            "nullable": true,
            "example": "Carl"
          },
          "last_name": {
            "type": "string",
            "nullable": true,
            "example": "Johnson"
          },
          "full_name": {
            "type": "string",
            "example": "Carl Johnson"
          },
          "salutation": {
            "type": "string",
            "nullable": true,
            "example": "Carl"
          },
          "email": {
            "type": "string",
            "format": "email",
            "nullable": true,
            "example": "carl.johnson@example.com"
          },
          "date_of_birth": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "1978-05-14"
          },
          "marital_status": {
            "type": "string",
            "nullable": true,
            "example": "married"
          },
          "profession": {
            "type": "string",
            "nullable": true,
            "example": "Civil engineer"
          },
          "external_reference": {
            "type": "string",
            "nullable": true,
            "description": "The firm's own reference for the client, from the CRM.",
            "example": "CJ-00412"
          },
          "adviser_id": {
            "type": "string",
            "nullable": true,
            "description": "The [user](#tag/Users) who looks after the client.",
            "example": "Xk92PqLm"
          },
          "adviser_name": {
            "type": "string",
            "nullable": true,
            "example": "Jane Patel"
          },
          "crm_links": {
            "type": "array",
            "description": "The CRM records the client is linked to. Empty when the client isn't linked to a CRM.",
            "items": {
              "$ref": "#/components/schemas/ClientCrmLink"
            }
          },
          "counts": {
            "type": "object",
            "properties": {
              "reports": {
                "type": "integer",
                "example": 6
              },
              "documents": {
                "type": "integer",
                "example": 14
              },
              "meetings": {
                "type": "integer",
                "example": 3
              }
            }
          }
        }
      },
      "ClientRef": {
        "type": "object",
        "nullable": true,
        "properties": {
          "id": {
            "type": "string",
            "example": "21GYLb9n"
          },
          "name": {
            "type": "string",
            "example": "Carl Johnson"
          }
        }
      },
      "CrmSource": {
        "type": "string",
        "enum": [
          "plannr",
          "intelliflo",
          "curo",
          "curo_pp",
          "xplan"
        ],
        "description": "`plannr`: PlannrCRM. `intelliflo`: Intelliflo Office. `curo`: Curo. `curo_pp`: Curo\nPower Platform. `xplan`: Iress Xplan.\n"
      },
      "ClientCrmLink": {
        "type": "object",
        "required": [
          "source",
          "client_id",
          "last_synced_at"
        ],
        "properties": {
          "source": {
            "$ref": "#/components/schemas/CrmSource"
          },
          "client_id": {
            "type": "string",
            "description": "The client's ID in the CRM: a UUID for PlannrCRM, a number (as a string) for\nIntelliflo, and an opaque string for Curo, Curo Power Platform and Xplan.\n",
            "example": "9b1f3c2e-6a1d-4f7e-9c55-0d7a1e2b3c4d"
          },
          "last_synced_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "example": "2026-11-02T07:12:45Z"
          }
        }
      },
      "DocumentType": {
        "type": "string",
        "description": "What the document is, as classified in AdvisoryAI.",
        "enum": [
          "meeting_note",
          "meeting_transcript",
          "fact_find",
          "cashflow_model",
          "risk_profile",
          "provider_information",
          "old_provider_information",
          "new_provider_information",
          "fund_information",
          "charges_and_fees",
          "suitability_letter",
          "annual_review",
          "loa_pack_summary_pension",
          "loa_pack_summary_investment",
          "other_information"
        ]
      },
      "DocumentSource": {
        "type": "string",
        "description": "Where the document came from.",
        "enum": [
          "upload",
          "crm",
          "atlas",
          "sharepoint"
        ]
      },
      "Document": {
        "type": "object",
        "required": [
          "id",
          "name",
          "type",
          "document_type",
          "client"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "Rt5vN8wQ"
          },
          "document_no": {
            "type": "integer",
            "description": "Sequential document number within the organisation.",
            "example": 1042
          },
          "name": {
            "type": "string",
            "description": "File name.",
            "example": "Fact find - Carl Johnson.pdf"
          },
          "type": {
            "type": "string",
            "enum": [
              "document",
              "audio"
            ],
            "example": "document"
          },
          "document_type": {
            "$ref": "#/components/schemas/DocumentType"
          },
          "source": {
            "$ref": "#/components/schemas/DocumentSource"
          },
          "file_extension": {
            "type": "string",
            "nullable": true,
            "enum": [
              "txt",
              "pdf",
              "docx",
              "csv",
              "xls",
              "xlsx",
              "msg",
              null
            ],
            "example": "pdf"
          },
          "page_count": {
            "type": "integer",
            "nullable": true,
            "example": 12
          },
          "client": {
            "$ref": "#/components/schemas/ClientRef"
          },
          "uploaded_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the document was uploaded, or its original date when imported.",
            "example": "2026-10-14T13:22:05Z"
          },
          "transcribed": {
            "type": "boolean",
            "description": "Whether the document's text (or transcript) is available.",
            "example": true
          },
          "extracted": {
            "type": "boolean",
            "description": "Whether AdvisoryAI has extracted client data from the document.",
            "example": true
          },
          "failed": {
            "type": "boolean",
            "description": "Whether processing failed.",
            "example": false
          },
          "failure_reason": {
            "type": "string",
            "nullable": true,
            "example": null
          },
          "file_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "Signed download link. Expires after 1 hour.",
            "example": "https://files.advisoryai.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMi.../fact-find.pdf"
          }
        }
      },
      "DocumentDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Document"
          },
          {
            "type": "object",
            "properties": {
              "text": {
                "type": "string",
                "nullable": true,
                "description": "Extracted text, or the transcript for a recording. `null` until `transcribed` is true.",
                "example": "Client: Carl Johnson\nDate of birth: 14/05/1978\n..."
              }
            }
          }
        ]
      },
      "ReportType": {
        "type": "string",
        "enum": [
          "meeting_note",
          "suitability_letter",
          "annual_review",
          "fact_find",
          "provider_information",
          "loa_pack_summary_pension",
          "loa_pack_summary_investment",
          "dynamic"
        ],
        "description": "The kind of report. `dynamic` reports are generated from an ad-hoc request rather\nthan a fixed template.\n"
      },
      "Report": {
        "type": "object",
        "required": [
          "id",
          "title",
          "report_type",
          "status",
          "client",
          "generated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "Mz3kW7aB"
          },
          "title": {
            "type": "string",
            "example": "Carl Johnson Annual Review"
          },
          "report_type": {
            "$ref": "#/components/schemas/ReportType"
          },
          "status": {
            "type": "string",
            "enum": [
              "generating",
              "completed",
              "failed"
            ],
            "example": "completed"
          },
          "client": {
            "$ref": "#/components/schemas/ClientRef"
          },
          "adviser": {
            "$ref": "#/components/schemas/UserRef"
          },
          "template": {
            "$ref": "#/components/schemas/TemplateRef"
          },
          "meeting_id": {
            "type": "string",
            "nullable": true,
            "description": "The [meeting](#tag/Meetings) the report was generated from, if any.",
            "example": "Pw7cQ2nL"
          },
          "generated_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the report was requested.",
            "example": "2026-10-29T16:41:09Z"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "example": "2026-10-29T16:44:52Z"
          },
          "meeting_date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Date of the meeting the report was generated from, if any.",
            "example": "2026-10-28"
          },
          "pdf_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "Signed link to the latest PDF. Expires after 1 hour."
          },
          "docx_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "Signed link to the latest Word document. Expires after 1 hour."
          }
        }
      },
      "ReportDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Report"
          },
          {
            "type": "object",
            "properties": {
              "content_html": {
                "type": "string",
                "nullable": true,
                "description": "The generated report as HTML. `null` while `status` is `generating`.",
                "example": "<h1>Annual Review</h1><p>Carl's portfolio has grown by 6.2% since…</p>"
              }
            }
          }
        ]
      },
      "ReportGenerationJsonRequest": {
        "type": "object",
        "required": [
          "client_id",
          "template_id"
        ],
        "properties": {
          "client_id": {
            "type": "string",
            "description": "The client the report is for.",
            "example": "21GYLb9n"
          },
          "template_id": {
            "type": "string",
            "description": "The [template](#tag/Templates) to generate from. It decides the report type.",
            "example": "Tb6yH3sK"
          },
          "adviser_id": {
            "type": "string",
            "description": "The [user](#tag/Users) the report is written for. Defaults to the client's adviser.",
            "example": "Xk92PqLm"
          },
          "meeting_id": {
            "type": "string",
            "description": "A meeting with this client whose transcript should be used.",
            "example": "Pw7cQ2nL"
          },
          "document_ids": {
            "type": "array",
            "description": "Documents already on the client to use.",
            "maxItems": 50,
            "items": {
              "type": "string"
            },
            "example": [
              "Rt5vN8wQ"
            ]
          },
          "joint_client_ids": {
            "type": "array",
            "description": "Household members to cover in the same report, for example a spouse for a joint\nannual review. Each must be one of the client's\n[relationships](#operation/listClientRelationships).\n",
            "maxItems": 5,
            "items": {
              "type": "string"
            },
            "example": []
          }
        }
      },
      "ReportGenerationRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ReportGenerationJsonRequest"
          },
          {
            "type": "object",
            "properties": {
              "files": {
                "type": "array",
                "description": "New files to upload and use. Up to 10 files per request.\n\n- Documents: PDF, Word (`.docx`), Excel (`.xls`, `.xlsx`), CSV, plain text and\n  Outlook messages (`.msg`), up to 30 MB each.\n- Audio recordings: MP3, MP4, M4A, WAV, AAC, FLAC, WebM and Ogg, up to 200 MB\n  each. AdvisoryAI transcribes them first.\n",
                "maxItems": 10,
                "items": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          }
        ]
      },
      "ReportGenerationStatus": {
        "type": "string",
        "enum": [
          "queued",
          "processing_documents",
          "generating",
          "completed",
          "failed"
        ],
        "description": "| Status | Meaning |\n|---|---|\n| `queued` | Accepted, waiting to start. |\n| `processing_documents` | Reading uploaded documents and transcribing recordings. |\n| `generating` | Writing the report. |\n| `completed` | The report is ready. `report_id` and `download` are set. |\n| `failed` | The report couldn't be generated. `error` says why. |\n"
      },
      "ReportGeneration": {
        "type": "object",
        "required": [
          "id",
          "status",
          "client",
          "template",
          "documents",
          "report_id",
          "download",
          "error",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "Gn4hJ8kP"
          },
          "status": {
            "$ref": "#/components/schemas/ReportGenerationStatus"
          },
          "client": {
            "$ref": "#/components/schemas/ClientRef"
          },
          "template": {
            "$ref": "#/components/schemas/TemplateRef"
          },
          "adviser": {
            "$ref": "#/components/schemas/UserRef"
          },
          "meeting_id": {
            "type": "string",
            "nullable": true,
            "example": "Pw7cQ2nL"
          },
          "documents": {
            "type": "array",
            "description": "Every document the report uses, including uploads, with its processing status.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "example": "Ux2mF6rD"
                },
                "name": {
                  "type": "string",
                  "example": "Aviva valuation Oct 2026.pdf"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "processing",
                    "ready",
                    "failed"
                  ],
                  "example": "ready"
                },
                "failure_reason": {
                  "type": "string",
                  "nullable": true,
                  "example": null
                }
              }
            }
          },
          "report_id": {
            "type": "string",
            "nullable": true,
            "description": "The generated [report](#operation/getReport). Set when `status` is `completed`.",
            "example": "Mz3kW7aB"
          },
          "download": {
            "type": "object",
            "nullable": true,
            "description": "Signed download links. Set when `status` is `completed`.",
            "properties": {
              "pdf_url": {
                "type": "string",
                "format": "uri",
                "nullable": true
              },
              "docx_url": {
                "type": "string",
                "format": "uri",
                "nullable": true
              },
              "expires_at": {
                "type": "string",
                "format": "date-time",
                "example": "2026-11-04T11:19:47Z"
              }
            }
          },
          "error": {
            "type": "object",
            "nullable": true,
            "description": "Set when `status` is `failed`.",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "no_usable_sources",
                  "document_processing_failed",
                  "generation_failed"
                ],
                "example": "document_processing_failed"
              },
              "message": {
                "type": "string",
                "example": "None of the uploaded documents could be read."
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-11-04T10:15:02Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-11-04T10:19:47Z"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "example": "2026-11-04T10:19:47Z"
          }
        }
      },
      "UserRole": {
        "type": "string",
        "enum": [
          "admin",
          "advisor",
          "paraplanner",
          "paraplanner_plus",
          "supervisor",
          "support"
        ]
      },
      "User": {
        "type": "object",
        "required": [
          "id",
          "first_name",
          "last_name",
          "email",
          "role"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "Xk92PqLm"
          },
          "member_no": {
            "type": "integer",
            "description": "Sequential member number within the organisation.",
            "example": 7
          },
          "first_name": {
            "type": "string",
            "example": "Jane"
          },
          "last_name": {
            "type": "string",
            "example": "Patel"
          },
          "email": {
            "type": "string",
            "format": "email",
            "example": "jane.patel@examplewealth.co.uk"
          },
          "role": {
            "$ref": "#/components/schemas/UserRole"
          },
          "invitation_status": {
            "type": "string",
            "enum": [
              "accepted",
              "invited",
              "self_signed"
            ],
            "description": "`accepted`: joined from an invitation. `invited`: invited but not joined yet.\n`self_signed`: signed up directly.\n",
            "example": "accepted"
          },
          "plannr_connected": {
            "type": "boolean",
            "description": "Whether the user has an active PlannrCRM connection.",
            "example": true
          },
          "last_sign_in_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "example": "2026-11-01T08:14:33Z"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "example": "2024-06-03T10:00:00Z"
          }
        }
      },
      "UserRef": {
        "type": "object",
        "nullable": true,
        "properties": {
          "id": {
            "type": "string",
            "example": "Xk92PqLm"
          },
          "name": {
            "type": "string",
            "example": "Jane Patel"
          }
        }
      },
      "ClientRelationship": {
        "type": "object",
        "required": [
          "client",
          "relationship"
        ],
        "properties": {
          "client": {
            "$ref": "#/components/schemas/ClientRef"
          },
          "relationship": {
            "type": "string",
            "enum": [
              "spouse",
              "parent",
              "child"
            ],
            "description": "What the related client is to this client.",
            "example": "spouse"
          },
          "adviser": {
            "$ref": "#/components/schemas/UserRef"
          },
          "linked_at": {
            "type": "string",
            "format": "date-time",
            "example": "2025-02-11T10:05:00Z"
          }
        }
      },
      "Activity": {
        "type": "object",
        "required": [
          "id",
          "resource_type",
          "action",
          "occurred_at",
          "client"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "Ac8tR4vY"
          },
          "resource_type": {
            "type": "string",
            "enum": [
              "client",
              "meeting",
              "report"
            ],
            "example": "report"
          },
          "resource_id": {
            "type": "string",
            "nullable": true,
            "description": "The meeting, report or client the activity is about. `null` once that record is deleted.",
            "example": "Mz3kW7aB"
          },
          "action": {
            "type": "string",
            "enum": [
              "created",
              "updated",
              "deleted",
              "details_updated"
            ],
            "description": "`details_updated` means a section of the client's details changed in AdvisoryAI;\n`section` says which.\n",
            "example": "created"
          },
          "section": {
            "type": "string",
            "nullable": true,
            "description": "For `details_updated`, the section that changed, for example `personal`, `employment` or `health`.",
            "example": null
          },
          "title": {
            "type": "string",
            "nullable": true,
            "description": "The meeting subject, report title or client name at the time.",
            "example": "Carl Johnson Annual Review"
          },
          "client": {
            "$ref": "#/components/schemas/ClientRef"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-10-29T16:41:09Z"
          }
        }
      },
      "MeetingStatus": {
        "type": "string",
        "enum": [
          "upcoming",
          "ended",
          "transcribing",
          "completed",
          "failed"
        ],
        "description": "`upcoming`: scheduled and not started. `ended`: finished, recording not yet\nprocessed. `transcribing`: AdvisoryAI is transcribing the recording. `completed`: the\ntranscript is ready. `failed`: the meeting couldn't be recorded or transcribed.\n"
      },
      "Meeting": {
        "type": "object",
        "required": [
          "id",
          "subject",
          "status",
          "starts_at",
          "ends_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "Pw7cQ2nL"
          },
          "subject": {
            "type": "string",
            "example": "Annual review - Carl Johnson"
          },
          "status": {
            "$ref": "#/components/schemas/MeetingStatus"
          },
          "starts_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-10-28T10:00:00Z"
          },
          "ends_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-10-28T11:00:00Z"
          },
          "source": {
            "type": "string",
            "enum": [
              "google",
              "outlook",
              "offline"
            ],
            "description": "The calendar the meeting came from. `offline` is an in-person meeting recorded in\nthe AdvisoryAI app.\n",
            "example": "outlook"
          },
          "client": {
            "$ref": "#/components/schemas/ClientRef"
          },
          "adviser": {
            "$ref": "#/components/schemas/UserRef"
          },
          "has_recording": {
            "type": "boolean",
            "example": true
          },
          "has_transcript": {
            "type": "boolean",
            "description": "Whether a [transcript](#operation/getMeetingTranscript) is available.",
            "example": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-10-20T15:32:11Z"
          }
        }
      },
      "Transcript": {
        "type": "object",
        "required": [
          "meeting_id",
          "text",
          "utterances"
        ],
        "properties": {
          "meeting_id": {
            "type": "string",
            "example": "Pw7cQ2nL"
          },
          "client": {
            "$ref": "#/components/schemas/ClientRef"
          },
          "text": {
            "type": "string",
            "description": "The full transcript as plain text.",
            "example": "Jane Patel: Thanks for coming in, Carl. Shall we start with your pension?\nCarl Johnson: Yes..."
          },
          "utterances": {
            "type": "array",
            "description": "The transcript split into speaker turns, in order.",
            "items": {
              "$ref": "#/components/schemas/Utterance"
            }
          },
          "speakers": {
            "type": "array",
            "description": "The people identified in the meeting.",
            "items": {
              "type": "object",
              "properties": {
                "speaker": {
                  "type": "integer",
                  "example": 0
                },
                "name": {
                  "type": "string",
                  "nullable": true,
                  "example": "Jane Patel"
                }
              }
            }
          },
          "notes": {
            "type": "string",
            "nullable": true,
            "description": "Notes the adviser added to the meeting in AdvisoryAI.",
            "example": "Follow up on the transfer value from Aviva."
          },
          "recording_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "Signed link to the meeting recording. Expires after 1 hour."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-10-28T11:09:40Z"
          }
        }
      },
      "Utterance": {
        "type": "object",
        "required": [
          "speaker",
          "text"
        ],
        "properties": {
          "speaker": {
            "type": "integer",
            "description": "Speaker index. Matches an entry in `speakers`.",
            "example": 0
          },
          "speaker_name": {
            "type": "string",
            "nullable": true,
            "example": "Jane Patel"
          },
          "start_seconds": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "description": "Offset from the start of the recording.",
            "example": 12.4
          },
          "end_seconds": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 18.9
          },
          "text": {
            "type": "string",
            "example": "Thanks for coming in, Carl. Shall we start with your pension?"
          }
        }
      },
      "Template": {
        "type": "object",
        "required": [
          "id",
          "name",
          "report_type",
          "format",
          "owner"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "Tb6yH3sK"
          },
          "name": {
            "type": "string",
            "example": "Annual Review (Example Wealth)"
          },
          "report_type": {
            "$ref": "#/components/schemas/ReportType"
          },
          "format": {
            "type": "string",
            "enum": [
              "html",
              "docx"
            ],
            "description": "Whether reports from this template are edited as HTML or produced from a Word template.",
            "example": "docx"
          },
          "owner": {
            "type": "string",
            "enum": [
              "organisation",
              "advisoryai"
            ],
            "description": "`organisation`: the firm's own template. `advisoryai`: a standard template AdvisoryAI provides.",
            "example": "organisation"
          },
          "published": {
            "type": "boolean",
            "description": "Whether advisers can generate reports from the template.",
            "example": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "example": "2025-09-01T09:00:00Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-08-14T12:30:00Z"
          }
        }
      },
      "TemplateRef": {
        "type": "object",
        "nullable": true,
        "properties": {
          "id": {
            "type": "string",
            "example": "Tb6yH3sK"
          },
          "name": {
            "type": "string",
            "example": "Annual Review (Example Wealth)"
          }
        }
      },
      "Organisation": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "Hq4nB2xT"
          },
          "name": {
            "type": "string",
            "example": "Example Wealth Ltd"
          },
          "logo_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "Signed link to the organisation logo."
          },
          "country": {
            "type": "string",
            "nullable": true,
            "example": "United Kingdom"
          },
          "mfa_required": {
            "type": "boolean",
            "description": "Whether every user must use multi-factor authentication.",
            "example": true
          },
          "plannrcrm_enabled": {
            "type": "boolean",
            "description": "Whether PlannrCRM integration is enabled for the organisation.",
            "example": true
          },
          "subscription_status": {
            "type": "string",
            "enum": [
              "active",
              "trial",
              "trial_expired",
              "no_plan"
            ],
            "example": "active"
          },
          "user_count": {
            "type": "integer",
            "example": 18
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "example": "2024-06-03T10:00:00Z"
          }
        }
      },
      "PlannrRecord": {
        "type": "object",
        "required": [
          "client_id",
          "plannr_client_id",
          "last_synced_at"
        ],
        "properties": {
          "client_id": {
            "type": "string",
            "example": "21GYLb9n"
          },
          "plannr_client_id": {
            "type": "string",
            "format": "uuid",
            "example": "9b1f3c2e-6a1d-4f7e-9c55-0d7a1e2b3c4d"
          },
          "last_synced_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-11-02T07:12:45Z"
          },
          "personal_details": {
            "$ref": "#/components/schemas/PlannrPersonalDetails"
          },
          "addresses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PlannrAddress"
            }
          },
          "employment": {
            "$ref": "#/components/schemas/PlannrEmployment"
          },
          "health": {
            "$ref": "#/components/schemas/PlannrHealth"
          },
          "review": {
            "$ref": "#/components/schemas/PlannrReview"
          },
          "plans": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PlannrPlan"
            }
          },
          "expenditure": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PlannrExpenditure"
            }
          }
        }
      },
      "PlannrPersonalDetails": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "nullable": true,
            "example": "Mr",
            "x-plannr-field": "title"
          },
          "first_name": {
            "type": "string",
            "nullable": true,
            "example": "Carl",
            "x-plannr-field": "first_name"
          },
          "middle_name": {
            "type": "string",
            "nullable": true,
            "example": "Andrew",
            "x-plannr-field": "middle_name"
          },
          "last_name": {
            "type": "string",
            "nullable": true,
            "example": "Johnson",
            "x-plannr-field": "last_name"
          },
          "maiden_name": {
            "type": "string",
            "nullable": true,
            "example": null,
            "x-plannr-field": "maiden_name"
          },
          "salutation": {
            "type": "string",
            "nullable": true,
            "example": "Carl",
            "x-plannr-field": "nickname"
          },
          "email": {
            "type": "string",
            "format": "email",
            "nullable": true,
            "example": "carl.johnson@example.com"
          },
          "phone": {
            "type": "string",
            "nullable": true,
            "example": "07700 900123",
            "x-plannr-field": "mobile"
          },
          "date_of_birth": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "1978-05-14",
            "x-plannr-field": "date_of_birth"
          },
          "gender": {
            "type": "string",
            "nullable": true,
            "example": "male",
            "x-plannr-field": "gender"
          },
          "marital_status": {
            "type": "string",
            "nullable": true,
            "example": "married",
            "x-plannr-field": "marital_status"
          },
          "married_since": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2006-09-02",
            "x-plannr-field": "date_of_marriage"
          },
          "national_insurance_number": {
            "type": "string",
            "nullable": true,
            "example": "QQ123456C",
            "x-plannr-field": "ni_number"
          },
          "nationalities": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "United Kingdom"
            ],
            "x-plannr-field": "nationalities"
          },
          "has_will": {
            "type": "boolean",
            "nullable": true,
            "example": true,
            "x-plannr-field": "has_will"
          },
          "will_up_to_date": {
            "type": "boolean",
            "nullable": true,
            "example": false,
            "x-plannr-field": "is_will_accurate"
          }
        }
      },
      "PlannrAddress": {
        "allOf": [
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "example": "3f6a2d8c-1b7e-4a90-8c2d-6e5f4a3b2c1d"
              }
            }
          },
          {
            "$ref": "#/components/schemas/Address"
          }
        ]
      },
      "PlannrEmployment": {
        "type": "object",
        "nullable": true,
        "description": "The client's most recent Plannr employment record.",
        "properties": {
          "employment_status": {
            "type": "string",
            "nullable": true,
            "example": "employed",
            "x-plannr-field": "status"
          },
          "occupation": {
            "type": "string",
            "nullable": true,
            "example": "Civil Engineer",
            "x-plannr-field": "occupation_code"
          },
          "job_title": {
            "type": "string",
            "nullable": true,
            "example": "Senior Structural Engineer",
            "x-plannr-field": "job_title"
          },
          "employer": {
            "type": "string",
            "nullable": true,
            "example": "Harbour Engineering Ltd",
            "x-plannr-field": "employer"
          },
          "employer_address": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "x-plannr-field": "address"
          },
          "started_at": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2014-09-01",
            "x-plannr-field": "started_at"
          },
          "ended_at": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": null,
            "x-plannr-field": "ended_at"
          },
          "work_email": {
            "type": "string",
            "nullable": true,
            "x-plannr-field": "email_address.value"
          },
          "work_phone": {
            "type": "string",
            "nullable": true,
            "x-plannr-field": "phone_number.value"
          },
          "is_business_owner": {
            "type": "boolean",
            "nullable": true,
            "example": false,
            "x-plannr-field": "is_business_owner"
          },
          "percentage_of_business_owned": {
            "type": "number",
            "nullable": true,
            "x-plannr-field": "percentage_of_business_owned"
          },
          "business_year_end": {
            "type": "string",
            "nullable": true,
            "x-plannr-field": "business_year_end"
          },
          "business_value": {
            "$ref": "#/components/schemas/Money"
          },
          "profit": {
            "$ref": "#/components/schemas/Money"
          },
          "turnover_history": {
            "type": "array",
            "nullable": true,
            "x-plannr-field": "turnover_history",
            "items": {
              "type": "object",
              "properties": {
                "year": {
                  "type": "string",
                  "example": "2025"
                },
                "value": {
                  "$ref": "#/components/schemas/Money"
                }
              }
            }
          },
          "accountant": {
            "type": "object",
            "properties": {
              "company_name": {
                "type": "string",
                "nullable": true,
                "x-plannr-field": "accountant_company_name"
              },
              "email": {
                "type": "string",
                "nullable": true,
                "x-plannr-field": "accountant_email"
              },
              "phone": {
                "type": "string",
                "nullable": true,
                "x-plannr-field": "accountant_telephone_number"
              },
              "address": {
                "type": "string",
                "nullable": true,
                "x-plannr-field": "accountant_address"
              }
            }
          }
        }
      },
      "PlannrHealth": {
        "type": "object",
        "properties": {
          "vulnerable": {
            "type": "string",
            "nullable": true,
            "enum": [
              "yes",
              "possibly",
              "no",
              null
            ],
            "example": "possibly",
            "x-plannr-field": "is_vulnerable"
          },
          "vulnerability_details": {
            "type": "string",
            "nullable": true,
            "example": "Recently bereaved; prefers written follow-ups after meetings.",
            "x-plannr-field": "vulnerability_details"
          },
          "procedure_adaptation": {
            "type": "string",
            "nullable": true,
            "x-plannr-field": "procedure_adaptation"
          },
          "adaptation_notes": {
            "type": "string",
            "nullable": true,
            "x-plannr-field": "adaption_details_notes"
          },
          "client_preferences": {
            "type": "string",
            "nullable": true,
            "x-plannr-field": "client_preferences"
          },
          "current_health": {
            "type": "string",
            "nullable": true,
            "example": "good",
            "x-plannr-field": "current_state_of_health"
          },
          "health_explanation": {
            "type": "string",
            "nullable": true,
            "x-plannr-field": "state_of_health_explanation"
          },
          "smoker_status": {
            "type": "string",
            "nullable": true,
            "example": "never",
            "x-plannr-field": "smoker_status"
          },
          "smoker_since": {
            "type": "string",
            "nullable": true,
            "x-plannr-field": "smoker_since"
          },
          "cigarettes_daily": {
            "type": "integer",
            "nullable": true,
            "x-plannr-field": "cigarettes_daily"
          },
          "alcohol_units_weekly": {
            "type": "number",
            "nullable": true,
            "example": 6,
            "x-plannr-field": "units_of_alcohol_weekly"
          },
          "height": {
            "type": "number",
            "nullable": true,
            "x-plannr-field": "height.value"
          },
          "weight": {
            "type": "number",
            "nullable": true,
            "x-plannr-field": "weight.value"
          },
          "health_notes": {
            "type": "string",
            "nullable": true,
            "x-plannr-field": "health_notes"
          }
        }
      },
      "PlannrReview": {
        "type": "object",
        "properties": {
          "next_review_date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2027-03-01",
            "x-plannr-field": "next_review_date"
          },
          "last_review_date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2026-03-01",
            "x-plannr-field": "previous_review_date"
          }
        }
      },
      "PlannrPlan": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "example": "7d2c9e14-3a5b-4f6c-8d7e-9f0a1b2c3d4e",
            "x-plannr-field": "uuid"
          },
          "name": {
            "type": "string",
            "example": "Vanguard Stocks & Shares ISA",
            "x-plannr-field": "name"
          },
          "type": {
            "type": "string",
            "example": "investment",
            "x-plannr-field": "type"
          },
          "sub_type": {
            "type": "string",
            "nullable": true,
            "example": "isa_stocks_shares",
            "x-plannr-field": "sub_type"
          },
          "sub_type_label": {
            "type": "string",
            "nullable": true,
            "example": "Stocks & Shares ISA",
            "x-plannr-field": "sub_type_label"
          },
          "abstract_type": {
            "type": "string",
            "nullable": true,
            "example": "asset",
            "x-plannr-field": "abstract_type"
          },
          "provider": {
            "type": "string",
            "nullable": true,
            "example": "Vanguard",
            "x-plannr-field": "provider"
          },
          "policy_number": {
            "type": "string",
            "nullable": true,
            "example": "VG-10293847",
            "x-plannr-field": "policy_number"
          },
          "status": {
            "type": "string",
            "nullable": true,
            "example": "active",
            "x-plannr-field": "status"
          },
          "value": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "description": "Latest valuation in pounds.",
            "example": 84250,
            "x-plannr-field": "latest_valuations[0].value.amount"
          },
          "valued_at": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2026-10-31",
            "x-plannr-field": "latest_valuations[0].valued_at"
          },
          "opened_at": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2019-04-08",
            "x-plannr-field": "opened_at"
          },
          "inactive_at": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": null,
            "x-plannr-field": "inactive_at"
          },
          "inactive_reason": {
            "type": "string",
            "nullable": true,
            "example": null,
            "x-plannr-field": "inactive_reason"
          },
          "review_date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "x-plannr-field": "review_date"
          },
          "advice_area": {
            "type": "string",
            "nullable": true,
            "x-plannr-field": "advice_area"
          },
          "include_in_valuation": {
            "type": "boolean",
            "nullable": true,
            "example": true,
            "x-plannr-field": "include_in_client_valuation"
          },
          "under_advice": {
            "type": "boolean",
            "nullable": true,
            "example": true,
            "x-plannr-field": "under_advice"
          },
          "is_wrap_product": {
            "type": "boolean",
            "nullable": true,
            "example": false,
            "x-plannr-field": "is_wrap_product"
          }
        }
      },
      "PlannrExpenditure": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "x-plannr-field": "uuid"
          },
          "section": {
            "type": "string",
            "nullable": true,
            "example": "servicing_debts",
            "x-plannr-field": "section"
          },
          "name": {
            "type": "string",
            "nullable": true,
            "example": "Mortgage Repayments",
            "x-plannr-field": "name"
          },
          "amount": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "description": "Amount in pounds.",
            "example": 1450,
            "x-plannr-field": "amount.amount"
          }
        }
      },
      "CrmSummary": {
        "type": "object",
        "required": [
          "client_id",
          "crm",
          "crm_client_id",
          "last_synced_at"
        ],
        "properties": {
          "client_id": {
            "type": "string",
            "example": "21GYLb9n"
          },
          "crm": {
            "$ref": "#/components/schemas/CrmSource"
          },
          "crm_client_id": {
            "type": "string",
            "example": "9b1f3c2e-6a1d-4f7e-9c55-0d7a1e2b3c4d"
          },
          "portfolio_value": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 412600
          },
          "aum_value": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 368100
          },
          "cash_position": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 22500
          },
          "isa_portfolio_value": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 84250
          },
          "isa_contribution_ytd": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 6000
          },
          "pension_contribution_ytd": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 14400
          },
          "wrapper_types": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "ISA",
              "Pension",
              "Cash"
            ]
          },
          "next_review_date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2027-03-01"
          },
          "last_review_date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2026-03-01"
          },
          "review_frequency_months": {
            "type": "integer",
            "example": 12
          },
          "review_status": {
            "type": "string",
            "nullable": true,
            "enum": [
              "overdue",
              "due",
              "on_track",
              null
            ],
            "example": "on_track"
          },
          "vulnerability_flag": {
            "type": "boolean",
            "nullable": true,
            "example": true
          },
          "vulnerability_notes": {
            "type": "string",
            "nullable": true,
            "example": "Recently bereaved; prefers written follow-ups after meetings."
          },
          "data_quality_score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "example": 82
          },
          "last_synced_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-11-02T07:12:45Z"
          }
        }
      },
      "IntellifloRecord": {
        "type": "object",
        "required": [
          "client_id",
          "intelliflo_client_id",
          "last_synced_at"
        ],
        "properties": {
          "client_id": {
            "type": "string",
            "example": "21GYLb9n"
          },
          "intelliflo_client_id": {
            "type": "integer",
            "format": "int64",
            "example": 30418877
          },
          "last_synced_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-11-02T07:12:45Z"
          },
          "personal_details": {
            "$ref": "#/components/schemas/IntellifloPersonalDetails"
          },
          "contact": {
            "type": "object",
            "properties": {
              "email": {
                "type": "string",
                "format": "email",
                "nullable": true,
                "example": "carl.johnson@example.com"
              },
              "mobile": {
                "type": "string",
                "nullable": true,
                "example": "07700 900123"
              }
            }
          },
          "addresses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IntellifloAddress"
            }
          },
          "employment": {
            "$ref": "#/components/schemas/IntellifloEmployment"
          },
          "incomes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IntellifloIncome"
            }
          },
          "health": {
            "$ref": "#/components/schemas/IntellifloHealth"
          },
          "vulnerability": {
            "$ref": "#/components/schemas/IntellifloVulnerability"
          },
          "plans": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IntellifloPlan"
            }
          },
          "assets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IntellifloAsset"
            }
          },
          "expenditure": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IntellifloExpenditure"
            }
          }
        }
      },
      "IntellifloPersonalDetails": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "nullable": true,
            "example": "Mr"
          },
          "first_name": {
            "type": "string",
            "nullable": true,
            "example": "Carl"
          },
          "middle_name": {
            "type": "string",
            "nullable": true,
            "example": "James"
          },
          "last_name": {
            "type": "string",
            "nullable": true,
            "example": "Johnson"
          },
          "maiden_name": {
            "type": "string",
            "nullable": true,
            "example": null
          },
          "salutation": {
            "type": "string",
            "nullable": true,
            "example": "Carl"
          },
          "date_of_birth": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "1978-05-14"
          },
          "gender": {
            "type": "string",
            "nullable": true,
            "example": "Male"
          },
          "marital_status": {
            "type": "string",
            "nullable": true,
            "example": "Married"
          },
          "marital_status_since": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2006-08-19"
          },
          "nationality": {
            "type": "string",
            "nullable": true,
            "example": "United Kingdom"
          },
          "place_of_birth": {
            "type": "string",
            "nullable": true,
            "example": "Leeds"
          },
          "national_insurance_number": {
            "type": "string",
            "nullable": true,
            "example": "QQ123456C"
          },
          "has_will": {
            "type": "boolean",
            "nullable": true,
            "example": true
          },
          "will_up_to_date": {
            "type": "boolean",
            "nullable": true,
            "example": false
          },
          "power_of_attorney_granted": {
            "type": "boolean",
            "nullable": true,
            "example": false
          },
          "external_reference": {
            "type": "string",
            "nullable": true,
            "example": "CJ-00412"
          },
          "residency": {
            "type": "object",
            "properties": {
              "uk_resident": {
                "type": "boolean",
                "nullable": true,
                "example": true
              },
              "uk_domicile": {
                "type": "boolean",
                "nullable": true,
                "example": true
              },
              "expatriate": {
                "type": "boolean",
                "nullable": true,
                "example": false
              },
              "country_of_residence": {
                "type": "string",
                "nullable": true,
                "example": "United Kingdom"
              },
              "country_of_domicile": {
                "type": "string",
                "nullable": true,
                "example": "United Kingdom"
              }
            }
          }
        }
      },
      "IntellifloAddressFields": {
        "type": "object",
        "properties": {
          "line_1": {
            "type": "string",
            "nullable": true,
            "example": "12 Mill Lane"
          },
          "line_2": {
            "type": "string",
            "nullable": true,
            "example": "Headingley"
          },
          "line_3": {
            "type": "string",
            "nullable": true,
            "example": null
          },
          "city": {
            "type": "string",
            "nullable": true,
            "example": "Leeds"
          },
          "county": {
            "type": "string",
            "nullable": true,
            "description": "Intelliflo county code.",
            "example": "WYK"
          },
          "postcode": {
            "type": "string",
            "nullable": true,
            "example": "LS6 3HN"
          },
          "country": {
            "type": "string",
            "nullable": true,
            "description": "Country code.",
            "example": "GB"
          }
        }
      },
      "IntellifloAddress": {
        "allOf": [
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer",
                "format": "int64",
                "example": 88120453
              },
              "type": {
                "type": "string",
                "nullable": true,
                "example": "Home"
              }
            }
          },
          {
            "$ref": "#/components/schemas/IntellifloAddressFields"
          }
        ]
      },
      "IntellifloEmployment": {
        "type": "object",
        "nullable": true,
        "description": "The client's most recent Intelliflo employment.",
        "properties": {
          "employment_status": {
            "type": "string",
            "nullable": true,
            "example": "Employed"
          },
          "occupation": {
            "type": "string",
            "nullable": true,
            "example": "Civil engineer"
          },
          "intended_retirement_age": {
            "type": "integer",
            "nullable": true,
            "example": 65
          }
        }
      },
      "IntellifloIncome": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "example": 5512903
          },
          "category": {
            "type": "string",
            "nullable": true,
            "example": "BasicAnnualIncome"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "example": "Salary"
          },
          "gross_amount": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 5200
          },
          "net_amount": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 3780
          },
          "currency": {
            "type": "string",
            "nullable": true,
            "example": "GBP"
          },
          "frequency": {
            "type": "string",
            "nullable": true,
            "example": "Monthly"
          },
          "starts_on": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2019-04-01"
          }
        }
      },
      "IntellifloHealth": {
        "type": "object",
        "properties": {
          "is_smoker": {
            "type": "boolean",
            "nullable": true,
            "example": false
          },
          "smoked_in_last_12_months": {
            "type": "boolean",
            "nullable": true,
            "example": false
          },
          "in_good_health": {
            "type": "boolean",
            "nullable": true,
            "example": true
          }
        }
      },
      "IntellifloVulnerability": {
        "type": "object",
        "nullable": true,
        "properties": {
          "has_vulnerability": {
            "type": "string",
            "nullable": true,
            "enum": [
              "Yes",
              "No",
              "Potential",
              null
            ],
            "example": "Potential"
          },
          "type": {
            "type": "string",
            "nullable": true,
            "enum": [
              "Temporary",
              "Permanent",
              null
            ],
            "example": "Temporary"
          },
          "categories": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "Health",
                "LifeEvent",
                "Resilience",
                "Capability"
              ]
            },
            "example": [
              "LifeEvent"
            ]
          },
          "notes": {
            "type": "string",
            "nullable": true,
            "example": "Recently bereaved; prefers written follow-ups after meetings."
          },
          "assessed_on": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2026-09-12"
          },
          "review_on": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2027-03-12"
          },
          "client_portal_suitable": {
            "type": "boolean",
            "nullable": true,
            "example": true
          }
        }
      },
      "IntellifloPlan": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "example": 70231188
          },
          "plan_type": {
            "type": "string",
            "nullable": true,
            "example": "Stocks & Shares ISA"
          },
          "category": {
            "type": "string",
            "nullable": true,
            "description": "Intelliflo plan `discriminator`.",
            "example": "InvestmentPlan"
          },
          "wrapper": {
            "type": "string",
            "nullable": true,
            "enum": [
              "ISA",
              "Cash",
              "Investment",
              "Pension",
              "Annuity",
              "Insurance",
              "Mortgage",
              "EquityRelease",
              "Loan",
              null
            ],
            "example": "ISA"
          },
          "status": {
            "type": "string",
            "nullable": true,
            "example": "In force"
          },
          "active": {
            "type": "boolean",
            "example": true
          },
          "provider": {
            "type": "string",
            "nullable": true,
            "example": "Aviva"
          },
          "policy_number": {
            "type": "string",
            "nullable": true,
            "example": "ISA-448812"
          },
          "starts_on": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2015-04-06"
          },
          "value": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 84250
          },
          "currency": {
            "type": "string",
            "nullable": true,
            "example": "GBP"
          },
          "valued_on": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2026-10-31"
          },
          "contributions": {
            "type": "array",
            "description": "Only retrieved for cash, investment and pension plans.",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-05-01"
                },
                "amount": {
                  "type": "number",
                  "format": "double",
                  "example": 500
                }
              }
            }
          }
        }
      },
      "IntellifloAsset": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "example": 4410982
          },
          "asset_type": {
            "type": "string",
            "nullable": true,
            "example": "MainResidence"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "example": "Family home"
          },
          "owner": {
            "type": "string",
            "nullable": true,
            "example": "Joint"
          },
          "percent_ownership": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 50
          },
          "value": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "description": "The whole asset's value, before the client's ownership share.",
            "example": 525000
          },
          "currency": {
            "type": "string",
            "nullable": true,
            "example": "GBP"
          },
          "valued_on": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2026-06-30"
          },
          "purchased_on": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2011-09-01"
          },
          "purchase_price": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 310000
          },
          "is_loan": {
            "type": "boolean",
            "nullable": true,
            "example": false
          },
          "is_investment_property": {
            "type": "boolean",
            "nullable": true,
            "example": false
          },
          "address": {
            "$ref": "#/components/schemas/IntellifloAddressFields"
          }
        }
      },
      "IntellifloExpenditure": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "example": 9012774
          },
          "section": {
            "type": "string",
            "nullable": true,
            "enum": [
              "basic_essential",
              "quality_of_living",
              "non_essential",
              "liability",
              null
            ],
            "example": "basic_essential"
          },
          "category": {
            "type": "string",
            "nullable": true,
            "example": "CouncilTax"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "example": "Council tax"
          },
          "net_amount": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 185
          },
          "currency": {
            "type": "string",
            "nullable": true,
            "example": "GBP"
          },
          "frequency": {
            "type": "string",
            "nullable": true,
            "example": "Monthly"
          },
          "starts_on": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2020-04-01"
          }
        }
      },
      "CuroXplanRecord": {
        "type": "object",
        "required": [
          "client_id",
          "source",
          "crm_client_id",
          "last_synced_at"
        ],
        "properties": {
          "client_id": {
            "type": "string",
            "example": "21GYLb9n"
          },
          "source": {
            "type": "string",
            "enum": [
              "curo",
              "curo_pp",
              "xplan"
            ],
            "example": "curo"
          },
          "crm_client_id": {
            "type": "string",
            "description": "The client's ID in the CRM. Treat it as opaque.",
            "example": "c-58213"
          },
          "last_synced_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-11-02T07:12:45Z"
          },
          "personal_details": {
            "$ref": "#/components/schemas/CuroXplanPersonalDetails"
          },
          "contact": {
            "type": "object",
            "properties": {
              "email": {
                "type": "string",
                "format": "email",
                "nullable": true,
                "example": "carl.johnson@example.com"
              },
              "phone": {
                "type": "string",
                "nullable": true,
                "example": "07700 900123"
              }
            }
          },
          "addresses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CuroXplanAddress"
            }
          },
          "employment": {
            "$ref": "#/components/schemas/CuroXplanEmployment"
          },
          "incomes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CuroXplanIncome"
            }
          },
          "expenses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CuroXplanExpense"
            }
          },
          "savings_investments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CuroXplanSavingsInvestment"
            }
          },
          "pensions": {
            "type": "array",
            "description": "Defined contribution pensions. Only the latest valuation is retrieved.",
            "items": {
              "type": "object",
              "properties": {
                "value": {
                  "type": "number",
                  "format": "double",
                  "nullable": true,
                  "example": 196400
                }
              }
            }
          },
          "assets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CuroXplanAsset"
            }
          },
          "liabilities": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CuroXplanLiability"
            }
          },
          "dependants": {
            "type": "array",
            "description": "Curo and Xplan only. Always empty for Curo Power Platform.",
            "items": {
              "$ref": "#/components/schemas/CuroXplanDependant"
            }
          }
        }
      },
      "CuroXplanPersonalDetails": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "nullable": true,
            "example": "Mr"
          },
          "first_name": {
            "type": "string",
            "nullable": true,
            "example": "Carl"
          },
          "middle_names": {
            "type": "string",
            "nullable": true,
            "example": "James"
          },
          "last_name": {
            "type": "string",
            "nullable": true,
            "example": "Johnson"
          },
          "preferred_name": {
            "type": "string",
            "nullable": true,
            "example": "Carl"
          },
          "date_of_birth": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "1978-05-14"
          },
          "legal_sex": {
            "type": "string",
            "nullable": true,
            "enum": [
              "male",
              "female",
              null
            ],
            "example": "male"
          },
          "gender": {
            "type": "string",
            "nullable": true,
            "example": "male"
          },
          "marital_status": {
            "type": "string",
            "nullable": true,
            "example": "married"
          },
          "marital_status_since": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2006-08-19"
          },
          "national_insurance_number": {
            "type": "string",
            "nullable": true,
            "example": "QQ123456C"
          },
          "nationality": {
            "type": "string",
            "nullable": true,
            "example": "British"
          },
          "additional_nationalities": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": []
          },
          "country_of_residence": {
            "type": "string",
            "nullable": true,
            "example": "United Kingdom"
          },
          "country_of_domicile": {
            "type": "string",
            "nullable": true,
            "example": "United Kingdom"
          },
          "country_of_birth": {
            "type": "string",
            "nullable": true,
            "example": "United Kingdom"
          },
          "place_of_birth": {
            "type": "string",
            "nullable": true,
            "example": "Leeds"
          }
        }
      },
      "CuroXplanAddressFields": {
        "type": "object",
        "properties": {
          "line_1": {
            "type": "string",
            "nullable": true,
            "example": "12 Mill Lane"
          },
          "line_2": {
            "type": "string",
            "nullable": true,
            "example": "Headingley"
          },
          "line_3": {
            "type": "string",
            "nullable": true,
            "example": null
          },
          "line_4": {
            "type": "string",
            "nullable": true,
            "example": null
          },
          "city": {
            "type": "string",
            "nullable": true,
            "example": "Leeds"
          },
          "county": {
            "type": "string",
            "nullable": true,
            "example": "West Yorkshire"
          },
          "postcode": {
            "type": "string",
            "nullable": true,
            "example": "LS6 3HN"
          },
          "country": {
            "type": "string",
            "nullable": true,
            "example": "United Kingdom"
          }
        }
      },
      "CuroXplanAddress": {
        "allOf": [
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "example": "addr-1"
              },
              "is_primary": {
                "type": "boolean",
                "example": true
              },
              "move_in_date": {
                "type": "string",
                "format": "date",
                "nullable": true,
                "example": "2011-09-01"
              }
            }
          },
          {
            "$ref": "#/components/schemas/CuroXplanAddressFields"
          }
        ]
      },
      "CuroXplanEmployment": {
        "type": "object",
        "nullable": true,
        "description": "The client's first employment record in the CRM.",
        "properties": {
          "employment_status": {
            "type": "string",
            "nullable": true,
            "example": "employed"
          },
          "occupation": {
            "type": "string",
            "nullable": true,
            "example": "Civil engineer"
          },
          "job_title": {
            "type": "string",
            "nullable": true,
            "example": "Senior Engineer"
          },
          "employer": {
            "type": "string",
            "nullable": true,
            "example": "Northfield Infrastructure Ltd"
          },
          "started_at": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2014-02-03"
          },
          "ended_at": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": null
          },
          "employer_address": {
            "$ref": "#/components/schemas/CuroXplanAddressFields"
          }
        }
      },
      "CuroXplanIncome": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "inc-1"
          },
          "category": {
            "type": "string",
            "nullable": true,
            "example": "employment"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "example": "Salary"
          },
          "gross_amount": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 62000
          },
          "net_amount": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 45400
          },
          "currency": {
            "type": "string",
            "nullable": true,
            "description": "Currency name as the CRM holds it.",
            "example": "Pound Sterling"
          },
          "frequency": {
            "type": "string",
            "nullable": true,
            "example": "annually"
          },
          "starts_at": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2014-02-03"
          },
          "ends_at": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": null
          }
        }
      },
      "CuroXplanExpense": {
        "type": "object",
        "properties": {
          "category": {
            "type": "string",
            "nullable": true,
            "example": "housing"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "example": "Mortgage repayments"
          },
          "amount": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 1450
          },
          "currency": {
            "type": "string",
            "nullable": true,
            "example": "Pound Sterling"
          },
          "frequency": {
            "type": "string",
            "nullable": true,
            "example": "monthly"
          }
        }
      },
      "CuroXplanSavingsInvestment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "si-1"
          },
          "category": {
            "type": "string",
            "nullable": true,
            "example": "Stocks and Shares ISA"
          },
          "product_name": {
            "type": "string",
            "nullable": true,
            "example": "Aviva Stocks & Shares ISA"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "example": null
          },
          "wrapper": {
            "type": "string",
            "nullable": true,
            "description": "See *Wrapper classification* in the [field mapping](#tag/Curo-and-Xplan-field-mapping).",
            "example": "ISA"
          },
          "provider": {
            "type": "string",
            "nullable": true,
            "example": "Aviva"
          },
          "policy_number": {
            "type": "string",
            "nullable": true,
            "example": "ISA-448812"
          },
          "status": {
            "type": "string",
            "nullable": true,
            "example": "active"
          },
          "value": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 84250
          },
          "valued_on": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2026-10-31"
          },
          "currency": {
            "type": "string",
            "nullable": true,
            "example": "Pound Sterling"
          },
          "owners": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CuroXplanOwner"
            }
          }
        }
      },
      "CuroXplanAsset": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "as-1"
          },
          "asset_type": {
            "type": "string",
            "nullable": true,
            "example": "property"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "example": "Family home"
          },
          "value": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 525000
          },
          "valued_on": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2026-06-30"
          },
          "original_value": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 310000
          },
          "original_value_date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2011-09-01"
          },
          "currency": {
            "type": "string",
            "nullable": true,
            "example": "Pound Sterling"
          }
        }
      },
      "CuroXplanLiability": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "li-1"
          },
          "liability_type": {
            "type": "string",
            "nullable": true,
            "example": "mortgage"
          },
          "provider": {
            "type": "string",
            "nullable": true,
            "example": "Nationwide"
          },
          "policy_number": {
            "type": "string",
            "nullable": true,
            "example": "MTG-20931"
          },
          "outstanding_balance": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 182000
          },
          "balance_date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2026-09-30"
          },
          "interest_rate": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 4.29
          },
          "repayment_amount": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "example": 1450
          },
          "repayment_frequency": {
            "type": "string",
            "nullable": true,
            "example": "monthly"
          },
          "start_date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2021-09-01"
          },
          "end_date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2041-09-01"
          },
          "currency": {
            "type": "string",
            "nullable": true,
            "example": "Pound Sterling"
          },
          "owners": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CuroXplanOwner"
            }
          }
        }
      },
      "CuroXplanDependant": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "nullable": true,
            "example": "Ella Johnson"
          },
          "relationship": {
            "type": "string",
            "nullable": true,
            "example": "Daughter"
          },
          "date_of_birth": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2012-07-22"
          }
        }
      },
      "CuroXplanOwner": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "c-58213"
          },
          "name": {
            "type": "string",
            "example": "Carl Johnson"
          }
        }
      }
    }
  }
}
