# 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 7.2.0. 157 paths, 234 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, 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, Clients, Specs, Search, Business Development.

## 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). Lovelio Connect apps authenticate with the OAuth access token from the connect flow instead (Authorization: Bearer lc_at_...) - it carries exactly the scopes the agency approved, enforced on every request. 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. Requesting any of them puts a Connect app in the elevated review tier. See /docs/agents/build-an-integration.

## Pagination and shared parameters

Every list endpoint takes these. Cursor-based: read `meta.next_cursor` from a response and pass it back as `after`.

| 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 |
| `before` | query | string | Cursor for backward 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. |

## 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 |

## 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 - every version, including breaking changes
- https://lovelio.ai/llms-full.txt - all of the above inlined in one fetch

## Endpoint index

Filtered to: Distribution.

### Distribution

Job board catalogue, posting rules, multi-board distribution, and the posting ledger

- `GET /distribution/boards` - List job boards
- `GET /distribution/rules` - List distribution rules
- `POST /distribution/rules` - Create a distribution rule
- `PATCH /distribution/rules/{id}` - Update a distribution rule
- `DELETE /distribution/rules/{id}` - Delete a distribution rule
- `GET /distribution/postings` - List job board postings
- `POST /distribution/publish` - Distribute a job to boards

---

## Distribution

Job board catalogue, posting rules, multi-board distribution, and the posting ledger

### GET /distribution/boards

**List job boards**

The catalogue of integrated job boards available to connect, with your monthly posting allowance per board (limit, used, remaining for the current month).

Operation ID: `listDistributionBoards`

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `200` | Array of boards | { success, data: array of DistributionBoard } |

### GET /distribution/rules

**List distribution rules**

Operation ID: `listDistributionRules`

**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. |

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `200` | Array of rules | { success, data: array of DistributionRule } |

### POST /distribution/rules

**Create a distribution rule**

Route matching jobs to boards. Free/mixed boards auto-publish; paid boards await confirmation.

Operation ID: `createDistributionRule`

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

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | yes |  |
| `target_board_slugs` | array of string | yes |  |
| `filter` | object | no | See the DistributionRule schema. country is an ISO 3166 alpha-2 code (a country name is normalised to one on write). |
| `filter.country` | string | no |  |
| `filter.category` | string | no |  |
| `filter.job_type` | string (`Permanent`, `Contract`, `Temporary`) | no |  |
| `filter.team_id` | string | no |  |
| `active` | boolean | no |  |

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `201` | Created rule | { success, data: DistributionRule } |

### PATCH /distribution/rules/{id}

**Update a distribution rule**

Operation ID: `updateDistributionRule`

**Path parameters**

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

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

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `patch` | object | no |  |
| `patch.name` | string | no |  |
| `patch.target_board_slugs` | array of string | no |  |
| `patch.filter` | object | no |  |
| `patch.active` | boolean | no |  |

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `200` | Updated rule | { success, data: DistributionRule } |

### DELETE /distribution/rules/{id}

**Delete a distribution rule**

Operation ID: `deleteDistributionRule`

**Path parameters**

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

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `204` | Deleted |  |

### GET /distribution/postings

**List job board postings**

The per-board posting ledger - where jobs have been posted, with status and errors. Each ad of a job has its own postings, so filter by job_ad_id to scope to one ad.

Operation ID: `listDistributionPostings`

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `job_id` | string | no | Filter to one job |
| `job_ad_id` | string | no | Filter to one job ad |
| `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. |

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `200` | Array of postings | { success, data: array of JobBoardPosting } |

### POST /distribution/publish

**Distribute a job to boards**

With board_slugs, publishes asynchronously to those boards (202 + task). Without, evaluates the company distribution rules and returns the resolved targets.

Operation ID: `distributeJob`

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

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `job_id` | string | yes |  |
| `board_slugs` | array of string | no |  |

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `200` | Rule-resolved targets | { success, data: object } |
| `202` | Async task accepted |  |

## Schemas

Objects referenced by the operations above.

### DistributionBoard

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `slug` | string | no |  |
| `name` | string | no |  |
| `regions` | array of string | no |  |
| `industries` | array of string | no |  |
| `pricing` | string (`free`, `paid`, `mixed`) | no |  |
| `auth_type` | string (`none`, `api_key`, `oauth2`, `basic`) | no |  |
| `capabilities` | object | no |  |
| `capabilities.edit` | boolean | no |  |
| `capabilities.expire` | boolean | no |  |
| `capabilities.applyBack` | boolean | no |  |
| `capabilities.statusQuery` | boolean | no |  |
| `monthly_post_limit` | integer \| null | no | Your monthly posting cap for this board (null = unlimited). Set in Settings; enforced atomically at publish - a post past the cap is rejected with error code quota_exceeded. |
| `posts_used_this_month` | integer | no | Posts consumed on this board in the current calendar month (company timezone). |
| `posts_remaining_this_month` | integer \| null | no | Posts left this month (null = unlimited). Never negative. |

### DistributionRule

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | no |  |
| `name` | string | no |  |
| `filter` | object | no | Every set field must equal the job's value; an empty filter matches every job. country is an ISO 3166 alpha-2 code (a country name is normalised to one on write). job_type is the contract basis: Permanent, Contract or Temporary. team_id is the job's team; category is the older free-text form of the same axis, matched against the team name. |
| `filter.country` | string | no |  |
| `filter.category` | string | no |  |
| `filter.job_type` | string (`Permanent`, `Contract`, `Temporary`) | no |  |
| `filter.team_id` | string | no |  |
| `target_board_slugs` | array of string | no |  |
| `active` | boolean | no |  |
| `ai_screen_enabled` | boolean | no | Per-job AI screening. When true, every inbound applicant whose AI verdict lands yes or maybe is automatically invited to the AI voice screen. Candidates a consultant adds by hand are never auto-invited. Set via the set_ai_screening batch op. |
| `ai_screen_focus` | array of string \| null | no | Consultant-set focus areas the AI screen digs into. Feeds the per-candidate question generator. |
| `created_at` | string (date-time) | no |  |
| `updated_at` | string (date-time) | no |  |

### JobBoardPosting

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | no |  |
| `job_id` | string \| null | no |  |
| `job_ad_id` | string \| null | no |  |
| `board_slug` | string | no |  |
| `status` | string (`pending`, `publishing`, `live`, `expired`, `rejected`, `error`) | no |  |
| `url` | string \| null | no |  |
| `external_id` | string \| null | no |  |
| `error_code` | string \| null | no |  |
| `error_message` | string \| null | no |  |
| `published_at` | string (date-time) \| null | no |  |
| `updated_at` | string (date-time) | no |  |
