---
title: "Rate limits"
description: "The request limits on Matchai's public MCP server and listing search today, how to read them, and how limits and RateLimit headers will work in the Matchai API."
last_updated: "2026-10-21"
lang: "en"
canonical: "https://matchai.co/docs/developers/rate-limits"
---

# Rate limits

> The request limits on Matchai's public MCP server and listing search today, how to read them, and how limits and RateLimit headers will work in the Matchai API.

Source: https://matchai.co/docs/developers/rate-limits

Limits keep Matchai fast for everyone. This page states what the public surfaces allow today, and how the planned Matchai API will report and apply its limits.

## On the public surfaces [#on-the-public-surfaces]

These limits apply to the surfaces you can use today without a key. Each is counted per **calling address**: the address your requests come from. Callers behind a shared address, such as an office network or a hosted assistant, share one allowance.

| Surface | Limit | When you go over |
|---|---|---|
| MCP server tool calls (`tools/call` at `https://matchai.co/mcp`) | 600 per minute | The call returns a tool result with `isError: true` and a message asking you to try again in a minute. The HTTP status is still `200`. |
| Listing search (`https://matchai.co/ask`, `GET` and `POST` together) | 60 per minute | `429` with the error `rate_limited` and a `Retry-After` of `60` seconds. |

A few details:

- **The MCP limit counts `tools/call` only.** The handshake, `tools/list` and the other methods are not counted. It is an abuse guard, set high enough that a busy assistant host does not reach it.
- **Listing search can also answer `503`.** When search is temporarily unavailable, it answers `503` with the error `unavailable` and a `Retry-After` of `30` seconds. Wait that long, then try again.
- **AI-readable pages have no per-minute figure**, but ask you to be polite: about one request per second per host. To read a whole site, fetch its `llms-full.txt` instead of crawling page by page. On a `429`, wait for `Retry-After`. The discovery files change at most about once a minute, so polling faster returns the same content.

See [listing search](https://matchai.co/docs/developers/listing-search#limits-caching-and-cors) and the [MCP server](https://matchai.co/docs/developers/mcp-server#access-data-and-limits) for the full picture.

### Handle a limit in code [#handle-a-limit-in-code]

<Tabs items={['JavaScript', 'Python']}>
<Tab value="JavaScript">

```js title="ask-with-wait.mjs"
async function ask(query) {
  const response = await fetch(`https://matchai.co/ask?query=${encodeURIComponent(query)}`);

  if (response.status === 429 || response.status === 503) {
    const wait = Number(response.headers.get('Retry-After') ?? 60);
    await new Promise((resolve) => setTimeout(resolve, wait * 1000));
    return ask(query); // Try once more after the wait.
  }

  return response.json();
}
```

</Tab>
<Tab value="Python">

```python title="ask_with_wait.py"
import time

import requests


def ask(query):
    response = requests.get("https://matchai.co/ask", params={"query": query}, timeout=30)

    if response.status_code in (429, 503):
        time.sleep(int(response.headers.get("Retry-After", 60)))
        return ask(query)  # Try once more after the wait.

    return response.json()
```

</Tab>
</Tabs>

Add a counter in real code, so a request that keeps failing eventually gives up.

## In the Matchai API [#in-the-matchai-api]

**(Coming soon)**

The Matchai API limits requests **per connection**: one Matchai account connected to one app. The figures are not published here yet. Do not hard-code them: the API tells you where you stand on every response.

### Rate limit headers [#rate-limit-headers]

Every response carries three headers:

| Header | Meaning |
|---|---|
| `RateLimit-Limit` | Requests allowed in the current window for this connection |
| `RateLimit-Remaining` | Requests left in the current window |
| `RateLimit-Reset` | Seconds until the window resets |

```http title="Response headers"
RateLimit-Limit: 120
RateLimit-Remaining: 117
RateLimit-Reset: 41
Matchai-Request-Id: req_7Hq2LmZ9xV4c
```

The numbers above are an illustration. They are not the real limit.

### When you go over [#when-you-go-over]

A request over the limit is refused with `429` and does nothing. The response carries `Retry-After`, the number of seconds to wait:

```json title="Response (429)"
{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limited",
    "message": "Too many requests. Try again in 20 seconds.",
    "doc_url": "https://matchai.co/docs/developers/errors#rate_limited",
    "request_id": "req_7Hq2LmZ9xV4c"
  }
}
```

A `429` is safe to retry. Wait for `Retry-After`, then send the request again with the **same** `Idempotency-Key`, so that nothing happens twice. [Retry safely](https://matchai.co/docs/developers/errors#retry-safely) has a complete example.

### Who shares a limit [#who-shares-a-limit]

- **All the keys of one account share one limit**, because they belong to one connection. A second key does not buy more room. See [API keys](https://matchai.co/docs/developers/authentication/api-keys#rotate-a-key).
- **Two professionals connected to the same app have separate limits.** One broker's busy morning does not slow another broker's connection.
- **Public operations work without credentials, with lower limits.** Looking up a professional's published profile or listings needs no key. Those requests are counted per IP address, and sending a key or token raises the limit. They are marked **Free** in the [API reference](https://matchai.co/docs/developers/api-reference).

### Limits are not Energy limits [#limits-are-not-energy-limits]

An action that uses Matchai Energy can be refused for two other reasons, and neither is a `429`:

- The account has run out of Energy: `402` with the code `insufficient_energy`.
- The account holder set an Energy limit for your connection, and you reached it: `402` with the code `energy_limit_reached`.

Waiting does not clear either one. See [errors](https://matchai.co/docs/developers/errors#energy).

### Stay well under the limit [#stay-well-under-the-limit]

- **Read the headers.** Slow down when `RateLimit-Remaining` is low, and pause until `RateLimit-Reset` when it reaches zero.
- **Listen instead of polling.** [Webhooks](https://matchai.co/docs/developers/webhooks) tell you when a lead arrives or a listing changes, with no requests at all.
- **Sync what changed.** Use `updated_since` on list endpoints, and ask for `limit=100` pages, the largest page.
- **Spread out bulk work.** Importing a hundred contacts is a queue, not a burst.
- **Repeat only what failed.** Idempotency keys let you retry a single request safely.

```js title="paced-fetch.mjs"
export async function pacedFetch(url, init) {
  const response = await fetch(url, init);

  const remaining = Number(response.headers.get('RateLimit-Remaining'));
  const reset = Number(response.headers.get('RateLimit-Reset'));

  // Nearly out of requests: wait for the window to reset before the next call.
  if (remaining <= 1 && reset > 0) {
    await new Promise((resolve) => setTimeout(resolve, reset * 1000));
  }

  return response;
}
```

<Cards>
  <Card title="Errors" href="https://matchai.co/docs/developers/errors">
    Every status and code, and how to retry.
  </Card>
  <Card title="Listing search" href="https://matchai.co/docs/developers/listing-search">
    The public endpoint with a 60-per-minute limit.
  </Card>
  <Card title="MCP server" href="https://matchai.co/docs/developers/mcp-server">
    The public tools and their limit.
  </Card>
</Cards>

## Sitemap

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