---
title: "Authentication"
description: "Two ways to call the Matchai API: API keys for your own account, and Connect with Matchai (OAuth 2.1) for apps. Scopes, final actions and Energy limits."
last_updated: "2026-10-21"
lang: "en"
status: "coming-soon"
canonical: "https://matchai.co/docs/developers/authentication"
---

# Authentication

> Two ways to call the Matchai API: API keys for your own account, and Connect with Matchai (OAuth 2.1) for apps. Scopes, final actions and Energy limits.

**Coming soon. This is documented ahead of launch so you can plan your integration; it is not available yet.**

Source: https://matchai.co/docs/developers/authentication

Every request to the Matchai API acts for **one Matchai account**. There are two ways to get the credential that proves it, and the right one depends on whose account you are working with.

## Two ways in [#two-ways-in]

| | API keys | Connect with Matchai |
|---|---|---|
| Use it for | Your own scripts and your own AI tools, on your own account | An app that serves other professionals: a website builder, a CRM, an AI assistant |
| Who grants access | You, when you create the key | The professional, by pressing **Allow** |
| Credential | A key that starts with `matchai_` | An OAuth 2.1 access token, with a refresh token |
| Scopes | Chosen when you create the key | Requested by your app and shown to the professional |
| Set-up | Create a key in Matchai | [Create a Matchai app](https://matchai.co/docs/developers/authentication/create-a-matchai-app), then run the [OAuth flow](https://matchai.co/docs/developers/authentication/connect-with-matchai) |
| Ended by | You revoke the key | The professional presses **Disconnect** |

Both go in the same header:

```http title="Request header"
Authorization: Bearer matchai_…
```

An OAuth access token is sent the same way, in place of the key.

<Cards>
  <Card title="API keys" href="https://matchai.co/docs/developers/authentication/api-keys">
    Create, scope, rotate and revoke a key for your own account.
  </Card>
  <Card title="Create a Matchai app" href="https://matchai.co/docs/developers/authentication/create-a-matchai-app">
    Register the app that professionals will connect to.
  </Card>
  <Card title="Connect with Matchai" href="https://matchai.co/docs/developers/authentication/connect-with-matchai">
    The OAuth 2.1 flow, the Allow page and a full code example.
  </Card>
</Cards>

<Callout title="Why every credential reaches one account only">
A key or token is bound to the account that granted it, so it can never read another professional's listings, contacts or messages. An app that serves three brokers holds three connections, each with its own tokens, however it is built. This is also why a personal key may not be used to run a service for other people: serving other professionals goes through Connect with Matchai, where each one agrees to it.
</Callout>

## What a request can do [#what-a-request-can-do]

A request can do what the account holder can do in Matchai, under the same rules and permissions, and nothing more. Two things narrow it further:

- **Scopes** limit which parts of the account the credential can touch.
- **Final actions** wait for the account holder's approval, unless they allowed your app to run them.

`GET /v1/me/connection` ([`getConnection`](https://matchai.co/docs/developers/api-reference/me/getConnection)) tells your app how it is connected: the app, the scopes granted, `auth_type` (`api_key` or `oauth`), `approval_mode` (`ask_me` or `allowed`) and whether the connection is in test mode.

## Scopes [#scopes]

Each operation in the [API reference](https://matchai.co/docs/developers/api-reference) lists the scopes it needs. Ask only for what your app uses: a professional is more likely to press **Allow** when the list is short and every line makes sense.

| Scope | What it allows |
|---|---|
| `profile:read` | Read the account's profile, language, time zone and Energy state |
| `profile:write` | Change the account's profile |
| `listings:read` | Read listings, their photos, rooms, documents and translations |
| `listings:write` | Create and edit listings, their photos, rooms and translations, and delete them |
| `listings:publish` | Publish and unpublish listings |
| `sites:read` | Read Matchai Sites and the listings on them |
| `sites:write` | Create, edit and publish Matchai Sites, and choose their listings |
| `page:read` | Read the Matchai Page and its links |
| `page:write` | Edit and publish the Matchai Page, its links and files |
| `contacts:read` | Read contacts and lead details |
| `contacts:write` | Add, edit, convert and delete contacts, and record website enquiries |
| `messages:read` | Read conversations, messages and sent emails, mark conversations read or archive them, and draft replies |
| `messages:send` | Send messages and emails, and start conversations, in the account holder's name |
| `calendar:read` | Read availability and appointments |
| `calendar:write` | Change availability, book and change appointments, and record visit requests |
| `documents:read` | Read Matchai Cloud documents and drop links |
| `documents:write` | Upload, file, tag, share and trash documents, and create drop links |
| `teams:read` | Read teams, their members and invitations |
| `teams:write` | Create and manage teams, and invite or remove members |
| `projects:read` | Read projects, their members, teams and listings |
| `projects:write` | Create and manage projects, and invite or remove members |
| `notifications:read` | Read notifications and notification preferences |
| `notifications:write` | Mark notifications as read and change notification preferences |
| `saved_listings:read` | Read the listings the account saved |
| `saved_listings:write` | Save and unsave listings |
| `analytics:read` | Read listing analytics, including AI activity |
| `webhooks:manage` | Manage webhook subscriptions and read events |

When a credential lacks a scope, the call fails with `403` and the code `scope_missing`, and the message names the scope. For a key, create a new key that has the scope and [revoke the old one](https://matchai.co/docs/developers/authentication/api-keys#rotate-a-key). For an OAuth connection, send the professional through your app's [connect flow](https://matchai.co/docs/developers/authentication/connect-with-matchai) again, so they can agree to the longer list.

A few operations need no scope because they only work with what is already public, such as looking up a professional's published profile. They are marked **Free** in the reference, and a key or token is optional for them.

## Final actions [#final-actions]

Four kinds of action are **final**: sending something to another person, publishing something to the public, paying, and deleting the account's content. The Matchai Assistant always asks before it does any of them, and the API holds your app to the same rule. Each final action is marked **Final action** in the reference.

The account holder chooses how your app handles them, per connection:

| Setting | What happens when your app calls a final action |
|---|---|
| **Ask me** (the default) | The call does not run. It returns `202 Accepted` and waits for the account holder to approve it in Matchai. |
| **Allowed** | The call runs straight away and returns its usual response. |

Anything that is not a final action runs under the credential's scopes, as the same edit would in the app: reads, drafts, and changes to the account holder's own records. Removing your own app's configuration, such as a webhook subscription, is not a final action either.

**Write your app for both settings.** The account holder can change the setting at any time, so never assume which one you will get.

### Follow an approval [#follow-an-approval]

A final action under **Ask me** answers `202` and does nothing yet:

```json title="Response (202)"
{
  "status": "pending_approval",
  "approval_id": "1e2f3a4b-5c6d-4e7f-8a8b-9c0d1e2f3a4b",
  "approval_url": "https://matchai.co/approvals/1e2f3a4b-5c6d-4e7f-8a8b-9c0d1e2f3a4b",
  "expires_at": "2026-10-25T13:05:00Z",
  "summary": "Send a message to Julien Bouchard"
}
```

<Steps>
<Step>

**Tell the person.** Matchai notifies the account holder, who approves or declines on a page that describes the action in plain words. Show `summary` in your own interface and link to `approval_url`, so the person can answer from where they are.

</Step>
<Step>

**Wait for the outcome.** The `approval.resolved` webhook arrives when the request is answered, or when it lapses. You can also poll `GET /v1/approvals/{approval_id}` ([`getApproval`](https://matchai.co/docs/developers/api-reference/approvals/getApproval)). Webhooks are the better choice: nobody has to guess how long a person takes.

</Step>
<Step>

**Read the result.** When the request is `approved`, Matchai has performed the call **exactly as you sent it**, and the `result` holds the status and body the call returned. Send the exact text the person saw. Never rewrite a draft they approved.

</Step>
</Steps>

An approval has one of five statuses:

| Status | Meaning |
|---|---|
| `pending` | Waiting for the account holder. Nothing has happened. |
| `approved` | Approved, and Matchai has performed the call. `result` holds the outcome. |
| `declined` | The account holder said no. Nothing happened. |
| `expired` | Nobody answered before `expires_at`. Nothing happened. |
| `cancelled` | Your app withdrew the request with `POST /v1/approvals/{approval_id}/cancel` ([`cancelApproval`](https://matchai.co/docs/developers/api-reference/approvals/cancelApproval)). |

`GET /v1/approvals` ([`listApprovals`](https://matchai.co/docs/developers/api-reference/approvals/listApprovals)) lists the requests your connection has made, and takes a `status` filter.

Send an `Idempotency-Key` with every final action. If your request times out and you repeat it with the same key, you get the same pending approval back instead of a second one.

This example publishes a listing and handles both settings:

```js title="publish.mjs"
import { randomUUID } from 'node:crypto';

const response = await fetch(`https://api.matchai.co/v1/listings/${listingId}/publish`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.MATCHAI_API_KEY}`,
    'Idempotency-Key': randomUUID(),
  },
});

if (response.status === 202) {
  // Ask me: nothing has happened yet.
  const pending = await response.json();
  console.log(`Waiting: ${pending.summary}`);
  console.log(`The account holder can answer at ${pending.approval_url}`);
} else if (response.ok) {
  // Allowed: the listing is published now.
  const listing = await response.json();
  console.log(`Published at ${listing.url}`);
} else {
  const { error } = await response.json();
  throw new Error(`${error.code}: ${error.message}`);
}
```

## Energy limits [#energy-limits]

Some actions spend Matchai Energy, the way the AI features do in the app: writing a listing with AI, editing photos, translating, drafting with AI, sending an email, and messages that go out by text. The reference marks each of them, and says how heavy the action is: light, moderate, heavy, or it varies.

- **The API never shows Energy as a number.** `GET /v1/me` returns its state: `full`, `comfortable`, `low` or `empty`.
- **A call that needs more than is left does nothing.** It answers `402` with the code `insufficient_energy`, and nothing is spent.
- **The account holder can set an Energy limit for each connection**, so a runaway script stops at a ceiling they chose. When your connection reaches it, calls that spend Energy answer `402` with the code `energy_limit_reached`, and `GET /v1/me/connection` reports `energy_limit_reached: true`.

Show the `message` of a `402` to the person: it says what happened and what to do next. See [errors](https://matchai.co/docs/developers/errors#insufficient_energy).

## Ending a connection [#ending-a-connection]

A connection ends in one of three ways, and each one stops every credential that belongs to it:

- The professional presses **Disconnect** on the Connectors page.
- You revoke an [API key](https://matchai.co/docs/developers/authentication/api-keys#revoke-a-key).
- Your app calls `DELETE /v1/me/connection` ([`disconnect`](https://matchai.co/docs/developers/api-reference/me/disconnect)). It ends the connection as if the professional had pressed **Disconnect**, and deletes nothing in the account.

After a connection ends, its webhook subscriptions are turned off and its requests answer `401`. Treat that as "connect again", not as an outage.

## Test mode [#test-mode]

An app that is connected only to its own developer's account is in **test mode**. Test connections are not billed, and the events they produce carry `test_mode: true`. [Create a Matchai app](https://matchai.co/docs/developers/authentication/create-a-matchai-app#test-mode) explains how it works, and who pays once other professionals connect.

<Cards>
  <Card title="Errors" href="https://matchai.co/docs/developers/errors">
    What each status and code means, and what to do about it.
  </Card>
  <Card title="Rate limits" href="https://matchai.co/docs/developers/rate-limits">
    How requests are limited per connection.
  </Card>
  <Card title="API reference" href="https://matchai.co/docs/developers/api-reference">
    Scopes, pricing and final actions for every endpoint.
  </Card>
</Cards>

## Sitemap

- [Every page of Matchai Docs](https://matchai.co/docs/sitemap.md)
- [llms.txt](https://matchai.co/docs/llms.txt)
