# LineHQ API reference

Base URL: `https://app.linehq.co.uk/api/v1`. Send an API key from the LineHQ portal in the `Authorization: Bearer` header of every request. Responses are JSON, and records are addressed by their `public_id`.

The whole reference as OpenAPI 3.1: https://linehq.co.uk/developers/openapi.json. Developer overview: https://linehq.co.uk/developers.

## Account

Who the key belongs to and the account's prepaid balance.

### The key's account, user and permissions

`GET /me`

Who this API key belongs to, the permissions it holds, and the private channel to subscribe to for live events. Call it first to check a key works.

- API key permission: `read`
- Account role: Owner or member

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/me" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/me", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/me",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` The key's identity.

```json
{
    "tenant": {
        "name": "Acme Plumbing Ltd",
        "status": "active"
    },
    "user": {
        "name": "Sam Patel",
        "email": "sam@acmeplumbing.co.uk",
        "role": "owner"
    },
    "abilities": [
        "read",
        "contacts:read"
    ],
    "can_transcribe": false,
    "realtime": {
        "enabled": true,
        "channel": "private-tenant.4821",
        "key": "linehq",
        "host": "app.linehq.co.uk",
        "port": 443,
        "scheme": "https"
    }
}
```

### The prepaid balance

`GET /balance`

The account's prepaid wallet balance, in pence.

- API key permission: `read`
- Account role: Owner or member

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/balance" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/balance", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/balance",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` The balance.

```json
{
    "balance_pence": 2500,
    "currency": "GBP"
}
```

## Numbers

The account's phone numbers and how each one answers calls.

### List numbers

`GET /numbers`

Every number on the account that hasn't been released, ordered by number.

- API key permission: `read`
- Account role: Owner or member

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/numbers" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/numbers", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/numbers",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` The account's numbers.

```json
{
    "data": [
        {
            "public_id": "8kQ2mVn4Lr7Tz1Xc9Bw3Hd6Fs0Jy5Pa2",
            "e164": "+442035550123",
            "label": "Sales",
            "status": "assigned",
            "sms_capable": true,
            "sms_ready": true,
            "cli_eligible": true,
            "is_default_caller_id": true
        }
    ]
}
```

### Get a number

`GET /numbers/{number}`

One number, with its business, caller ID and text-message status.

- API key permission: `read`
- Account role: Owner or member

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `number` (required) | path | string | The number's `public_id`, from List numbers. |

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/numbers/NUMBER_ID" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/numbers/NUMBER_ID", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/numbers/NUMBER_ID",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` The number.

```json
{
    "public_id": "8kQ2mVn4Lr7Tz1Xc9Bw3Hd6Fs0Jy5Pa2",
    "e164": "+442035550123",
    "label": "Sales",
    "status": "assigned",
    "ownership": "platform",
    "business_profile": {
        "public_id": "Vb7Nq1Zr4Kx8Lm2Tc6Wd0Hs3Jf9Pg5Ya",
        "name": "Acme Plumbing"
    },
    "release_at": null,
    "sms_capable": true,
    "sms_ready": true,
    "cli_eligible": true,
    "cli_verified": false,
    "is_default_caller_id": true,
    "forward_cli": {
        "mode": "auto",
        "shows": "own",
        "own_e164": "+442035550123",
        "platform_e164": null,
        "own_available": true
    }
}
```

### Get a number's routing

`GET /numbers/{number}/routing`

How a number answers calls: the default route, any scheduled routes (the first matching window wins), and a temporary override while one is set. Read-only; routing is changed in the portal or the LineHQ app.

- API key permission: `read`
- Account role: Owner or member

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `number` (required) | path | string | The number's `public_id`, from List numbers. |

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/numbers/NUMBER_ID/routing" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/numbers/NUMBER_ID/routing", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/numbers/NUMBER_ID/routing",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` The number's routing.

```json
{
    "number": {
        "public_id": "8kQ2mVn4Lr7Tz1Xc9Bw3Hd6Fs0Jy5Pa2",
        "e164": "+442035550123",
        "status": "assigned",
        "voicemail_transcription_enabled": true,
        "call_transcription_enabled": false,
        "privacy_mode_enabled": false
    },
    "default": {
        "mode": "forward",
        "recording_enabled": true,
        "voicemail_fallback": true,
        "whisper": false,
        "ai_cover": "off",
        "forward_targets": [
            {
                "kind": "pstn",
                "e164": "+447700900001",
                "staff_label": "Sam",
                "ring_seconds": 25
            }
        ],
        "ivr": {
            "options": []
        }
    },
    "schedules": [
        {
            "name": "Weekends",
            "windows": [
                {
                    "days": [
                        6,
                        7
                    ],
                    "start": "09:00",
                    "end": "17:30"
                }
            ],
            "mode": "voicemail",
            "recording_enabled": false,
            "voicemail_fallback": false,
            "whisper": false,
            "ai_cover": "off",
            "forward_targets": [],
            "ivr": {
                "options": []
            }
        }
    ],
    "override": null
}
```

## Calls

Calls, their recordings, transcripts and AI summaries.

[Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

### List calls

`GET /calls`

The account's calls, newest first, each with its AI summary once one exists. Calls our call protection filtered out are left out unless you ask for them with `activity`.

- API key permission: `read`
- Account role: Owner or member
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `activity` | query | one of `"business"`, `"filtered"`, `"all"` | `business` (the default) for real calls, `filtered` for calls call protection screened out, or `all`. |
| `search` | query | string | Part of the caller's or the dialled number, as international digits, or of the caller's name. |
| `period` | query | one of `"all"`, `"today"`, `"7d"`, `"30d"`, `"month"`, `"custom"` | `today`, `7d`, `30d`, `month` (this calendar month), or `custom` with `date_from` and `date_to`. |
| `date_from` | query | string | With `period=custom`, the first day to include (YYYY-MM-DD). |
| `date_to` | query | string | With `period=custom`, the last day to include (YYYY-MM-DD). |
| `status` | query | one of `"answered"`, `"no-answer"`, `"voicemail"`, `"failed"`, `"in-progress"`, `"busy"`, `"blocked"` | Only calls with this outcome. |
| `sentiment` | query | one of `"positive"`, `"neutral"`, `"negative"` | Only calls whose summary has this sentiment. |
| `urgency` | query | one of `"low"`, `"normal"`, `"high"` | Only calls whose summary has this urgency. |
| `page` | query | integer | The page to return, from 1. |
| `per_page` | query | integer | Records per page, up to 100. Defaults to 25. |

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/calls" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/calls", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/calls",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` A page of calls.

```json
{
    "data": [
        {
            "public_id": "Qm4Tz8Kc1Lp6Wr3Nx9Bv2Hd7Fs5Jy0Ga",
            "from": "+447700900123",
            "caller_display_name": "Jo Smith",
            "to": "+442035550123",
            "to_name": "Sales",
            "direction": "inbound",
            "disposition": "answered",
            "started_at": "2026-10-14T09:30:12+01:00",
            "forward_seconds": 184,
            "total_rated_amount_pence": 0,
            "recording_public_id": "Rk3Lm8Nq2Tz6Vb1Xc5Wd9Hs4Jf0Pg7Ya",
            "transcript": {
                "status": "completed",
                "summary_status": "completed",
                "summary": "Jo from Harbour Cafe needs a boiler repair today.",
                "intent": "Book a boiler repair",
                "sentiment": "neutral",
                "urgency": "high",
                "action_items": [
                    "Call Jo back to book a visit today"
                ],
                "callback_number": "+447700900123"
            }
        }
    ],
    "current_page": 1,
    "last_page": 1,
    "per_page": 25,
    "total": 1,
    "next_page_url": null,
    "prev_page_url": null
}
```

### Get a call

`GET /calls/{call}`

One call, in the same shape as a row of List calls.

- API key permission: `read`
- Account role: Owner or member
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `call` (required) | path | string | The call's `public_id`, from List calls. |

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/calls/CALL_ID" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/calls/CALL_ID", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/calls/CALL_ID",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` The call.

```json
{
    "public_id": "Qm4Tz8Kc1Lp6Wr3Nx9Bv2Hd7Fs5Jy0Ga",
    "from": "+447700900123",
    "caller_display_name": "Jo Smith",
    "to": "+442035550123",
    "to_name": "Sales",
    "direction": "inbound",
    "disposition": "answered",
    "started_at": "2026-10-14T09:30:12+01:00",
    "forward_seconds": 184,
    "total_rated_amount_pence": 0,
    "recording_public_id": "Rk3Lm8Nq2Tz6Vb1Xc5Wd9Hs4Jf0Pg7Ya",
    "transcript": {
        "status": "completed",
        "summary_status": "completed",
        "summary": "Jo from Harbour Cafe needs a boiler repair today.",
        "intent": "Book a boiler repair",
        "sentiment": "neutral",
        "urgency": "high",
        "action_items": [
            "Call Jo back to book a visit today"
        ],
        "callback_number": "+447700900123"
    }
}
```

### Transcribe a call

`POST /calls/{call}/transcribe`

Start a transcription and AI summary of a recorded call. It is paid: minutes come from the plan's allowance, then any prepaid transcription bundle, then the prepaid balance, and they count towards the account's daily spending limit. Needs a key with the `transcribe` permission, which only an owner, or a member the owner allows to start transcriptions, can create. It returns straight away; read the summary from Get a call once `summary_status` is `completed`. Asking again for a call already transcribed costs nothing.

- API key permission: `transcribe`
- Account role: Owner or member
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `call` (required) | path | string | The call's `public_id`, from List calls. |

Example request:

```bash
curl -X POST "https://app.linehq.co.uk/api/v1/calls/CALL_ID/transcribe" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/calls/CALL_ID/transcribe", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.post(
    "https://app.linehq.co.uk/api/v1/calls/CALL_ID/transcribe",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `202` Queued, or already done.

```json
{
    "call_public_id": "Qm4Tz8Kc1Lp6Wr3Nx9Bv2Hd7Fs5Jy0Ga",
    "status": "pending",
    "summary_status": "pending"
}
```

### Get a recording link

`GET /recordings/{recording}/stream-url`

A signed link to a call's recording audio, valid for 10 minutes. Ask for a new one each time you play or download it.

- API key permission: `read`
- Account role: Owner or member
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `recording` (required) | path | string | The recording's `public_id`, a call's `recording_public_id`. |

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/recordings/RECORDING_ID/stream-url" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/recordings/RECORDING_ID/stream-url", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/recordings/RECORDING_ID/stream-url",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` The link.

```json
{
    "url": "https://app.linehq.co.uk/media/recordings/Rk3Lm8Nq2Tz6Vb1Xc5Wd9Hs4Jf0Pg7Ya?expires=1760450400&signature=0f3c9b",
    "expires_at": "2026-10-14T14:00:00+01:00"
}
```

### Get a transcript

`GET /recordings/{recording}/transcript`

The full transcript of a recording, with timed, speaker-labelled segments and the AI summary. An empty object means the recording hasn't been transcribed.

- API key permission: `read`
- Account role: Owner or member
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `recording` (required) | path | string | The recording's `public_id`, a call's `recording_public_id`. |

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/recordings/RECORDING_ID/transcript" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/recordings/RECORDING_ID/transcript", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/recordings/RECORDING_ID/transcript",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` The transcript, or `{}`.

```json
{
    "status": "completed",
    "text": "Hi, it's Jo from Harbour Cafe. Our boiler's stopped working, could someone come out today?",
    "language": "en",
    "minutes": 1,
    "rated_amount_pence": 0,
    "summary_status": "completed",
    "summary": "Jo from Harbour Cafe needs a boiler repair today.",
    "segments": [
        {
            "start": 0,
            "end": 6.4,
            "speaker": "customer",
            "text": "Hi, it's Jo from Harbour Cafe. Our boiler's stopped working, could someone come out today?"
        }
    ],
    "intent": "Book a boiler repair",
    "sentiment": "neutral",
    "urgency": "high",
    "action_items": [
        "Call Jo back to book a visit today"
    ],
    "callback_number": "+447700900123"
}
```

### Price a transcription

`GET /recordings/{recording}/transcript/estimate`

What transcribing this recording would cost, and whether the account can afford it now.

- API key permission: `read`
- Account role: Owner or member
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `recording` (required) | path | string | The recording's `public_id`, a call's `recording_public_id`. |

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/recordings/RECORDING_ID/transcript/estimate" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/recordings/RECORDING_ID/transcript/estimate", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/recordings/RECORDING_ID/transcript/estimate",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` The price.

```json
{
    "available": true,
    "minutes": 4,
    "cost_pence": 0,
    "rate_pence_per_min": 4,
    "within_allowance": true,
    "bundle_minutes": 0,
    "balance_pence": 2500,
    "affordable": true,
    "existing_status": null
}
```

## Text messages

Text-message conversations on the account's numbers.

[Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

### List text conversations

`GET /sms/conversations`

The account's text-message threads, most recent first. One thread per number and other party.

- API key permission: `read`
- Account role: Owner or member
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `page` | query | integer | The page to return, from 1. |
| `per_page` | query | integer | Records per page, up to 100. Defaults to 25. |

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/sms/conversations" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/sms/conversations", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/sms/conversations",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` A page of conversations.

```json
{
    "data": [
        {
            "public_id": "Tc5Wd9Hs4Jf0Pg7YaRk3Lm8Nq2Tz6Vb1X",
            "counterparty_e164": "+447700900123",
            "number_e164": "+442035550123",
            "number_label": "Sales",
            "last_message_at": "2026-10-14T09:12:00+01:00",
            "last_message_preview": "Running 10 minutes late, sorry!",
            "unread_count": 1
        }
    ],
    "current_page": 1,
    "last_page": 1,
    "per_page": 25,
    "total": 1,
    "next_page_url": null,
    "prev_page_url": null
}
```

### List a conversation's messages

`GET /sms/conversations/{conversation}/messages`

The messages in one thread, oldest first, with the thread itself.

- API key permission: `read`
- Account role: Owner or member
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `conversation` (required) | path | string | The conversation's `public_id`, from List text conversations. |
| `page` | query | integer | The page to return, from 1. |
| `per_page` | query | integer | Messages per page, up to 200. Defaults to 100. |

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/sms/conversations/CONVERSATION_ID/messages" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/sms/conversations/CONVERSATION_ID/messages", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/sms/conversations/CONVERSATION_ID/messages",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` A page of messages.

```json
{
    "data": [
        {
            "conversation_public_id": "Tc5Wd9Hs4Jf0Pg7YaRk3Lm8Nq2Tz6Vb1X",
            "direction": "inbound",
            "from_e164": "+447700900123",
            "to_e164": "+442035550123",
            "body": "Running 10 minutes late, sorry!",
            "status": "received",
            "segments": 1,
            "received_at": "2026-10-14T09:12:00+01:00"
        }
    ],
    "conversation": {
        "public_id": "Tc5Wd9Hs4Jf0Pg7YaRk3Lm8Nq2Tz6Vb1X",
        "counterparty_e164": "+447700900123",
        "number_e164": "+442035550123",
        "number_label": "Sales",
        "last_message_at": "2026-10-14T09:12:00+01:00",
        "last_message_preview": "Running 10 minutes late, sorry!",
        "unread_count": 1
    },
    "current_page": 1,
    "last_page": 1,
    "per_page": 100,
    "total": 1,
    "next_page_url": null,
    "prev_page_url": null
}
```

## Contacts

The contact directory LineHQ names callers from. Keep it in step with your own system.

[Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

### List contacts

`GET /contacts`

The account's contact directory, most recently changed first. LineHQ uses it to name callers. Needs a key with the `contacts:read` permission.

- API key permission: `contacts:read`
- Account role: Owner or member
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `search` | query | string | Part of the display name, company or email address, or of a phone number as international digits (`+44…`, not `07…`). |
| `source` | query | string | Only contacts from this source, such as the one your system writes them with. |
| `page` | query | integer | The page to return, from 1. |
| `per_page` | query | integer | Records per page, up to 100. Defaults to 25. |

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/contacts" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/contacts", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/contacts",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` A page of contacts.

```json
{
    "data": [
        {
            "public_id": "Lm8Nq2Tz6Vb1Xc5Wd9Hs4Jf0Pg7YaRk3",
            "display_name": "Jo Smith",
            "first_name": "Jo",
            "last_name": "Smith",
            "company": "Harbour Cafe",
            "email": "jo@harbourcafe.co.uk",
            "notes": null,
            "source": "acme-crm",
            "external_id": "10442",
            "source_created_at": null,
            "source_updated_at": null,
            "phones": [
                {
                    "e164": "+447700900123",
                    "raw_number": "07700 900123",
                    "label": "mobile",
                    "is_primary": true
                }
            ],
            "links": [],
            "created_at": "2026-10-01T10:00:00+01:00",
            "updated_at": "2026-10-14T08:45:00+01:00"
        }
    ],
    "current_page": 1,
    "last_page": 1,
    "per_page": 25,
    "total": 1,
    "next_page_url": null,
    "prev_page_url": null
}
```

### Create or update a contact

`POST /contacts`

Add a contact. Send `external_id` (your system's id for it) and the same record can be sent again safely: a contact with the same `source` and `external_id` is updated rather than added twice, and the answer is still `201`. Without `external_id` every request adds a new contact. Phone numbers that can't be read as a phone number are dropped. Needs a key with the `contacts:write` permission.

- API key permission: `contacts:write`
- Account role: Owner or member
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Request body:

| Field | Type | Description |
| --- | --- | --- |
| `source` | string | Your system's name. With `external_id`, it identifies the contact so a repeat send updates it. |
| `external_id` | string or null |  |
| `display_name` (required) | string |  |
| `first_name` | string or null |  |
| `last_name` | string or null |  |
| `company` | string or null |  |
| `email` | string or null |  |
| `notes` | string or null |  |
| `external_url` | string or null | A link to the contact in your system. |
| `source_created_at` | string or null |  |
| `source_updated_at` | string or null |  |
| `phones` (required) | array of object |  |

Example request:

```bash
curl -X POST "https://app.linehq.co.uk/api/v1/contacts" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "source": "acme-crm",
    "external_id": "10442",
    "display_name": "Jo Smith",
    "company": "Harbour Cafe",
    "email": "jo@harbourcafe.co.uk",
    "phones": [
        {
            "number": "07700 900123",
            "label": "mobile",
            "is_primary": true
        }
    ]
}'
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/contacts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
      "source": "acme-crm",
      "external_id": "10442",
      "display_name": "Jo Smith",
      "company": "Harbour Cafe",
      "email": "jo@harbourcafe.co.uk",
      "phones": [
          {
              "number": "07700 900123",
              "label": "mobile",
              "is_primary": true
          }
      ]
  }),
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.post(
    "https://app.linehq.co.uk/api/v1/contacts",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
    json={
        "source": "acme-crm",
        "external_id": "10442",
        "display_name": "Jo Smith",
        "company": "Harbour Cafe",
        "email": "jo@harbourcafe.co.uk",
        "phones": [
            {
                "number": "07700 900123",
                "label": "mobile",
                "is_primary": True,
            },
        ],
    },
)
response.raise_for_status()
print(response.json())
```

Example response, `201` The contact.

```json
{
    "public_id": "Lm8Nq2Tz6Vb1Xc5Wd9Hs4Jf0Pg7YaRk3",
    "display_name": "Jo Smith",
    "first_name": "Jo",
    "last_name": "Smith",
    "company": "Harbour Cafe",
    "email": "jo@harbourcafe.co.uk",
    "notes": null,
    "source": "acme-crm",
    "external_id": "10442",
    "source_created_at": null,
    "source_updated_at": null,
    "phones": [
        {
            "e164": "+447700900123",
            "raw_number": "07700 900123",
            "label": "mobile",
            "is_primary": true
        }
    ],
    "links": [],
    "created_at": "2026-10-01T10:00:00+01:00",
    "updated_at": "2026-10-14T08:45:00+01:00"
}
```

### Create or update many contacts

`POST /contacts/bulk-upsert`

Up to 500 contacts at once, each matched on `source` and `external_id` like Create or update a contact. The batch is checked first, and one invalid contact rejects it all with `422`; a contact that then fails to save is counted in `failed_count` and the rest are kept. Needs a key with the `contacts:write` permission.

- API key permission: `contacts:write`
- Account role: Owner or member
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Request body:

| Field | Type | Description |
| --- | --- | --- |
| `source` (required) | string | Your system's name. Used for every contact that doesn't give its own. |
| `contacts` (required) | array of object |  |

Example request:

```bash
curl -X POST "https://app.linehq.co.uk/api/v1/contacts/bulk-upsert" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "source": "acme-crm",
    "contacts": [
        {
            "external_id": "10442",
            "display_name": "Jo Smith",
            "phones": [
                {
                    "number": "+447700900123"
                }
            ]
        },
        {
            "external_id": "10443",
            "display_name": "Priya Shah",
            "company": "Shah Dental",
            "phones": [
                {
                    "number": "020 7946 0991",
                    "label": "office"
                }
            ]
        }
    ]
}'
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/contacts/bulk-upsert", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
      "source": "acme-crm",
      "contacts": [
          {
              "external_id": "10442",
              "display_name": "Jo Smith",
              "phones": [
                  {
                      "number": "+447700900123"
                  }
              ]
          },
          {
              "external_id": "10443",
              "display_name": "Priya Shah",
              "company": "Shah Dental",
              "phones": [
                  {
                      "number": "020 7946 0991",
                      "label": "office"
                  }
              ]
          }
      ]
  }),
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.post(
    "https://app.linehq.co.uk/api/v1/contacts/bulk-upsert",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
    json={
        "source": "acme-crm",
        "contacts": [
            {
                "external_id": "10442",
                "display_name": "Jo Smith",
                "phones": [
                    {
                        "number": "+447700900123",
                    },
                ],
            },
            {
                "external_id": "10443",
                "display_name": "Priya Shah",
                "company": "Shah Dental",
                "phones": [
                    {
                        "number": "020 7946 0991",
                        "label": "office",
                    },
                ],
            },
        ],
    },
)
response.raise_for_status()
print(response.json())
```

Example response, `202` How many were saved.

```json
{
    "imported_count": 2,
    "failed_count": 0
}
```

### Replace a contact

`PUT /contacts/{contact}`

Replace a contact's details and phone numbers with the ones sent. Its `source` and `external_id` stay as they were. Needs a key with the `contacts:write` permission.

- API key permission: `contacts:write`
- Account role: Owner or member
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `contact` (required) | path | string | The contact's `public_id`. |

Request body:

| Field | Type | Description |
| --- | --- | --- |
| `source` | string | Your system's name. With `external_id`, it identifies the contact so a repeat send updates it. |
| `external_id` | string or null |  |
| `display_name` (required) | string |  |
| `first_name` | string or null |  |
| `last_name` | string or null |  |
| `company` | string or null |  |
| `email` | string or null |  |
| `notes` | string or null |  |
| `external_url` | string or null | A link to the contact in your system. |
| `source_created_at` | string or null |  |
| `source_updated_at` | string or null |  |
| `phones` (required) | array of object |  |

Example request:

```bash
curl -X PUT "https://app.linehq.co.uk/api/v1/contacts/CONTACT_ID" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "Jo Smith",
    "company": "Harbour Cafe & Bakery",
    "phones": [
        {
            "number": "+447700900123",
            "label": "mobile",
            "is_primary": true
        }
    ]
}'
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/contacts/CONTACT_ID", {
  method: "PUT",
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
      "display_name": "Jo Smith",
      "company": "Harbour Cafe & Bakery",
      "phones": [
          {
              "number": "+447700900123",
              "label": "mobile",
              "is_primary": true
          }
      ]
  }),
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.put(
    "https://app.linehq.co.uk/api/v1/contacts/CONTACT_ID",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
    json={
        "display_name": "Jo Smith",
        "company": "Harbour Cafe & Bakery",
        "phones": [
            {
                "number": "+447700900123",
                "label": "mobile",
                "is_primary": True,
            },
        ],
    },
)
response.raise_for_status()
print(response.json())
```

Example response, `200` The contact.

```json
{
    "public_id": "Lm8Nq2Tz6Vb1Xc5Wd9Hs4Jf0Pg7YaRk3",
    "display_name": "Jo Smith",
    "first_name": "Jo",
    "last_name": "Smith",
    "company": "Harbour Cafe",
    "email": "jo@harbourcafe.co.uk",
    "notes": null,
    "source": "acme-crm",
    "external_id": "10442",
    "source_created_at": null,
    "source_updated_at": null,
    "phones": [
        {
            "e164": "+447700900123",
            "raw_number": "07700 900123",
            "label": "mobile",
            "is_primary": true
        }
    ],
    "links": [],
    "created_at": "2026-10-01T10:00:00+01:00",
    "updated_at": "2026-10-14T08:45:00+01:00"
}
```

### Delete a contact

`DELETE /contacts/{contact}`

Remove a contact from the directory. Needs a key with the `contacts:write` permission.

- API key permission: `contacts:write`
- Account role: Owner or member
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `contact` (required) | path | string | The contact's `public_id`. |

Example request:

```bash
curl -X DELETE "https://app.linehq.co.uk/api/v1/contacts/CONTACT_ID" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/contacts/CONTACT_ID", {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.delete(
    "https://app.linehq.co.uk/api/v1/contacts/CONTACT_ID",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` Deleted.

```json
{
    "deleted": true
}
```

## To-dos

Follow-ups the AI summary finds in calls.

[Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

### List to-dos

`GET /tasks`

Follow-ups the AI summary found in the account's calls, newest first. Open ones by default.

- API key permission: `read`
- Account role: Owner or member
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `status` | query | one of `"open"`, `"done"`, `"all"` | `open` (the default), `done` or `all`. |
| `page` | query | integer | The page to return, from 1. |
| `per_page` | query | integer | Records per page, up to 100. Defaults to 25. |

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/tasks" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/tasks", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/tasks",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` A page of to-dos.

```json
{
    "data": [
        {
            "public_id": "Wd9Hs4Jf0Pg7YaRk3Lm8Nq2Tz6Vb1Xc5",
            "text": "Call Jo back to book a visit today",
            "status": "open",
            "callback_e164": "+447700900123",
            "sentiment": "neutral",
            "urgency": "high",
            "summary_snippet": "Jo from Harbour Cafe needs a boiler repair today.",
            "created_at": "2026-10-14T09:34:02+01:00",
            "updated_at": "2026-10-14T09:34:02+01:00",
            "completed_at": null,
            "done_by": null,
            "call": {
                "public_id": "Qm4Tz8Kc1Lp6Wr3Nx9Bv2Hd7Fs5Jy0Ga",
                "from_e164": "+447700900123",
                "to_e164": "+442035550123",
                "number_label": "Sales",
                "started_at": "2026-10-14T09:30:12+01:00"
            }
        }
    ],
    "current_page": 1,
    "last_page": 1,
    "per_page": 25,
    "total": 1,
    "next_page_url": null,
    "prev_page_url": null
}
```

### Tick off or reopen a to-do

`PATCH /tasks/{task}`

Mark a to-do done, recording the key's user as the one who did it, or open it again. Needs a key with the `inbox:write` permission.

- API key permission: `inbox:write`
- Account role: Owner or member
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `task` (required) | path | string | The to-do's `public_id`. |

Request body:

| Field | Type | Description |
| --- | --- | --- |
| `status` (required) | one of `"open"`, `"done"` |  |

Example request:

```bash
curl -X PATCH "https://app.linehq.co.uk/api/v1/tasks/TASK_ID" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "done"
}'
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/tasks/TASK_ID", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
      "status": "done"
  }),
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.patch(
    "https://app.linehq.co.uk/api/v1/tasks/TASK_ID",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
    json={
        "status": "done",
    },
)
response.raise_for_status()
print(response.json())
```

Example response, `200` The to-do.

```json
{
    "public_id": "Wd9Hs4Jf0Pg7YaRk3Lm8Nq2Tz6Vb1Xc5",
    "text": "Call Jo back to book a visit today",
    "status": "open",
    "callback_e164": "+447700900123",
    "sentiment": "neutral",
    "urgency": "high",
    "summary_snippet": "Jo from Harbour Cafe needs a boiler repair today.",
    "created_at": "2026-10-14T09:34:02+01:00",
    "updated_at": "2026-10-14T09:34:02+01:00",
    "completed_at": null,
    "done_by": null,
    "call": {
        "public_id": "Qm4Tz8Kc1Lp6Wr3Nx9Bv2Hd7Fs5Jy0Ga",
        "from_e164": "+447700900123",
        "to_e164": "+442035550123",
        "number_label": "Sales",
        "started_at": "2026-10-14T09:30:12+01:00"
    }
}
```

## Notifications

The notifications behind each user's bell in the portal.

[Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

### List notifications

`GET /notifications`

The key's user's notifications, the same ones as the bell in the portal, newest first, with how many are unread.

- API key permission: `read`
- Account role: Owner or member
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `unread` | query | one of `0`, `1` | `1` for unread notifications only. |
| `page` | query | integer | The page to return, from 1. |
| `per_page` | query | integer | Records per page, up to 100. Defaults to 25. |

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/notifications" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/notifications", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/notifications",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` A page of notifications.

```json
{
    "data": [
        {
            "id": "9b2f6c1e-3d4a-4f8b-9c7d-2e1a5b6c7d8e",
            "type": "missed_call",
            "title": "Missed call from +447700900123",
            "body": "To Sales, 09:30",
            "url": "https://app.linehq.co.uk/dashboard",
            "read": false,
            "created_at": "2026-10-14T09:31:00+01:00",
            "read_at": null
        }
    ],
    "unread_count": 1,
    "current_page": 1,
    "last_page": 1,
    "per_page": 25,
    "total": 1,
    "next_page_url": null,
    "prev_page_url": null
}
```

### Mark a notification read

`POST /notifications/{notification}/read`

Needs a key with the `inbox:write` permission.

- API key permission: `inbox:write`
- Account role: Owner or member
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `notification` (required) | path | string | The notification's `id`. |

Example request:

```bash
curl -X POST "https://app.linehq.co.uk/api/v1/notifications/NOTIFICATION_ID/read" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/notifications/NOTIFICATION_ID/read", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.post(
    "https://app.linehq.co.uk/api/v1/notifications/NOTIFICATION_ID/read",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` Marked read.

```json
{
    "id": "9b2f6c1e-3d4a-4f8b-9c7d-2e1a5b6c7d8e",
    "read": true,
    "unread_count": 0
}
```

### Mark every notification read

`POST /notifications/read-all`

Needs a key with the `inbox:write` permission.

- API key permission: `inbox:write`
- Account role: Owner or member
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Example request:

```bash
curl -X POST "https://app.linehq.co.uk/api/v1/notifications/read-all" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/notifications/read-all", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.post(
    "https://app.linehq.co.uk/api/v1/notifications/read-all",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` All read.

```json
{
    "unread_count": 0
}
```

## Message slips

Messages callers leave with the virtual receptionist.

[Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

### List message slips

`GET /reception-messages`

Messages callers left with the virtual receptionist, newest first. The recordings play through Get a recording link.

- API key permission: `read`
- Account role: Owner or member
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `search` | query | string | Part of the caller's number, as international digits. |
| `period` | query | one of `"all"`, `"today"`, `"7d"`, `"30d"`, `"month"`, `"custom"` | `all` (the default), `today`, `7d`, `30d`, `month` (this calendar month), or `custom` with `date_from` and `date_to`. |
| `date_from` | query | string |  |
| `date_to` | query | string |  |
| `status` | query | one of `"unheard"`, `"heard"` | `unheard` or `heard`. |
| `sentiment` | query | one of `"positive"`, `"neutral"`, `"negative"` |  |
| `urgency` | query | one of `"low"`, `"normal"`, `"high"` |  |
| `page` | query | integer | The page to return, from 1. |
| `per_page` | query | integer | Message slips per page, up to 100. Defaults to 20. |

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/reception-messages" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/reception-messages", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/reception-messages",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` A page of message slips.

```json
{
    "data": [
        {
            "public_id": "Hs4Jf0Pg7YaRk3Lm8Nq2Tz6Vb1Xc5Wd9",
            "from_e164": "+447700900123",
            "department": "Sales",
            "received_at": "2026-10-14T18:42:00+01:00",
            "heard_at": null,
            "name_recording_public_id": "Nq2Tz6Vb1Xc5Wd9Hs4Jf0Pg7YaRk3Lm8",
            "message_recording_public_id": "Pg7YaRk3Lm8Nq2Tz6Vb1Xc5Wd9Hs4Jf0",
            "transcript_status": "completed",
            "caller_name": "Jo Smith",
            "summary_status": "completed",
            "sentiment": "neutral",
            "urgency": "normal"
        }
    ],
    "current_page": 1,
    "last_page": 1,
    "per_page": 20,
    "total": 1,
    "next_page_url": null,
    "prev_page_url": null
}
```

## Job requests

Jobs picked out of message slips and calls.

[Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

### List job requests

`GET /job-requests`

Jobs the AI picked out of message slips and calls (who, what, where and when), newest first. Open ones by default.

- API key permission: `read`
- Account role: Owner or member
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `status` | query | one of `"open"`, `"new"`, `"confirmed"`, `"done"`, `"declined"`, `"all"` | `open` (the default: `new` and `confirmed`), one status (`new`, `confirmed`, `done`, `declined`), or `all`. |
| `page` | query | integer | The page to return, from 1. |
| `per_page` | query | integer | Job requests per page, up to 100. Defaults to 20. |

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/job-requests" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/job-requests", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/job-requests",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` A page of job requests.

```json
{
    "data": [
        {
            "public_id": "Yk3Lm8Nq2Tz6Vb1Xc5Wd9Hs4Jf0Pg7Ra",
            "status": "new",
            "caller_name": "Jo Smith",
            "caller_e164": "+447700900123",
            "contact_e164": "+447700900123",
            "line_e164": "+442035550123",
            "line_label": "Sales",
            "job_type": "Boiler repair",
            "description": "Boiler stopped working, no hot water.",
            "address": "4 Harbour Street, Hull",
            "postcode": "HU1 1AA",
            "preferred_when": "Tomorrow morning",
            "preferred_date": "2026-10-15",
            "preferred_time_window": "morning",
            "received_at": "2026-10-14T18:43:10+01:00",
            "done_at": null,
            "done_by": null,
            "message_recording_public_id": "Pg7YaRk3Lm8Nq2Tz6Vb1Xc5Wd9Hs4Jf0",
            "call_public_id": "Qm4Tz8Kc1Lp6Wr3Nx9Bv2Hd7Fs5Jy0Ga"
        }
    ],
    "current_page": 1,
    "last_page": 1,
    "per_page": 20,
    "total": 1,
    "next_page_url": null,
    "prev_page_url": null
}
```

## Blocked callers

Numbers whose calls the account rejects or sends straight to voicemail.

[Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

### List blocked callers

`GET /blocked-callers`

Numbers the account has blocked, newest first. Each says what its calls get.

- API key permission: `read`
- Account role: Owner or member
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/blocked-callers" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/blocked-callers", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/blocked-callers",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` Every blocked caller.

```json
{
    "data": [
        {
            "public_id": "Kc1Lp6Wr3Nx9Bv2Hd7Fs5Jy0GaQm4Tz8",
            "e164": "+447700900666",
            "action": "reject",
            "label": "Sales calls",
            "number": null,
            "created_at": "2026-10-02"
        }
    ]
}
```

## Analytics

Call totals over time.

### Call analytics

`GET /analytics`

Call totals for the last 7, 30 or 90 days: outcomes, calls per day and per hour, calls per number, and the mood of summarised calls. Calls call protection filtered out are counted separately in `summary.filtered`.

- API key permission: `read`
- Account role: Owner or member

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `days` | query | one of `7`, `30`, `90` | `7`, `30` (the default) or `90`. |
| `number` | query | string | Only calls to this number: its `public_id`, from List numbers. A `public_id` that names none of the account's live numbers is a `404`. `by_number` always covers every number. |

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/analytics" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/analytics", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/analytics",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` The totals.

```json
{
    "days": 7,
    "summary": {
        "total": 46,
        "filtered": 3,
        "answered": 38,
        "missed": 5,
        "voicemail": 2,
        "blocked": 1,
        "answer_rate": 83,
        "avg_forward_seconds": 151
    },
    "by_day": {
        "2026-10-07": 4,
        "2026-10-08": 5,
        "2026-10-09": 7,
        "2026-10-10": 6,
        "2026-10-11": 2,
        "2026-10-12": 1,
        "2026-10-13": 11,
        "2026-10-14": 10
    },
    "by_hour": [
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        3,
        6,
        7,
        5,
        4,
        3,
        5,
        4,
        3,
        2,
        0,
        0,
        0,
        0,
        0,
        0
    ],
    "by_number": [
        {
            "public_id": "8kQ2mVn4Lr7Tz1Xc9Bw3Hd6Fs0Jy5Pa2",
            "e164": "+442035550123",
            "label": "Sales",
            "total": 46,
            "answered": 38
        }
    ],
    "sentiment": {
        "positive": 12,
        "neutral": 20,
        "negative": 3
    }
}
```

## Billing

Movements on the prepaid balance, and receipts.

### List balance movements

`GET /billing/ledger`

Every change to the prepaid balance, newest first.

- API key permission: `read`
- Account role: Owner or member

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `page` | query | integer | The page to return, from 1. |
| `per_page` | query | integer | Records per page, up to 100. Defaults to 25. |

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/billing/ledger" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/billing/ledger", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/billing/ledger",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` A page of balance movements.

```json
{
    "data": [
        {
            "type": "charge",
            "amount_pence": -12,
            "balance_after_pence": 2488,
            "note": "Forwarded call 7d3e9a41-2b6c-4f85-9e17-c0a4b8d25f6e",
            "created_at": "2026-10-14T09:33:20+01:00"
        },
        {
            "type": "topup",
            "amount_pence": 2500,
            "balance_after_pence": 2500,
            "note": "Calling credit",
            "created_at": "2026-10-01T10:00:00+01:00"
        }
    ],
    "current_page": 1,
    "last_page": 1,
    "per_page": 25,
    "total": 2,
    "next_page_url": null,
    "prev_page_url": null
}
```

### List receipts

`GET /billing/receipts`

The account's last 48 payments, newest first.

- API key permission: `read`
- Account role: Owner or member

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/billing/receipts" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/billing/receipts", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/billing/receipts",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` The receipts.

```json
{
    "data": [
        {
            "number": "LHQ-261001-K7Q2XM",
            "kind": "topup",
            "amount_pence": 2500,
            "description": "Calling credit",
            "paid_at": "2026-10-01T10:00:00+01:00"
        }
    ]
}
```

## Webhooks

Webhook endpoints LineHQ posts events to, and their deliveries. Owners only, with an API key holding the `webhooks:manage` permission. Adding an endpoint, sending a test and resending need the Integrations add-on; the rest keep working after it lapses, so an owner can tidy up.

### List webhook endpoints

`GET /webhooks`

The account's webhook endpoints. Owners only, with a key holding the `webhooks:manage` permission, because an endpoint's address can be a secret.

- API key permission: `webhooks:manage`
- Account role: Owner only

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/webhooks" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/webhooks", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/webhooks",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` The endpoints, and what can be added.

```json
{
    "can_add": true,
    "events": [
        "call.ended",
        "voicemail.received",
        "transcript.ready"
    ],
    "max": 5,
    "endpoints": [
        {
            "public_id": "Fs5Jy0GaQm4Tz8Kc1Lp6Wr3Nx9Bv2Hd7",
            "name": "Acme CRM",
            "url": "https://crm.acmeplumbing.co.uk/hooks/linehq",
            "events": [
                "call.ended",
                "transcript.ready"
            ],
            "status": "active",
            "failing_since": null
        }
    ]
}
```

### Add a webhook endpoint

`POST /webhooks`

Add an endpoint. Its signing secret is returned once, here; store it to check each delivery's signature. The address must be public HTTPS. At most 20 a minute.

- API key permission: `webhooks:manage`
- Account role: Owner only
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Request body:

| Field | Type | Description |
| --- | --- | --- |
| `name` (required) | string |  |
| `url` (required) | string | An `https://` address on the public internet. |
| `events` (required) | array of one of `"call.ended"`, `"voicemail.received"`, `"transcript.ready"` |  |

Example request:

```bash
curl -X POST "https://app.linehq.co.uk/api/v1/webhooks" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme CRM",
    "url": "https://crm.acmeplumbing.co.uk/hooks/linehq",
    "events": [
        "call.ended",
        "transcript.ready"
    ]
}'
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/webhooks", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
      "name": "Acme CRM",
      "url": "https://crm.acmeplumbing.co.uk/hooks/linehq",
      "events": [
          "call.ended",
          "transcript.ready"
      ]
  }),
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.post(
    "https://app.linehq.co.uk/api/v1/webhooks",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
    json={
        "name": "Acme CRM",
        "url": "https://crm.acmeplumbing.co.uk/hooks/linehq",
        "events": [
            "call.ended",
            "transcript.ready",
        ],
    },
)
response.raise_for_status()
print(response.json())
```

Example response, `201` Added. The secret is never shown again.

```json
{
    "endpoint": {
        "public_id": "Fs5Jy0GaQm4Tz8Kc1Lp6Wr3Nx9Bv2Hd7",
        "name": "Acme CRM",
        "url": "https://crm.acmeplumbing.co.uk/hooks/linehq",
        "events": [
            "call.ended",
            "transcript.ready"
        ],
        "status": "active",
        "failing_since": null
    },
    "secret": "whsec_3f9c2b7a1d8e4f60b5c2a9e7d1f3b8c4"
}
```

### Remove a webhook endpoint

`DELETE /webhooks/{endpoint}`

Remove the endpoint. Deliveries to it stop at once.

- API key permission: `webhooks:manage`
- Account role: Owner only

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `endpoint` (required) | path | string | The endpoint's `public_id`, from List webhooks. |

Example request:

```bash
curl -X DELETE "https://app.linehq.co.uk/api/v1/webhooks/ENDPOINT_ID" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/webhooks/ENDPOINT_ID", {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
```

```python
import os

import requests

response = requests.delete(
    "https://app.linehq.co.uk/api/v1/webhooks/ENDPOINT_ID",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
```

Example response, `204` Removed.

### Change a webhook endpoint

`PATCH /webhooks/{endpoint}`

Change any of its name, address or events, or pause and resume it.

- API key permission: `webhooks:manage`
- Account role: Owner only

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `endpoint` (required) | path | string | The endpoint's `public_id`, from List webhooks. |

Request body:

| Field | Type | Description |
| --- | --- | --- |
| `name` | string |  |
| `url` | string |  |
| `events` | array of one of `"call.ended"`, `"voicemail.received"`, `"transcript.ready"` |  |
| `status` | one of `"active"`, `"paused"` |  |

Example request:

```bash
curl -X PATCH "https://app.linehq.co.uk/api/v1/webhooks/ENDPOINT_ID" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "paused"
}'
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/webhooks/ENDPOINT_ID", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
      "status": "paused"
  }),
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.patch(
    "https://app.linehq.co.uk/api/v1/webhooks/ENDPOINT_ID",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
    json={
        "status": "paused",
    },
)
response.raise_for_status()
print(response.json())
```

Example response, `200` The endpoint.

```json
{
    "endpoint": {
        "public_id": "Fs5Jy0GaQm4Tz8Kc1Lp6Wr3Nx9Bv2Hd7",
        "name": "Acme CRM",
        "url": "https://crm.acmeplumbing.co.uk/hooks/linehq",
        "events": [
            "call.ended",
            "transcript.ready"
        ],
        "status": "active",
        "failing_since": null
    }
}
```

### Replace a webhook endpoint's secret

`POST /webhooks/{endpoint}/rotate`

Issue a new signing secret, returned once, here. Deliveries are signed with the new one from now on.

- API key permission: `webhooks:manage`
- Account role: Owner only

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `endpoint` (required) | path | string | The endpoint's `public_id`, from List webhooks. |

Example request:

```bash
curl -X POST "https://app.linehq.co.uk/api/v1/webhooks/ENDPOINT_ID/rotate" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/webhooks/ENDPOINT_ID/rotate", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.post(
    "https://app.linehq.co.uk/api/v1/webhooks/ENDPOINT_ID/rotate",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` The new secret.

```json
{
    "endpoint": {
        "public_id": "Fs5Jy0GaQm4Tz8Kc1Lp6Wr3Nx9Bv2Hd7",
        "name": "Acme CRM",
        "url": "https://crm.acmeplumbing.co.uk/hooks/linehq",
        "events": [
            "call.ended",
            "transcript.ready"
        ],
        "status": "active",
        "failing_since": null
    },
    "secret": "whsec_8d1e4a7c2f9b3e60a5d2c8f1b7e4a9c3"
}
```

### Send a test event

`POST /webhooks/{endpoint}/test`

Queue a signed test delivery to an active endpoint. At most 10 a minute.

- API key permission: `webhooks:manage`
- Account role: Owner only
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `endpoint` (required) | path | string | The endpoint's `public_id`, from List webhooks. |

Example request:

```bash
curl -X POST "https://app.linehq.co.uk/api/v1/webhooks/ENDPOINT_ID/test" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/webhooks/ENDPOINT_ID/test", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.post(
    "https://app.linehq.co.uk/api/v1/webhooks/ENDPOINT_ID/test",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `202` Queued.

```json
{
    "queued": true
}
```

### List deliveries

`GET /webhooks/{endpoint}/deliveries`

The endpoint's latest 100 deliveries from the last 30 days, newest first.

- API key permission: `webhooks:manage`
- Account role: Owner only

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `endpoint` (required) | path | string | The endpoint's `public_id`, from List webhooks. |

Example request:

```bash
curl "https://app.linehq.co.uk/api/v1/webhooks/ENDPOINT_ID/deliveries" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/webhooks/ENDPOINT_ID/deliveries", {
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://app.linehq.co.uk/api/v1/webhooks/ENDPOINT_ID/deliveries",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `200` The deliveries.

```json
{
    "deliveries": [
        {
            "event": "call.ended",
            "event_id": "2c9e7b14-5f3a-4d8e-b1c6-7a9f0e2d4b63",
            "status": "failed",
            "attempts": 6,
            "response_code": 500,
            "last_error": "HTTP 500",
            "created_at": "2026-10-14T09:33:21+01:00",
            "next_attempt_at": null
        }
    ]
}
```

### Resend a delivery

`POST /webhooks/{endpoint}/deliveries/{eventId}/resend`

Send a failed or cancelled delivery again, as a fresh delivery with the same event id so your receiver can spot the repeat. At most 30 a minute.

- API key permission: `webhooks:manage`
- Account role: Owner only
- [Needs the Integrations add-on (included on Scale; available on Mobile and Reception)](https://linehq.co.uk/pricing)

Parameters:

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `endpoint` (required) | path | string | The endpoint's `public_id`, from List webhooks. |
| `eventId` (required) | path | string | The delivery's `event_id`. |

Example request:

```bash
curl -X POST "https://app.linehq.co.uk/api/v1/webhooks/ENDPOINT_ID/deliveries/EVENT_ID/resend" \
  -H "Authorization: Bearer $LINEHQ_API_KEY" \
  -H "Accept: application/json"
```

```javascript
const response = await fetch("https://app.linehq.co.uk/api/v1/webhooks/ENDPOINT_ID/deliveries/EVENT_ID/resend", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LINEHQ_API_KEY}`,
    Accept: "application/json",
  },
});
if (!response.ok) throw new Error(`LineHQ answered ${response.status}`);
const data = await response.json();
```

```python
import os

import requests

response = requests.post(
    "https://app.linehq.co.uk/api/v1/webhooks/ENDPOINT_ID/deliveries/EVENT_ID/resend",
    headers={"Authorization": f"Bearer {os.environ['LINEHQ_API_KEY']}", "Accept": "application/json"},
)
response.raise_for_status()
print(response.json())
```

Example response, `202` Queued.

```json
{
    "queued": true
}
```

