# 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: Business Development.

### Business Development

- `GET /bd/targets` - List BD targets
- `GET /bd/targets/{id}` - Get BD target
- `GET /bd/leads` - List BD leads
- `GET /bd/leads/{id}` - Get BD lead
- `GET /bd/patch` - Get BD patch

---

## Business Development

### GET /bd/targets

**List BD targets**

The territory map: standing employer records in the agency's patch, scored 0-100 against the agency's own history (deterministic - nothing invented). Read-only; status moves only through the batch ops pursue_bd_target, dismiss_bd_target and convert_bd_target, which accept the bdt_ ids returned here.

Operation ID: `listBdTargets`

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `status` | string (`new`, `pursuing`, `dismissed`, `converted`) | no |  |
| `market` | string (`AU`, `UK`, `US`, `CA`) | no |  |
| `min_score` | integer | no | Only targets with lookalike_score at or above this value |
| `limit` | integer, max 100, default 25 | no |  |
| `after` | string | no | Cursor from meta.next_cursor |

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `200` | BD targets | { success, data: array of BdTarget, meta } |

### GET /bd/targets/{id}

**Get BD target**

One territory target with its reason-to-call feed: the dated market signals, candidate-corpus signals and consultant-logged touches that accumulate on the target (newest first, up to 50).

Operation ID: `getBdTarget`

**Path parameters**

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

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `200` | BD target with events | { success, data: BdTarget } |
| `404` | Not found |  |

### GET /bd/leads

**List BD leads**

The daily BD briefs: companies hiring right now that look like the agency's best clients. Read-only; leads resolve only through the batch ops dismiss_bd_lead and convert_bd_lead, which accept the bdl_ ids returned here. Lovelio never drafts or sends outreach - humans own the BD.

Operation ID: `listBdLeads`

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `status` | string (`open`, `dismissed`, `converted`) | no |  |
| `kind` | string (`new_business`, `client_expansion`) | no |  |
| `limit` | integer, max 100, default 25 | no |  |
| `after` | string | no | Cursor from meta.next_cursor |

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `200` | BD leads | { success, data: array of BdLead, meta } |

### GET /bd/leads/{id}

**Get BD lead**

Operation ID: `getBdLead`

**Path parameters**

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

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `200` | BD lead | { success, data: BdLead } |
| `404` | Not found |  |

### GET /bd/patch

**Get BD patch**

The agency's declared patch (its own words plus the AI's structured reading) and the territory state summary. Nulls mean the agency has not described its patch yet. Writing goes through the two-phase update_bd_patch batch op: call with { statement } for a preview, then { apply: true, statement, profile } to save. When statement is null, update_bd_patch { draft: true } returns data.draft { profile, territory, evidence } - a patch drafted from the enrichment already in the account, as role and sector chips to remove plus city/region/country territories to choose from. Neither the draft nor the preview writes anything.

Operation ID: `getBdPatch`

**Responses**

| Code | Description | Body |
| --- | --- | --- |
| `200` | Patch and territory state | { success, data: object } |

## Schemas

Objects referenced by the operations above.

### BdLead

A daily BD brief: a company hiring right now that looks like the agency's best clients, with the agency's own history as the proof. Briefs only - Lovelio never drafts or sends outreach.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | no |  |
| `account_id` | string | no |  |
| `employer_name` | string | no |  |
| `market` | string (`AU`, `UK`, `US`, `CA`, `OTHER`) | no | OTHER means the lead was captured by hand from an ad outside the markets Lovelio sweeps; it carries no currency. |
| `kind` | string (`new_business`, `client_expansion`) | no |  |
| `status` | string (`open`, `dismissed`, `converted`) | no |  |
| `score` | integer | no |  |
| `fit_score` | integer \| null | no |  |
| `signals` | array of string | no |  |
| `why_now` | string \| null | no |  |
| `why_you` | string \| null | no |  |
| `fit_note` | string \| null | no |  |
| `est_fee` | number \| null | no |  |
| `fee_basis` | string \| null | no |  |
| `currency` | string \| null | no |  |
| `matched_client_id` | string \| null | no |  |
| `surfaced_on` | string (date) | no |  |
| `dismissed_at` | string (date-time) \| null | no |  |
| `converted_at` | string (date-time) \| null | no |  |
| `converted_client_id` | string \| null | no |  |
| `created_at` | string (date-time) | no |  |
| `updated_at` | string (date-time) | no |  |

### BdTarget

One employer on the agency's territory map, scored deterministically against the agency's own placement and job history. The human owns the status; the scorer only refreshes score and evidence.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | no |  |
| `account_id` | string | no |  |
| `employer_name` | string | no |  |
| `market` | string (`AU`, `UK`, `US`, `CA`) | no |  |
| `status` | string (`new`, `pursuing`, `dismissed`, `converted`) | no |  |
| `lookalike_score` | integer | no | 0-100, deterministic fit against the agency's history |
| `right_to_win` | string \| null | no | One line: why this agency in particular |
| `score_facts` | object | no | The evidence behind the score (worked families, salary bands, ad facts) |
| `matched_client_id` | string \| null | no |  |
| `pursued_at` | string (date-time) \| null | no |  |
| `dismissed_at` | string (date-time) \| null | no |  |
| `converted_at` | string (date-time) \| null | no |  |
| `converted_client_id` | string \| null | no |  |
| `last_scored_at` | string (date-time) | no |  |
| `created_at` | string (date-time) | no |  |
| `updated_at` | string (date-time) | no |  |

### Meta

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