# 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: Candidate Evidence.

### Candidate Evidence

- `POST /evidence/evaluations` - Check candidate evidence
- `GET /evidence/evaluations/{id}` - Read an evidence review
- `POST /evidence/packs` - Prepare a compact evidence pack
- `GET /evidence/packs/{id}` - Read an evidence pack

---

## Candidate Evidence

### POST /evidence/evaluations

**Check candidate evidence**

Check original evidence against requirements using the same search core as Lovelio. Group membership is snapshotted. Work continues after disconnection. Read result_url until complete and follow next_cursor for every candidate. Supported, contradicted, not established and uncertain are distinct; missing or failed processing never means a candidate failed a requirement. Does not rank, reject or move candidates. A job scope additionally requires jobs:read; a pool scope requires talent_pools:read. Cache reuse validates original source hashes. Only original CVs and structured candidate records are checked; email, notes and calls are not checked.

Operation ID: `evaluateCandidateEvidence`

Scopes: `candidates:read`

**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 |
| --- | --- | --- | --- |
| `scope` | object | yes | The candidate group to snapshot. Explicit IDs accept up to 1,000; group selectors enumerate their complete membership. Search uses the shared facet state, not only its displayed page. |
| `checks` | array of object | no | Explicit ad hoc checks. Supply checks or job_id. Full compiled job requirements are never truncated to this limit. |
| `checks[].id` | string | yes |  |
| `checks[].term` | string | yes |  |
| `checks[].label` | string | no |  |
| `checks[].strength` | string (`must`, `nice`, `exclude`) | no |  |
| `job_id` | string | no | Compile every structured requirement for this job through the shared search core. |
| `source_policy` | string (`original_cv`), default "original_cv" | no |  |

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `200` | Permission-checked result. Candidate rows count against the agent access allowance. | { success, data: object, meta } |
| `202` | Permission-checked result. Candidate rows count against the agent access allowance. | { success, data: object, 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` | Scope or role denied. | Error |
| `405` | METHOD_NOT_ALLOWED: use one of the methods in the Allow header. | Error |
| `409` | 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` | Invalid group or checks. | 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 |

### GET /evidence/evaluations/{id}

**Read an evidence review**

Check original evidence against requirements using the same search core as Lovelio. Group membership is snapshotted. Work continues after disconnection. Read result_url until complete and follow next_cursor for every candidate. Supported, contradicted, not established and uncertain are distinct; missing or failed processing never means a candidate failed a requirement. Does not rank, reject or move candidates. A job scope additionally requires jobs:read; a pool scope requires talent_pools:read. Cache reuse validates original source hashes. Only original CVs and structured candidate records are checked; email, notes and calls are not checked.

Operation ID: `getEvidenceEvaluation`

Scopes: `candidates:read`

**Path parameters**

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

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `cursor` | string | no |  |
| `limit` | integer, 1-100, default 50 | no |  |

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `200` | Permission-checked result. Candidate rows count against the agent access allowance. | { success, data: EvidenceEvaluation, 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` | Review not found or no longer accessible. | 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` | Candidate access limit reached. | Error |
| `500` | INTERNAL_ERROR: retry with backoff. An idempotent POST must keep the same key. | Error |

### POST /evidence/packs

**Prepare a compact evidence pack**

Check original evidence against requirements using the same search core as Lovelio. Group membership is snapshotted. Work continues after disconnection. Read result_url until complete and follow next_cursor for every candidate. Supported, contradicted, not established and uncertain are distinct; missing or failed processing never means a candidate failed a requirement. Does not rank, reject or move candidates. A job scope additionally requires jobs:read; a pool scope requires talent_pools:read. Cache reuse validates original source hashes. Only original CVs and structured candidate records are checked; email, notes and calls are not checked. Packs preserve every requirement and conflicting quote within each candidate page. Purpose labels the downstream workflow. No submission or email is sent.

Operation ID: `prepareEvidencePack`

Scopes: `candidates:read`

**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 |
| --- | --- | --- | --- |
| `scope` | object | yes | The candidate group to snapshot. Explicit IDs accept up to 1,000; group selectors enumerate their complete membership. Search uses the shared facet state, not only its displayed page. |
| `checks` | array of object | no | Explicit ad hoc checks. Supply checks or job_id. Full compiled job requirements are never truncated to this limit. |
| `checks[].id` | string | yes |  |
| `checks[].term` | string | yes |  |
| `checks[].label` | string | no |  |
| `checks[].strength` | string (`must`, `nice`, `exclude`) | no |  |
| `job_id` | string | no | Compile every structured requirement for this job through the shared search core. |
| `source_policy` | string (`original_cv`), default "original_cv" | no |  |
| `purpose` | string (`submission`, `interview_preparation`, `job_comparison`) | yes |  |

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `200` | Permission-checked result. Candidate rows count against the agent access allowance. | { success, data: object, meta } |
| `202` | Permission-checked result. Candidate rows count against the agent access allowance. | { success, data: object, 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 |
| `405` | METHOD_NOT_ALLOWED: use one of the methods in the Allow header. | Error |
| `409` | 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 |
| `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 |

### GET /evidence/packs/{id}

**Read an evidence pack**

Check original evidence against requirements using the same search core as Lovelio. Group membership is snapshotted. Work continues after disconnection. Read result_url until complete and follow next_cursor for every candidate. Supported, contradicted, not established and uncertain are distinct; missing or failed processing never means a candidate failed a requirement. Does not rank, reject or move candidates. A job scope additionally requires jobs:read; a pool scope requires talent_pools:read. Cache reuse validates original source hashes. Only original CVs and structured candidate records are checked; email, notes and calls are not checked.

Operation ID: `getEvidencePack`

Scopes: `candidates:read`

**Path parameters**

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

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `cursor` | string | no |  |
| `limit` | integer, 1-100, default 50 | no |  |

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `200` | Permission-checked result. Candidate rows count against the agent access allowance. | { success, data: EvidencePack, 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` | Pack not found or no longer accessible. | 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` | Candidate access limit reached. | 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. |

### EvidenceEvaluation

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | no |  |
| `task_id` | string \| null | no |  |
| `status` | string | no |  |
| `scope_type` | string (`selection`, `talent_pool`, `job_applicants`, `import_batch`, `search`, `all`) | no |  |
| `checks` | array of object | no |  |
| `checks[].id` | string | no |  |
| `checks[].term` | string | no |  |
| `checks[].label` | string | no |  |
| `checks[].predicate` | object \| null | no | Complete shared search predicate, including explicit OR alternatives, numeric bounds, polarity and time windows. Interpret the whole predicate, never only the short label. |
| `checks[].strength` | string (`must`, `nice`, `exclude`) | no |  |
| `rows` | array of object | no |  |
| `rows[].candidate_id` | string | no |  |
| `rows[].name` | string \| null | no |  |
| `rows[].status` | string (`pending`, `complete`, `missing_source`, `too_long`, `failed`, `stale`) | no |  |
| `rows[].scope_match` | string (`matched`, `not_established`, `unchecked`) | no |  |
| `rows[].source_hash` | string \| null | no |  |
| `rows[].cache_hits` | integer, min 0 | no |  |
| `rows[].error` | string \| null | no |  |
| `rows[].checks` | object | no |  |
| `progress` | object | no |  |
| `progress.total` | integer, min 0 | no |  |
| `progress.evaluated` | integer, min 0 | no |  |
| `progress.pending` | integer, min 0 | no |  |
| `progress.failed` | integer, min 0 | no |  |
| `progress.missing_source` | integer, min 0 | no |  |
| `progress.too_long` | integer, min 0 | no |  |
| `progress.stale` | integer, min 0 | no |  |
| `progress.cache_hits` | integer, min 0 | no |  |
| `progress.excluded` | integer, min 0 | no |  |
| `progress.unfinished_imports` | integer, min 0 | no |  |
| `progress.complete` | boolean | no |  |
| `next_cursor` | string \| null | no |  |
| `sources_checked` | array of string | no |  |
| `created_at` | string | no |  |
| `completed_at` | string \| null | no |  |
| `error` | string \| null | no |  |
| `result_url` | string | no |  |

### EvidencePack

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | no |  |
| `purpose` | string (`submission`, `interview_preparation`, `job_comparison`) | no |  |
| `compact` | object | no |  |
| `compact.positive_quote_budget` | integer, min 0 | no |  |
| `compact.omitted_passages` | array of object | no |  |
| `compact.omitted_passages[].candidate_id` | string | no |  |
| `compact.omitted_passages[].check_id` | string | no |  |
| `compact.full_evidence_url` | string | no |  |
| `context` | object | no |  |
| `context.as_of` | string | no |  |
| `context.job` | object \| null | no |  |
| `context.client_dna` | string \| null | no |  |
| `context.client_dna_included` | boolean | no |  |
| `context.preferences` | array of object | no |  |
| `context.preferences[].candidate_id` | string | no |  |
| `context.preferences[].values` | object | no |  |
| `evaluation` | EvidenceEvaluation | no |  |
| `coverage` | object | no |  |
| `coverage.sources_checked` | array of string | no |  |
| `coverage.sources_not_checked` | array of string | no |  |
| `coverage.complete` | boolean | no |  |

### Meta

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