# Lovelio API reference

Recruitment automation API. Create jobs, manage candidates, run ad-hoc actions, integrate with your tools.

Generated from the OpenAPI 3.1 document at https://lovelio.ai/api/v1/openapi.json.
API version 1.0.0. 171 paths, 252 operations.

This is the plain-markdown copy of the reference rendered at https://lovelio.ai/docs/api.

## Fetch less than all of this

| URL | What you get |
| --- | --- |
| `https://lovelio.ai/docs/api.md` | Everything: this header plus every operation in full. |
| `https://lovelio.ai/docs/api.md?index=1` | This header plus the endpoint index. No per-operation detail. |
| `https://lovelio.ai/docs/api.md?tag=Jobs` | This header plus one section in full. Comma-separate for several: `?tag=Jobs,Applications`. |
| `https://lovelio.ai/api/v1/openapi.json` | The machine-readable source this file is generated from. |

Tags: System, Accounts, Jobs, Job Ads, Candidates, Candidate Imports, Applications, Interviews, Submissions, Placements, Contractor Documents, Quotas, Marketplace, Webhooks, Batch, Activities, Documents, Tasks, Outreach, Talent Pools, Review Queue, Forms, Referees, Integrations, Chat Integrations, Analytics, Calendar, Scheduled Emails, Workflow Rules, Email Templates, Stages, Distribution, Candidate Evidence, Commands, Clients, Specs, Search.

## Base URL

Docs live on lovelio.ai. The API does not. Every workspace belongs to a region and an API key only works on its own region's host. Calling `lovelio.ai/api/v1/...` returns a JSON error naming all three hosts rather than data.

| Region | Base URL |
| --- | --- |
| Production (US (N. California)) | `https://us.lovelio.ai/api/v1` |
| Production (EU (London)) | `https://eu.lovelio.ai/api/v1` |
| Production (ANZ (Sydney)) | `https://anz.lovelio.ai/api/v1` |

A workspace's exact host is shown in Settings > API keys.

## Authentication

```http
Authorization: Bearer sk_live_...
```

API key, sent as Authorization: Bearer <key>. Three key types: sk_live_ (production), sk_test_ (development - same workspace, same data, marked as a test key; use a separate workspace if you need isolated test data), and sk_trial_ (issued at signup, expires 7 days later - swap to a live key from Settings > API keys). Keys are server-generated, shown once at creation, and scoped per permission (or admin for everything). The regional /api/mcp endpoint also accepts these keys through the Authorization header, with the same scopes and record access. Current key status and scopes are rechecked on every request. Lovelio Connect apps authenticate with the OAuth access token from the connect flow instead (Authorization: Bearer lc_at_...; personal MCP clients use mcp_at_...) - it carries exactly the scopes the agency approved, enforced on every request. Personal actions use the current membership role and record access; direct account administration requires an admin. Consultant OAuth scopes also follow the current agency rule. Additional scopes require fresh consent. Money is gated at FIELD level, not route level: the three scopes placements:financials:read, clients:financials:read and marketplace:financials:read unlock salary, fee, contract rates, expected GP, commission percents, the client fee schedule and split-fee deal amounts. Without them those fields return null and everything else on the record still comes through - a read never 403s for want of a money scope. admin satisfies all three. A fourth field gate, placements:contracting:read, unlocks the timesheet and payroll hand-off on a contract placement: the client's payment terms and how the contractor is paid (Australia: PAYG or ABN, and the ABN). Requesting any of them puts a Connect app in the elevated review tier. See /docs/agents/build-an-integration.

## Pagination and shared parameters

Paginated list endpoints declare the parameters they support below. For cursor-based lists, read `meta.next_cursor` from a response and pass it back as `after`. Bounded catalogue and configuration lists return their full result and do not take a cursor.

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `limit` | query | integer, 1-100, default 25 | Rows per page. Clamped to the 1-100 range rather than rejected, so asking for 500 returns 100 - read meta.has_more and page with the cursor. Unparseable values fall back to 25. |
| `after` | query | string | Cursor for forward pagination |
| `created_after` | query | string (date-time) | Only rows created at or after this ISO 8601 date or datetime (inclusive). Malformed values are a 422, never silently ignored. |
| `created_before` | query | string (date-time) | Only rows created at or before this ISO 8601 date or datetime (inclusive). Malformed values are a 422, never silently ignored. |
| `Idempotency-Key` | header | string | Unique key for one logical mutation, scoped to the account. Completed results expire after 24 hours; uncertain executions retain their safety reservation until reconciled. The key is claimed before side effects. Identical completed retries replay the saved response. Concurrent retries return 409 IDEMPOTENCY_IN_PROGRESS. Changed JSON, form fields, record ids, filenames, content types, or file bytes return 409 IDEMPOTENCY_KEY_REUSED. Random multipart boundaries do not affect matching. |

## Rate limit headers

| Header | Type | Description |
| --- | --- | --- |
| `X-RateLimit-Limit` | integer | Requests allowed per minute |
| `X-RateLimit-Remaining` | integer | Requests remaining in window |
| `X-RateLimit-Reset` | integer | Unix timestamp when window resets |
| `X-Idempotency-Operation-ID` | string (uuid) | Durable operation identity for replay or reconciliation. |

## Guides

- https://lovelio.ai/docs/agents - what an agent can drive end to end
- https://lovelio.ai/docs/agents/top-10-tasks - runnable examples for the ten most common calls
- https://lovelio.ai/docs/agents/build-an-integration - sandbox agency, test API key, live webhook testing
- https://lovelio.ai/docs/agents/error-handling - error envelope, request IDs, retries, idempotency keys
- https://lovelio.ai/docs/agents/webhooks - subscribing and verifying HMAC signatures
- https://lovelio.ai/docs/agents/sdks - single-file TypeScript and Python clients
- https://lovelio.ai/docs/api/changelog - public API release history
- https://lovelio.ai/llms-full.txt - all of the above inlined in one fetch

## Endpoint index

Filtered to: Contractor Documents.

### Contractor Documents

Contractor documents: right to work, checks, licences and insurance on the person, and what each contract needs

- `GET /candidates/{id}/papers` - List a candidate's documents
- `POST /candidates/{id}/papers` - Save a document
- `POST /candidates/{id}/contractor-page-link` - Get a contractor page link
- `GET /placements/{id}/papers` - Check a placement's documents
- `PUT /placements/{id}/papers/{kind}/waiver` - Mark a document not needed
- `DELETE /placements/{id}/papers/{kind}/waiver` - Ask for a document again

---

## Contractor Documents

Contractor documents: right to work, checks, licences and insurance on the person, and what each contract needs

### GET /candidates/{id}/papers

**List a candidate's documents**

Every document on file for the person, newest first: right to work, checks, licences, insurance, each with the day it stops counting.

Operation ID: `listCandidatePapers`

Scopes: `candidates:read`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes |  |

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `limit` | integer, 1-100, default 25 | no | Rows per page. Clamped to the 1-100 range rather than rejected, so asking for 500 returns 100 - read meta.has_more and page with the cursor. Unparseable values fall back to 25. |
| `after` | string | no | Cursor for forward pagination |

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `200` | Documents on file | { success, data: array of Paper, meta } |
| `400` | Invalid request. Correct the named field or add the required Idempotency-Key. | Error |
| `401` | AUTHENTICATION_REQUIRED: supply the full key or token on the correct environment and region. The same code covers a credential that is no longer usable: a revoked key and an authorising user whose access an admin has paused. The message says which. | Error |
| `402` | PREMIUM_REQUIRED: the agency has no active plan, so the whole account is closed to people and agents alike until an admin signs in at lovelio.ai and turns it back on. Stop retrying and tell whoever runs the integration. | Error |
| `403` | Access refused. Check the error code and the required permissions. ACCOUNT_SUSPENDED means an admin at Lovelio has made the whole agency dark: nothing will work until that is lifted, and no credential change helps. CONNECTION_PENDING_APPROVAL means a consultant's connection is waiting for an admin at the agency to approve it: the credential is valid, so do not refresh or reconnect; the person gets an email when it is live. | Error |
| `404` | Candidate not found | Error |
| `405` | METHOD_NOT_ALLOWED: use one of the methods in the Allow header. | Error |
| `423` | RESTORE_IN_PROGRESS: the agency is being put back to an earlier time, which takes seconds. Nothing was written. Wait for the Retry-After header (60 seconds), then send the same request again. | Error |
| `429` | RATE_LIMIT_EXCEEDED: wait for Retry-After seconds before retrying. DAILY_LIMIT_REACHED: the key has accessed today's allowance of candidates. The allowance is set by the agency (200 a day by default for a key acting for a non-admin, and an agency can raise it, lower it, or choose to be told rather than stop); records already returned today do not count again, and the allowance resets with the person's day. | Error |
| `500` | INTERNAL_ERROR: retry with backoff. An idempotent POST must keep the same key. | Error |

### POST /candidates/{id}/papers

**Save a document**

Record a document you have checked. Saving the same kind in the same country again replaces it. Pass placement_id to take the country from that contract (and to hold an engagement document on it; that also needs placements:write), or pass country. Maps to action save_paper.

Operation ID: `savePaper`

Scopes: `candidates:write`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes |  |

**Header parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Idempotency-Key` | string | yes | Unique key for one logical mutation, scoped to the account. Completed results expire after 24 hours; uncertain executions retain their safety reservation until reconciled. The key is claimed before side effects. Identical completed retries replay the saved response. Concurrent retries return 409 IDEMPOTENCY_IN_PROGRESS. Changed JSON, form fields, record ids, filenames, content types, or file bytes return 409 IDEMPOTENCY_KEY_REUSED. Random multipart boundaries do not affect matching. |

**Request body** (`application/json`, required)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `kind` | string | yes |  |
| `country` | string | no | ISO country code. Required unless placement_id is given. |
| `placement_id` | string | no | pla_ id. Must be a placement for this candidate. |
| `details` | string | no |  |
| `issued_on` | string (date) | no | The date on the document, YYYY-MM-DD. |
| `expires_on` | string (date) | no | The printed expiry, YYYY-MM-DD. |
| `document_id` | string | no | doc_ id of a file already on the candidate. |

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `201` | Document saved | { success, data: Paper } |
| `400` | Invalid request. Correct the named field or add the required Idempotency-Key. | Error |
| `401` | AUTHENTICATION_REQUIRED: supply the full key or token on the correct environment and region. The same code covers a credential that is no longer usable: a revoked key and an authorising user whose access an admin has paused. The message says which. | Error |
| `402` | PREMIUM_REQUIRED: the agency has no active plan, so the whole account is closed to people and agents alike until an admin signs in at lovelio.ai and turns it back on. Stop retrying and tell whoever runs the integration. | Error |
| `403` | Access refused. Check the error code and the required permissions. ACCOUNT_SUSPENDED means an admin at Lovelio has made the whole agency dark: nothing will work until that is lifted, and no credential change helps. CONNECTION_PENDING_APPROVAL means a consultant's connection is waiting for an admin at the agency to approve it: the credential is valid, so do not refresh or reconnect; the person gets an email when it is live. | Error |
| `404` | Candidate or placement not found | Error |
| `405` | METHOD_NOT_ALLOWED: use one of the methods in the Allow header. | Error |
| `409` | The placement is for someone else Idempotency errors: IDEMPOTENCY_IN_PROGRESS means retry the identical request with the same key after Retry-After. IDEMPOTENCY_KEY_REUSED needs a fresh key for a distinct request. IDEMPOTENCY_OUTCOME_UNKNOWN must be reconciled before any new key is used. | Error |
| `422` | Unknown kind for that country, a bad date, or no country | Error |
| `423` | RESTORE_IN_PROGRESS: the agency is being put back to an earlier time, which takes seconds. Nothing was written. Wait for the Retry-After header (60 seconds), then send the same request again. | Error |
| `429` | RATE_LIMIT_EXCEEDED: wait for Retry-After seconds before retrying. DAILY_LIMIT_REACHED: the key has accessed today's allowance of candidates. The allowance is set by the agency (200 a day by default for a key acting for a non-admin, and an agency can raise it, lower it, or choose to be told rather than stop); records already returned today do not count again, and the allowance resets with the person's day. | Error |
| `500` | INTERNAL_ERROR: retry with backoff. An idempotent POST must keep the same key. | Error |
| `503` | Idempotency storage error: retry only the identical request with the same key. | Error |

### POST /candidates/{id}/contractor-page-link

**Get a contractor page link**

A fresh link to the contractor's own page, opened on a phone with no password: they set when they are free from, their day rate and their preferred areas, answer an open ask, and upload the documents their contracts need. An upload lands in their documents with status to_check until a person saves it as checked. Only a hash of the link is kept; it works for 60 days. Every availability ask, extension ask and document chase already carries one. Maps to action get_contractor_page_link.

Operation ID: `getContractorPageLink`

Scopes: `candidates:write`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes |  |

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `201` | The link | { success, data: object } |
| `400` | Invalid request. Correct the named field or add the required Idempotency-Key. | Error |
| `401` | AUTHENTICATION_REQUIRED: supply the full key or token on the correct environment and region. The same code covers a credential that is no longer usable: a revoked key and an authorising user whose access an admin has paused. The message says which. | Error |
| `402` | PREMIUM_REQUIRED: the agency has no active plan, so the whole account is closed to people and agents alike until an admin signs in at lovelio.ai and turns it back on. Stop retrying and tell whoever runs the integration. | Error |
| `403` | Access refused. Check the error code and the required permissions. ACCOUNT_SUSPENDED means an admin at Lovelio has made the whole agency dark: nothing will work until that is lifted, and no credential change helps. CONNECTION_PENDING_APPROVAL means a consultant's connection is waiting for an admin at the agency to approve it: the credential is valid, so do not refresh or reconnect; the person gets an email when it is live. | Error |
| `404` | Candidate not found | Error |
| `405` | METHOD_NOT_ALLOWED: use one of the methods in the Allow header. | Error |
| `423` | RESTORE_IN_PROGRESS: the agency is being put back to an earlier time, which takes seconds. Nothing was written. Wait for the Retry-After header (60 seconds), then send the same request again. | Error |
| `429` | RATE_LIMIT_EXCEEDED: wait for Retry-After seconds before retrying. DAILY_LIMIT_REACHED: the key has accessed today's allowance of candidates. The allowance is set by the agency (200 a day by default for a key acting for a non-admin, and an agency can raise it, lower it, or choose to be told rather than stop); records already returned today do not count again, and the allowance resets with the person's day. | Error |
| `500` | INTERNAL_ERROR: retry with backoff. An idempotent POST must keep the same key. | Error |

### GET /placements/{id}/papers

**Check a placement's documents**

What this contract needs and where each document stands: current, missing, expired, ending before the contract does, or marked not needed. Maps to action get_placement_papers.

Operation ID: `getPlacementPapers`

Scopes: `placements:read`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes |  |

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `200` | The documents check | { success, data: PlacementPapers } |
| `400` | Invalid request. Correct the named field or add the required Idempotency-Key. | Error |
| `401` | AUTHENTICATION_REQUIRED: supply the full key or token on the correct environment and region. The same code covers a credential that is no longer usable: a revoked key and an authorising user whose access an admin has paused. The message says which. | Error |
| `402` | PREMIUM_REQUIRED: the agency has no active plan, so the whole account is closed to people and agents alike until an admin signs in at lovelio.ai and turns it back on. Stop retrying and tell whoever runs the integration. | Error |
| `403` | Access refused. Check the error code and the required permissions. ACCOUNT_SUSPENDED means an admin at Lovelio has made the whole agency dark: nothing will work until that is lifted, and no credential change helps. CONNECTION_PENDING_APPROVAL means a consultant's connection is waiting for an admin at the agency to approve it: the credential is valid, so do not refresh or reconnect; the person gets an email when it is live. | Error |
| `404` | Placement not found | Error |
| `405` | METHOD_NOT_ALLOWED: use one of the methods in the Allow header. | Error |
| `423` | RESTORE_IN_PROGRESS: the agency is being put back to an earlier time, which takes seconds. Nothing was written. Wait for the Retry-After header (60 seconds), then send the same request again. | Error |
| `429` | RATE_LIMIT_EXCEEDED: wait for Retry-After seconds before retrying. DAILY_LIMIT_REACHED: the key has accessed today's allowance of candidates. The allowance is set by the agency (200 a day by default for a key acting for a non-admin, and an agency can raise it, lower it, or choose to be told rather than stop); records already returned today do not count again, and the allowance resets with the person's day. | Error |
| `500` | INTERNAL_ERROR: retry with backoff. An idempotent POST must keep the same key. | Error |

### PUT /placements/{id}/papers/{kind}/waiver

**Mark a document not needed**

Stop asking for one document on this contract only; every other contract still asks. Right to work can never be marked not needed: everyone working needs it. Maps to action waive_paper.

Operation ID: `waivePaper`

Scopes: `placements:write`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes |  |
| `kind` | string | yes | The document word, e.g. police_check. |

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `200` | Marked not needed | PaperWaiverResult |
| `400` | Invalid request. Correct the named field or add the required Idempotency-Key. | Error |
| `401` | AUTHENTICATION_REQUIRED: supply the full key or token on the correct environment and region. The same code covers a credential that is no longer usable: a revoked key and an authorising user whose access an admin has paused. The message says which. | Error |
| `402` | PREMIUM_REQUIRED: the agency has no active plan, so the whole account is closed to people and agents alike until an admin signs in at lovelio.ai and turns it back on. Stop retrying and tell whoever runs the integration. | Error |
| `403` | Access refused. Check the error code and the required permissions. ACCOUNT_SUSPENDED means an admin at Lovelio has made the whole agency dark: nothing will work until that is lifted, and no credential change helps. CONNECTION_PENDING_APPROVAL means a consultant's connection is waiting for an admin at the agency to approve it: the credential is valid, so do not refresh or reconnect; the person gets an email when it is live. | Error |
| `404` | Placement not found | Error |
| `405` | METHOD_NOT_ALLOWED: use one of the methods in the Allow header. | Error |
| `422` | Right to work, or a kind Lovelio does not know | Error |
| `423` | RESTORE_IN_PROGRESS: the agency is being put back to an earlier time, which takes seconds. Nothing was written. Wait for the Retry-After header (60 seconds), then send the same request again. | Error |
| `429` | RATE_LIMIT_EXCEEDED: wait for Retry-After seconds before retrying. DAILY_LIMIT_REACHED: the key has accessed today's allowance of candidates. The allowance is set by the agency (200 a day by default for a key acting for a non-admin, and an agency can raise it, lower it, or choose to be told rather than stop); records already returned today do not count again, and the allowance resets with the person's day. | Error |
| `500` | INTERNAL_ERROR: retry with backoff. An idempotent POST must keep the same key. | Error |

### DELETE /placements/{id}/papers/{kind}/waiver

**Ask for a document again**

Undo a not needed mark: the contract asks for the document again. Maps to action waive_paper with waived false.

Operation ID: `unwaivePaper`

Scopes: `placements:write`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes |  |
| `kind` | string | yes |  |

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `200` | Needed again | PaperWaiverResult |
| `400` | Invalid request. Correct the named field or add the required Idempotency-Key. | Error |
| `401` | AUTHENTICATION_REQUIRED: supply the full key or token on the correct environment and region. The same code covers a credential that is no longer usable: a revoked key and an authorising user whose access an admin has paused. The message says which. | Error |
| `402` | PREMIUM_REQUIRED: the agency has no active plan, so the whole account is closed to people and agents alike until an admin signs in at lovelio.ai and turns it back on. Stop retrying and tell whoever runs the integration. | Error |
| `403` | Access refused. Check the error code and the required permissions. ACCOUNT_SUSPENDED means an admin at Lovelio has made the whole agency dark: nothing will work until that is lifted, and no credential change helps. CONNECTION_PENDING_APPROVAL means a consultant's connection is waiting for an admin at the agency to approve it: the credential is valid, so do not refresh or reconnect; the person gets an email when it is live. | Error |
| `404` | Placement not found | Error |
| `405` | METHOD_NOT_ALLOWED: use one of the methods in the Allow header. | Error |
| `423` | RESTORE_IN_PROGRESS: the agency is being put back to an earlier time, which takes seconds. Nothing was written. Wait for the Retry-After header (60 seconds), then send the same request again. | Error |
| `429` | RATE_LIMIT_EXCEEDED: wait for Retry-After seconds before retrying. DAILY_LIMIT_REACHED: the key has accessed today's allowance of candidates. The allowance is set by the agency (200 a day by default for a key acting for a non-admin, and an agency can raise it, lower it, or choose to be told rather than stop); records already returned today do not count again, and the allowance resets with the person's day. | Error |
| `500` | INTERNAL_ERROR: retry with backoff. An idempotent POST must keep the same key. | Error |

## Schemas

Objects referenced by the operations above.

### Error

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `success` | boolean (`false`) | yes |  |
| `data` | any | yes | Usually null. Some errors include structured recovery details, such as remaining draft fields. |
| `meta` | Meta | yes |  |
| `error` | object | yes |  |
| `error.code` | string | yes |  |
| `error.message` | string | yes |  |
| `error.field` | string | no |  |
| `error.docs` | string (uri) | yes |  |
| `error.request_id` | string | yes | Same request ID as meta.request_id. |

### Meta

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `request_id` | string | yes |  |
| `count` | integer | no |  |
| `has_more` | boolean | no |  |
| `next_cursor` | string \| null | no |  |

### Paper

A document the agency has checked for a person: right to work, a police check, a licence, insurance. It lives on the person and counts in one country, so their next contract there re-uses it.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | no |  |
| `candidate_id` | string | no | cnd_ id. |
| `placement_id` | string \| null | no | pla_ id. Set only for an engagement document, which answers one contract (public liability for one engagement). |
| `country` | string | no | ISO country code the document counts in. |
| `kind` | string | no | The document word: right_to_work, police_check, wwcc, white_card, forklift_licence, public_liability and so on. |
| `name` | string \| null | no | The kind's name in that country. |
| `details` | string \| null | no | What the document says: a visa subclass, a licence number, a card class. |
| `issued_on` | string (date) \| null | no | The date on the document. |
| `expires_on` | string (date) \| null | no | The printed expiry. |
| `counts_until` | string (date) \| null | no | The last day it counts: the printed expiry, or the date on the document plus the kind's rule (an Australian police check counts for 12 months), whichever comes first. Null when it never ends. |
| `document_id` | string \| null | no | doc_ id of the file on the candidate that shows it. |
| `checked_at` | string (date-time) \| null | no |  |
| `sent_at` | string (date-time) \| null | no | When the contractor sent it on their own page. Set with no checked_at, it is waiting to be checked: save it to mark it checked. |
| `created_at` | string (date-time) | no |  |
| `updated_at` | string (date-time) | no |  |

### PaperWaiverResult

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `success` | boolean | no |  |
| `data` | object | no |  |
| `data.placement_id` | string | no | pla_ id. |
| `data.kind` | string | no |  |
| `data.waived` | boolean | no |  |
| `data.waived_at` | string (date-time) \| null | no |  |

### PlacementPapers

What one contract needs and where each document stands. Derived fresh on every read from the job, the pay route and the country rules; the same check the placement screen shows.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `placement_id` | string | no | pla_ id. |
| `candidate_id` | string | no | cnd_ id. |
| `candidate_name` | string | no |  |
| `client_name` | string \| null | no |  |
| `placement_status` | string (`pending_start`, `started`, `fell_off`, `completed`) | no |  |
| `start_date` | string (date) \| null | no |  |
| `end_date` | string (date) \| null | no | The contract end date. Null for an open-ended contract. |
| `country` | string \| null | no | The country whose rules apply: the workplace, else the agency. |
| `rules_label` | string | no | "Basics only" for a country Lovelio has no rules for. |
| `summary` | string | no | The whole answer in one plain paragraph, ready to relay. |
| `requirements` | array of object | no |  |
| `requirements[].kind` | string | no |  |
| `requirements[].name` | string | no |  |
| `requirements[].reason` | string (`right_to_work`, `pay`, `job`) | no | Why it is needed: everyone working, the pay route (an ABN contractor carries insurance), or the job asks for it. |
| `requirements[].why` | string | no | One sentence saying why. |
| `requirements[].scope` | string (`person`, `placement`) | no | person: one document serves every contract in the country. placement: this contract only. |
| `requirements[].lasts_months` | integer \| null | no | How long one counts from its own date, when the rules say so. |
| `requirements[].status` | string (`current`, `missing`, `expired`, `ends_first`, `to_check`, `not_needed`) | no | current for the whole contract; missing; expired; ends_first: stops counting before the contract ends; to_check: the contractor sent it on their page and no one has checked it yet; not_needed: marked not needed for this contract. |
| `requirements[].waived` | object \| null | no | Set when marked not needed for this contract. |
| `requirements[].waived.by_name` | string \| null | no |  |
| `requirements[].waived.at` | string (date-time) | no |  |
| `requirements[].paper` | object \| null | no | The document on file for it, if any. |
| `requirements[].paper.id` | string | no | ppr_ id. |
| `requirements[].paper.details` | string \| null | no |  |
| `requirements[].paper.issued_on` | string (date) \| null | no |  |
| `requirements[].paper.expires_on` | string (date) \| null | no |  |
| `requirements[].paper.counts_until` | string (date) \| null | no |  |
| `requirements[].paper.checked_by_name` | string \| null | no |  |
| `requirements[].paper.checked_at` | string (date-time) \| null | no |  |
| `requirements[].paper.sent_at` | string (date-time) \| null | no |  |
| `requirements[].paper.document_id` | string \| null | no | doc_ id. |
| `requirements[].paper.placement_id` | string \| null | no | pla_ id, for an engagement document. |
