# Why a Proxy Request Ran Out of Tries

> URL: https://foura.ai/docs/troubleshooting/proxy-attempt-report
> Updated: 2026-09-30
> Index: https://foura.ai/llms.txt

## The Problem

A `POST /api/proxy/` call comes back with an error and no data. The message is short and always the same shape:

```json
{
  "error": "Download maxTry limit reached",
  "total": 34.812,
  "request": { "...": "..." }
}
```

That sentence reads identically whether every exit was blocked, every exit was dead, or FourA fetched the real page on almost every attempt and your own `validate` rules threw it away. Those three need opposite fixes.

## The Answer: `attemptReport`

Every Proxy call that ran its attempts and failed carries an `attemptReport` object beside the error, including one that ended with `"error": "Timeout"`. A host name that doesn't resolve fails before any attempt, with `status: 0` and no report. It counts what the attempts actually ran into:

```json
{
  "error": "Download maxTry limit reached",
  "attemptReport": {
    "total": 25,
    "noResponse": 0,
    "defense": 0,
    "contentRejected": 25,
    "statusRejected": 0,
    "other": 0,
    "vendors": [],
    "profilesTried": ["default"],
    "summary": "25 attempt(s): 25 returned HTTP 200 with no defense present and were rejected only by your validate.data - the page was fetched, your content rule did not match it"
  },
  "total": 34.812
}
```

The `error` string is deliberately unchanged, so a client matching on it keeps working. Read `attemptReport.summary` for a one-line answer, or the counts when you want to branch on them.

### Fields

| Field | Type | What it counts |
|-------|------|----------------|
| `total` | integer | Attempts made |
| `noResponse` | integer | The exit never answered, or the site didn't answer in time |
| `defense` | integer | The site answered and a bot-check vendor was recognised on that answer |
| `contentRejected` | integer | HTTP 200, no bot check, rejected only by your `validate.data` |
| `statusRejected` | integer | The site answered, no bot check, rejected by your `validate.status` |
| `other` | integer | Answered, and none of the above |
| `vendors` | string[] | Every bot-check vendor recognised anywhere in the task |
| `profilesTried` | string[] | The browser profiles the task sent, in first-use order. `default` means the request went out exactly as you wrote it. |
| `summary` | string | One sentence built from the counts. Safe to log or show a user. |

## Reading It

### `contentRejected` is high

The pages arrived. Your `validate.data` rule didn't match them.

This is the one you can fix yourself, and it's the one every other signal hides: the requests look like failures in every metric, and FourA was delivering real content the whole time. Fetch the page once through `POST /api/single/` with no `validate` at all, look at what actually comes back, and rewrite the rule against it.

A common cause is one rule applied to a set of pages that aren't all the same. A selector that exists on article pages and not on video pages fails every time it lands on a video page, forever, at full cost.

### `statusRejected` is high

The site answered and your `validate.status` rule said no. If those statuses are 401, 403, 429, or 503, the site is refusing the client rather than saying the page isn't there. Try:

- Another browser profile (`browser`, `os`, `version` on the inner `request` object)
- `exitCountries` if the content is region-locked
- `POST /api/browser/` if the refusal needs JavaScript to clear

### `defense` is high

A bot check was recognised on the answers, and `vendors` names which. See [Site checks](https://foura.ai/docs/api/site-checks) for what FourA clears today and what it only reports. If the vendor isn't one that gets cleared on this endpoint, move the call to `POST /api/browser/` or `POST /api/auto/`.

### `noResponse` is high

The exits didn't answer at all, so nothing was learned about the target. Raise `maxTries`, raise `timeout_ms`, and check the URL resolves from the public internet.

### `other` is high

The site answered with a status, no bot check was recognised, and the attempt failed for another reason, such as a `validate.headers` rule. A target slower than your `timeout_ms` counts under `noResponse`.

## Browser Profile Rotation

When a site refuses the browser FourA sent, Proxy stops insisting on it and tries another family from the [public profile catalogue](https://foura.ai/docs/api/endpoints). It costs no extra attempt: the rotation changes what a retry sends, never whether one happens.

Proxy also remembers, per site, the family that last worked there after the site refused the default. A later request to that site can start on that family from its first attempt, and FourA moves on to the next family once the site starts refusing that one too. A phone browser is only remembered for a site that answers JSON.

`profilesTried` is how you see it happen. One entry means every attempt sent the same browser: `default` is the request as written, and a catalogue id is the family FourA had already learned for that site. Several mean the rotation ran and the site refused each of them, which is a different situation from never having rotated at all.

On a **successful** Proxy response, a `profile` field appears only when the rotation chose a browser you didn't ask for:

```json
{
  "status": 200,
  "data": "<!doctype html>...",
  "proxy": "A1B2C3",
  "profile": "...",
  "total": 4.108
}
```

The value is a catalogue id from `GET /api/profiles`. Absent means the request went out exactly as written. Present means the browser that worked wasn't the one you typed, so pass that id back as `profile` on follow-up calls rather than replaying the one that failed. The dashboard [Playground](https://foura.ai/docs/dashboard/playground) does this for you with **Carry**.

An explicit `profile`, `browser`, `os`, or `version` on your request is never overridden. Neither is a request carrying your own `User-Agent` or `Cookie` header, since a clearance is bound to the signature that earned it.

## Reading It in Code

```python
import requests

API = "https://eu.api.foura.ai"
H = {"X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json"}

r = requests.post(f"{API}/api/proxy/", headers=H, json={
    "maxTries": 5,
    "request": {
        "method": "GET",
        "url": "https://example.com/product/42",
        "validate": {"data": {"accept": ["Add to cart"]}},
    },
}).json()

if "error" in r:
    rep = r.get("attemptReport", {})
    print(rep.get("summary", r["error"]))

    if rep.get("contentRejected", 0) > rep.get("total", 0) / 2:
        # The pages arrived. The validate rule is what threw them away.
        raise SystemExit("validate.data did not match the real page")
    if rep.get("defense", 0):
        print("bot check met:", ", ".join(rep.get("vendors", [])))
```

## Related

- [API Endpoints](https://foura.ai/docs/api/endpoints): The full Proxy request and response reference
- [Site checks](https://foura.ai/docs/api/site-checks): Vendors, `defense`, and replaying a clearance
- [Request Outcomes](https://foura.ai/docs/api/outcomes): How a rejected response is classified and billed
- [Common Issues](https://foura.ai/docs/troubleshooting/common-issues): Other problems and their fixes
- [Choosing the Right Endpoint](https://foura.ai/docs/guides/choosing-task-type): When Proxy is the wrong tool
