# Authenticate with the REST API

The Relaypoint REST API lets your own systems create conversations, read customers and manage your workspace. Every request is authenticated with an **API key** sent as a Bearer token over HTTPS.

![A diagram: your app calls the REST API with a Bearer key; the API feeds an event bus, which posts signed webhooks to your endpoint](https://demo-doc-site.clevera.help/media/f9a06160c37c25da597ace897d9cabfcb04a03dea8b905d52ed40a4bfa2f4ca2.png "How the API and webhooks fit together")

## Before you start

- [x] You're an **Admin** or **Owner** in the workspace
- [ ] You've created an API key (below)
- [ ] You've stored it somewhere safe, such as a secrets manager

:::warning Keep keys secret
An API key acts with your permissions. Never put it in client-side code, a public repository or a support conversation.
:::

## Create an API key

:::steps
### Open API settings

Go to **Settings › API keys** and select **Create key**.

### Choose scopes

Give the key only what it needs, e.g. `conversations:read` and `conversations:write`.

### Copy the key

The key is shown **once**. Copy it into your secrets manager before closing the dialog.
:::

## Make your first request

Base URL: `https://api.relaypoint.example/v1`. Send the key in the `Authorization` header.

:::tabs
@tab cURL

```bash
curl https://api.relaypoint.example/v1/conversations?status=open \
  -H "Authorization: Bearer $RELAYPOINT_KEY" \
  -H "Accept: application/json"
```

@tab JavaScript

```javascript
const res = await fetch('https://api.relaypoint.example/v1/conversations?status=open', {
  headers: { Authorization: `Bearer ${process.env.RELAYPOINT_KEY}` },
});
if (!res.ok) throw new Error(`Relaypoint ${res.status}`);
const { data } = await res.json();
console.log(`${data.length} open conversations`);
```

@tab Python

```python
import os
import requests

res = requests.get(
    "https://api.relaypoint.example/v1/conversations",
    params={"status": "open"},
    headers={"Authorization": f"Bearer {os.environ['RELAYPOINT_KEY']}"},
    timeout=10,
)
res.raise_for_status()
print(len(res.json()["data"]), "open conversations")
```

@tab PHP

```php
<?php
$response = Http::withToken(env('RELAYPOINT_KEY'))
    ->get('https://api.relaypoint.example/v1/conversations', ['status' => 'open'])
    ->throw();

echo count($response->json('data')) . " open conversations\n";
```

@tab Ruby

```ruby
require "net/http"
require "json"

uri = URI("https://api.relaypoint.example/v1/conversations?status=open")
req = Net::HTTP::Get.new(uri, "Authorization" => "Bearer #{ENV.fetch('RELAYPOINT_KEY')}")
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts JSON.parse(res.body)["data"].length
```

@tab Go

```go
req, _ := http.NewRequest("GET", "https://api.relaypoint.example/v1/conversations?status=open", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("RELAYPOINT_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
    log.Fatal(err)
}
defer res.Body.Close()
```
:::

A successful response looks like this:

```json
{
  "data": [
    {
      "id": "conv_8h2k1x",
      "subject": "Webhook deliveries failing with 401",
      "status": "open",
      "customer": { "name": "Jamie Kim", "email": "jamie@example.com" },
      "assignee": null,
      "created_at": "2026-10-30T09:14:22Z"
    }
  ],
  "next_cursor": "eyJpZCI6ImNvbnZfOGgyazF4In0"
}
```

## Errors

| Status | Code              | What to do                                       |
| -----: | ----------------- | ------------------------------------------------ |
|    400 | `invalid_request` | Fix the parameter named in `error.field`         |
|    401 | `unauthenticated` | Check the key and the `Bearer ` prefix           |
|    403 | `missing_scope`   | Create a key with the scope named in the message |
|    404 | `not_found`       | The id doesn't exist in this workspace           |
|    429 | `rate_limited`    | Wait `Retry-After` seconds, then retry           |

:::info Rate limits
Each key can make **600 requests per minute**. Every response carries `X-RateLimit-Remaining` so you can slow down before you hit the limit.
:::

:::details Pagination
List endpoints return up to 50 items and a `next_cursor`. Pass it back as `?cursor=` to get the next page. When `next_cursor` is `null`, you've reached the end.
:::

::image{src="https://demo-doc-site.clevera.help/media/f1720c0226a707f03437b36b9566dceabbe5cebbb9ba588e2c38fe8107d2a306.png" alt="The Integrations settings page with six integration tiles: Email forwarding and Live chat widget connected, Webhooks, Team chat and CRM sync ready to set up, and Status page coming soon" caption="Prefer no code? Many tools connect from Settings › Integrations." align="center" width="70%"}

## Video walkthrough

::embed{src="https://share.clevera.ai/embed/?id=video_qsspzdmxhwzsnbf6&token=raW6OxjFecKTfznoBkuSidzPT0yIGqR6&language_code=en-US" ratio="16:9" title="Create and share a dashboard (demo video with dummy data)"}

:::success
That's it: you're authenticated. Next, [receive events with webhooks](docs:section/dsec_az9e5fjk9tfgbsxy) so you don't have to poll.
:::
