# Allow tests through your firewall

> How to confirm that a CDN, WAF or rate limiter is blocking a test, what to allowlist (User-Agent first, IP address second), the steps for Cloudflare, Vercel and a generic WAF, and what a 429 does to a test.

If a test you set up has been `Rate limited`, keeps failing with a 403, or gets a challenge page
instead of your response, the CDN, WAF or rate limiter in front of your service is probably
treating Fixter's requests as unwanted traffic. This page helps you confirm that first, then
lets the tests through.

## Confirm that a firewall is the cause

Open the failed run on the test's page (the [API Tests page](https://app.fixter.dev/api-tests)) or
ask your agent for the test's recent runs with `get_api_test_runs`. One of these three patterns
means the block is in front of your service, not in it:

- **The run is `Rate limited`.** The cause reads `Rate limited by the target`: the response was
  HTTP 429. If your service itself never answers 429, the limiter in front of it did.
- **The run failed its status assertion with a 403**, for example `Expected 2xx, got 403`, and
  the recorded response body is not your service's own error. An MCP test shows it as
  `MCP server did not identify itself` on its first run, or as `MCP server rejected <method>`
  once past the handshake.
- **The recorded response is a challenge page.** The body in the run detail is HTML asking for a
  browser check, a captcha or JavaScript, which only bot protection sends. Bodies are cut at
  16 KB, which is enough to recognise one.

Then prove it from outside Fixter. Paste this into a shell, replacing `YOUR_ENDPOINT_URL` with the URL
your test calls, and it sends the same request twice, once with the User-Agent Fixter's tests use and
once without:

```sh
URL=YOUR_ENDPOINT_URL
curl -sS -o /dev/null -w 'with the User-Agent:    %{http_code}\n' -A 'FixterMonitoring/1.0 (+https://fixter.dev)' "$URL"
curl -sS -o /dev/null -w 'without the User-Agent: %{http_code}\n' "$URL"
```

A 403, 429 or 503 on the first and a 200 on the second is a rule that matches the User-Agent. The
same code on both is a rule that matches something else (an IP range, missing cookies, a
geography) or a limit your service has really hit; the vendor steps below cover both.

Your own CDN or WAF logs settle it fastest: filter them for the User-Agent `FixterMonitoring` and
the action the rule took. If those logs already reach Fixter, ask your agent for log lines carrying
that User-Agent, or open the failed run and use **Open in Logs**, which searches on the run's
trace id (how that works is on [Debugging](/using-fixter/debugging/)).

## What to allow

| Allow by | Value | Applies to |
|---|---|---|
| User&#8209;Agent | `FixterMonitoring/1.0 (+https://fixter.dev)` | Every request an HTTP test or an MCP test sends, and every OAuth2 token fetch a credential makes |
| IP&nbsp;address | `34.247.142.193` | All of them: one static IPv4 address in eu-west-1 |

**Allow by User-Agent where your firewall can match a header.** A User-Agent rule admits only the
tests. An IP address rule admits every request that leaves Fixter's infrastructure, because the
address is shared by all of it, so use the IP only where the firewall cannot match a header
(Cloudflare's free plan is the common case).

Two details that decide which rule works:

- A header the test or its credential sets wins over the fixed User-Agent: an HTTP test that sets
  its own `User-Agent` request header sends that value instead, and a User-Agent rule does not
  match it. Remove the header or allow by IP address.
- The IP address changes only if the infrastructure behind it is rebuilt, and a change of address
  is announced ahead of time on this page.

## Allow the tests

Pick the variant that matches your firewall. Menu names are the vendor's, as their documentation
gives them.

<Tabs>
  <TabItem label="Cloudflare (Free)">

  Bot Fight Mode, the Free plan's bot protection, cannot be skipped with a WAF custom rule:
  "You cannot bypass or skip Bot Fight Mode using WAF custom rules or Page Rules", but "it will not
  trigger if an IP Access rule matches the request first"
  ([Cloudflare: Bot Fight Mode](https://developers.cloudflare.com/bots/get-started/bot-fight-mode/)).
  So allow the IP address with an IP Access rule
  ([Cloudflare: Create an IP access rule](https://developers.cloudflare.com/waf/tools/ip-access-rules/create/)):

  1. Open the **Security rules** page for your zone.
  2. Choose **Create rule** > **IP access rules**.
  3. Enter `34.247.142.193`, choose the action **Allow** and the **Zone** scope.
  4. Select **Create**.

  Allow "Excludes visitors from all security checks, including Browser Integrity Check, Under Attack
  mode, and the WAF"
  ([Cloudflare: IP access rule actions](https://developers.cloudflare.com/waf/tools/ip-access-rules/actions/)).

  Cloudflare shows the **IP access rules** option only once at least one IP access rule exists. If
  it is not there, create the rule with Cloudflare's API ("Create an IP Access Rule"), as the same
  page describes.

  </TabItem>
  <TabItem label="Cloudflare (Pro and above)">

  On Pro, Business and Enterprise, Super Bot Fight Mode takes exceptions from a custom rule with the
  Skip action
  ([Cloudflare: Super Bot Fight Mode](https://developers.cloudflare.com/bots/get-started/super-bot-fight-mode/)).
  Following [Cloudflare: Skip action](https://developers.cloudflare.com/waf/custom-rules/skip/):

  1. Open the **Security rules** page for your zone.
  2. Choose **Create rule** > **Custom rules**.
  3. Use the expression `http.user_agent contains "FixterMonitoring"`, or
     `ip.src eq 34.247.142.193` to match by IP address.
  4. Choose the action **Skip** and tick **All Super Bot Fight Mode rules**. If a Cloudflare rate
     limiting rule is what returns the 429s, tick **All rate limiting rules** as well
     ([Cloudflare: Skip options](https://developers.cloudflare.com/waf/custom-rules/skip/options/)).

  </TabItem>
  <TabItem label="Vercel">

  Vercel's Bot Protection challenges non-browser traffic. "For trusted automated traffic, you can
  create custom WAF rules with bypass actions that will allow this traffic to skip the bot
  protection ruleset" ([Vercel: Bot Management](https://vercel.com/docs/bot-management)). Following
  [Vercel: WAF custom rules](https://vercel.com/docs/vercel-firewall/vercel-waf/custom-rules):

  1. Open the project's **Firewall** in the sidebar, then **⋯** > **Configure**.
  2. Choose **Add New...** > **Rule**.
  3. Under **If**, add a condition on **User Agent** containing `FixterMonitoring`
     ([Vercel: Bypassing rulesets](https://vercel.com/docs/vercel-firewall/vercel-waf/managed-rulesets)).
     To match by IP address instead, use the same rule with a condition on the IP address
     `34.247.142.193`.
  4. Under **Then**, choose **Bypass**.
  5. Select **Save Rule**, **Review Changes**, then **Publish**.

  Traffic a custom bypass rule matches "is allowed through any custom or managed rules"
  ([Vercel: Firewall concepts](https://vercel.com/docs/vercel-firewall/firewall-concepts)).

  **System Bypass** is a different thing: it exempts an IP address from Vercel's system mitigations
  (DDoS protection), not from Bot Protection, and is on Pro and Enterprise only. If you need it:
  **Firewall** > **Add New** > **System Bypass**, enter `34.247.142.193` under
  **IP Address Or CIDR**, a **Domain** (`*` for all of the project's domains) and an optional
  **Note**, then **Create System Bypass**
  ([Vercel: System Bypass rules](https://vercel.com/docs/vercel-firewall/vercel-waf/system-bypass-rules)).

  **Attack Mode** challenges every visitor except verified bots and your own Functions and Cron Jobs,
  and Vercel does not say that a bypass rule lets other traffic past it. Turn it off once the attack
  is over: **Firewall** > **Bot Management** > **Attack Mode** > **Disable**
  ([Vercel: Attack Mode](https://vercel.com/docs/vercel-firewall/attack-mode)).

  </TabItem>
  <TabItem label="Other WAF, nginx or your own limiter">

  Exempt the User-Agent `FixterMonitoring/1.0 (+https://fixter.dev)`, or the IP address
  `34.247.142.193`, from the rule that blocks or limits the tests.

  If you cannot exempt it, set the limit above what one test sends per interval. A run is up to
  1 + retries attempts (retries happen on failures only, never on a 429). Per attempt:

  - **HTTP test**: one request, plus one per redirect followed, at most five redirects, so at most
    six requests.
  - **MCP test on 2026-07-28**: one request for `Handshake`, two for `Tools list`, three for
    `Tool call`.
  - **MCP test on 2025-03-26, 2025-06-18 or 2025-11-25**: two to four requests by check depth, plus a
    closing `DELETE` when the server opened a session, so up to five.
  - **MCP test on 2024-11-05**: three, four or five requests by check depth, counting the GET that opens
    the event stream.
  - **Looking up the revision**: a `Newest supported` test that has no revision yet sends up to four
    more requests first to look it up. Any MCP test whose server answers as if it changed revision
    also sends those four, and a `Newest supported` test then repeats the attempt at the revision
    found.

  Size the limit for the worst case you run, for example a `Tool call` test on an older revision with
  retries, and count every test that points at the same endpoint.

  </TabItem>
</Tabs>

Once the rule is in, the next run passes within one interval, or after the `Retry-After` you
sent when that is later, and the two `curl` requests above return the same status. One passing
run returns a rate-limited or failing test to `Up`.

## What a 429 means for a test

When your side answers HTTP 429:

- **The run is `Rate limited`, not failed.** Its status and cause are `RATE_LIMITED`, and the app
  shows the cause as `Rate limited by the target`. For an MCP test, a 429 on any request of the
  session counts.
- **It is not retried**, whatever the test's retry setting.
- **`Retry-After` is honoured.** If your response carries it, the next run is deferred by it, up to
  one hour, when that is later than the next normal run. The run message then reads
  `Rate limited by the target (HTTP 429); next check deferred by <N> seconds` (or minutes); with
  nothing deferred it reads `Rate limited by the target (HTTP 429)`.
- **The test turns `Rate limited` after the failure threshold.** After as many rate-limited runs
  in a row as the test's failure threshold, the test's state turns `Rate limited` and it sends one
  notification, `<name> is being rate limited`. The next success returns it to `Up` and sends
  `No longer rate limited: <name>`. See [Notifications](/api-testing/notifications/).
- **Uptime is unaffected.** Rate-limited runs are left out of uptime and never make a downtime
  interval.
- **A 429 during the trial run on save means the test is not saved.** The app shows
  `Trial run was rate limited by the target`. Allow the tests first, then save again.

If an HTTP test's [status pattern](/api-testing/http-tests/#status-pattern) accepts 429 (for example
`429` or `4xx`), a 429 is an ordinary response checked against the assertions, and none of the
above applies. The full rules are in
[Retries and Retry-After](/api-testing/runs-and-uptime/#retries-and-retry-after) and
[How states change](/api-testing/runs-and-uptime/#how-states-change).

## If it is still blocked

A 403 or a challenge page is usually bot protection, not rate limiting, and the same allowlist fixes it.
The test does not show it as `Rate limited`:

- An HTTP test fails its status assertion, for example `Expected 2xx, got 403`. The run is a
  `FAILURE`: it is retried, counts toward the test going down and counts against uptime.
- An MCP test that has not yet found its server's revision (a `Newest supported` test on its
  first run, including the trial run on save) ends the revision lookup as none on a 401, 403 or 5xx. It
  fails assertion `mcp.initialize`, `MCP server did not identify itself`, with the run message
  `the target did not identify itself as an MCP server`.
- An MCP test that gets past the handshake and then meets a bare non-2xx fails as `CONNECT` with
  `MCP server rejected <method>`, for example `MCP server rejected tools/list`.

Open the failed run to see the response your firewall sent; the run detail is described on
[Runs, states and uptime](/api-testing/runs-and-uptime/#what-a-run-records). If the test went down, the
message it sent is on [Notifications](/api-testing/notifications/). If the test sets its own
`User-Agent` request header, a User-Agent rule does not match it; remove the header or allow
by IP address.

Still stuck? Email [info@fixter.dev](mailto:info@fixter.dev) and one of our founders will help you fix your problem asap.