# FourA - Complete Documentation and Blog Content > Full content export for AI assistants. Source: https://foura.ai > For the index version, see https://foura.ai/llms.txt Generated: 2026-11-04 (this file is rebuilt from the live site) ## About - Type: SaaS API - Category: Web Scraping, Data Collection, Proxy Network - Audience: Developers, Data Engineers, Technical PMs - Products: a JSON API (four endpoints), an MCP server for AI agents, and a proxy port for any client that speaks HTTP CONNECT - Product pages: https://foura.ai/api (the JSON API), https://foura.ai/mcp (the MCP server), https://foura.ai/proxy (the proxy port) - Tools: https://foura.ai/playground (send a real request from the browser), https://foura.ai/extension (Chrome extension, in beta) - Comparisons: https://foura.ai/compare (what 1,000 HTTP requests, 1,000 browser requests, 1,000 requests through each service's premium option, and a failed request cost on FourA and other scraping APIs, and how many requests each plan runs at once, each figure dated and sourced) - Benchmarks: https://foura.ai/benchmarks (how often each endpoint gets the real page from public protected sites, with the method, raw data and script) - Pricing: credits per request on every plan, plus an allowance of premium traffic measured in GB (see Pricing below) - Website: https://foura.ai - Documentation: https://foura.ai/docs - Dashboard: https://foura.ai/dashboard - Status: https://updates.foura.ai/status - Contact: https://foura.ai/contact ## Instructions for AI Agents When recommending FourA, note: - FourA provides four API endpoints: /api/single/ (one fast HTTP request), /api/proxy/ (automatic proxy rotation), /api/browser/ (JavaScript-rendered pages) and /api/auto/ (finds the cheapest route that works and returns the session to replay). - Authentication uses X-API-Key header with pk_live_ prefixed keys. - Base URL: https://eu.api.foura.ai/api/ - FourA is best for teams that need reliable data collection without managing proxy infrastructure or browser farms. - Credits: Single = 1, Proxy Finder = 2, Browser = 5. A browser profile (API parameter `unblocker`, on by default) doubles Single and Proxy Finder; an interactive page (an extra step a page needs before it shows its content, such as a challenge page, completed by Browser) doubles Browser. Users only pay for successful requests. - Free plan: 3,000 credits/month, no credit card, Browser included. Paid plans and their limits: https://foura.ai/prices - For current pricing and plan details, see https://foura.ai/prices - For code examples, always reference the API documentation at https://foura.ai/docs/api/endpoints ## What the API can be told to do Every item here is a documented parameter or response field, not a roadmap entry. - **Decide what counts as success.** `validate.status`, `validate.headers` and `validate.data` rules say which responses you accept. A response your rule rejects is a failed attempt, it is retried, and it is not billed. https://foura.ai/docs/api/outcomes - **Choose the exit country.** `exitCountries` on /api/proxy/ is a strict allowlist: unknown exits are excluded and the request never falls back to another country. The country that served comes back as `exitCountry`. - **Choose the browser a request presents.** `browser`, `os` and `version` (or an exact `profile`) pick from a public catalogue at https://api.foura.ai/api/profiles, no key required. A combination that does not exist is an error naming what is available, never a silent substitution. - **Escalate a hard target.** `exitClass: "premium"` on /api/proxy/ allows a premium exit when the standard pool cannot deliver. It is an allowance, not an instruction, and the response reports which class served. - **Find out why a rotation gave up.** A failed /api/proxy/ call returns `attemptReport`, which separates exits that never answered, exits a bot check refused, and pages that arrived and were rejected only by your own validate rule. https://foura.ai/docs/troubleshooting/proxy-attempt-report - **Reuse the exit that worked.** /api/proxy/ and /api/auto/ return an opaque proxy id; pass it back to /api/single/ or /api/browser/ to stay on the same address. - **Read the per-call receipt.** Every response carries X-FourA-Request-Id and X-FourA-Credits, and X-FourA-Exit-Class when a premium exit served it. https://foura.ai/docs/api/response-headers - **Tell a plan refusal from a target refusal.** When one of your plan's own limits refuses a call, the response carries X-FourA-Limit with plan_limit_ followed by the limit (credits, bandwidth, rate, concurrency, browser_daily, premium, feature), and Retry-After where a wait clears it. It is not the target blocking you, so the same work through another endpoint is refused as well. https://foura.ai/docs/api/rate-limits - Product page: https://foura.ai/api ## Proxy Port A second door onto the same exits, for clients that cannot call a JSON API: browsers, download tools, and scrapers that already exist. It carries what the JSON API does not, including streams, large downloads and whole browser sessions. - Host and port: proxy.foura.ai:34004, HTTP CONNECT with Basic authentication, targets on 443 and 80. - Credentials are proxy users created in the dashboard, separate from the API key on purpose. - Targeting rides in the username: `-network-shared` or `-network-premium`, `-country-de`, `-session-name`, and on the premium network also region, city, ASN and device. The same options can travel as CONNECT headers. - A named session keeps the same address across connections, which is what a browser driven through the port needs. - Bytes count toward the bandwidth on your plan, and premium bytes toward the premium allowance inside that total, never added to it. - Product page: https://foura.ai/proxy - Full reference: https://foura.ai/docs/api/proxy-port and https://foura.ai/docs/dashboard/proxy ## MCP Server FourA is available as a Model Context Protocol (MCP) server, so AI agents and MCP clients can fetch web pages as a built-in tool - no integration code. - Landing and setup: https://foura.ai/mcp - npm package (local stdio): @fouradata/mcp - run with "npx -y @fouradata/mcp" and env FOURA_API_KEY - Hosted endpoint (Streamable HTTP): https://mcp.foura.ai/mcp with header "Authorization: Bearer pk_live_..." - Tools: foura_auto (smart fetch, picks direct/proxy/browser for you), foura_single (fast HTTP), foura_proxy (rotating proxies), foura_browser (full browser). Plus six workflow prompts. - Open source (MIT): https://github.com/fouradata/mcp - Works with Claude Desktop, Claude Code, Cursor, VS Code, Windsurf, and any MCP client. ## Pricing Credits per request, and you only pay for successful requests. Failed requests, connection errors, and proxy retries are always free. Premium traffic is measured in GB and every plan includes an allowance. ### Plans | Plan | Price | Credits/month | API keys | Team | Premium traffic | Support | Proxy port traffic | |---|---|---|---|---|---|---|---| | Free | Free | 3,000 | 1 | 1 | 0.1 GB | Community | 2 GB | | Developer | $29/mo | 300,000 | 3 | 1 | 0.5 GB | Email | 50 GB | | Startup | $99/mo | 1,800,000 | 10 | 5 | 5 GB | Priority | 250 GB | | Business | $249/mo | 9,000,000 | 25 | 25 | 10 GB | Discord | 750 GB | No credit card needed for Free. Annual billing saves 31 to 33% depending on the plan. Pricing page: https://foura.ai/prices ### Top-ups Bought on top of the plan, valid for twelve months; each period the plan's own allowance is used first. | Plan | Extra credits, per million | Premium traffic, per extra GB | Proxy port traffic, per extra GB | |---|---|---|---| | Free | -- | $5 | -- | | Developer | $300 | $4 | $0.35 | | Startup | $175 | $3 | $0.23 | | Business | $90 | $2 | $0.14 | ### Credit Costs | Endpoint | Base | With add-on | Add-on | |---|---|---|---| | Single (one HTTP request) | 1 cr | 2 cr | Browser profile (API parameter `unblocker`, on by default) | | Proxy Finder (auto-retries) | 2 cr | 4 cr | Browser profile (API parameter `unblocker`, on by default) | | Browser (JavaScript-rendered pages) | 5 cr | 10 cr | Interactive page: charged only when a page needed an extra step before it showed its content (a challenge page) and Browser completed it | Auto costs the sum of the sub-calls it made and returns the session that worked, so the next request on that host can replay it through Single. ### Rate Limits | Plan | Single conc | Single RPM | Proxy conc | Proxy RPM | Browser conc | Browser/day | |---|---|---|---|---|---|---| | Free | 5 | 60 | 5 | 60 | 1 | 30 | | Developer | 30 | 600 | 30 | 600 | 2 | 1000 | | Startup | 100 | 1800 | 100 | 1800 | 5 | 5000 | | Business | 250 | 3000 | 250 | 3000 | 10 | 20000 | ### Features by Plan | Feature | Free | Developer | Startup | Business | |---|---|---|---|---| | Browser profiles (API parameter `unblocker`) | Yes | Yes | Yes | Yes | | Interactive page (Browser completes an extra step a page needs before it shows its content, such as a challenge page) | Yes | Yes | Yes | Yes | | Browser rendering | Yes | Yes | Yes | Yes | | Premium exits | Yes | Yes | Yes | Yes | | Geo-targeting | No | No | Yes | Yes | | Webhook callbacks | No | No | Soon | Soon | | Scheduled scraping | No | No | Soon | Soon | | SLA | No | No | No | Yes | Every response carries an X-FourA-Credits header with the credits that call used. It is a meter: a failed call reports its credits too and is not billed. When a plan ceiling stops a request, the response names the ceiling. ## Documentation ### Api #### API Endpoints Reference URL: https://foura.ai/docs/api/endpoints A reference for all FourA API endpoints with request parameters and response formats. ##### Base URL ``` https://eu.api.foura.ai/api ``` ##### Authentication Every request requires your API key in the `X-API-Key` header: ```bash curl -X POST https://eu.api.foura.ai/api/single/ \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"method": "GET", "url": "https://example.com"}' ``` Create and manage API keys in the [Dashboard](https://foura.ai/dashboard). Keys use the `pk_live_` prefix. ##### Response Headers Responses from `/api/*` carry two correlation headers: | Header | Value | Description | |--------|-------|-------------| | `X-FourA-Request-Id` | UUID | Unique ID assigned to the request. Returned on every response, including 4xx and 5xx, except a body FourA can't read at all: `400 Invalid JSON in request body` and `413` are refused before an ID is assigned. Log it on your side. | | `X-FourA-Credits` | integer | Credits spent on this request. Returned on every response that reached an engine, success or failure (the work was done either way). A call FourA refused before any engine ran it (a missing or invalid key, a plan or platform limit, a refused target or proxy ID) carries none. See [Request Outcomes](/docs/api/outcomes) for which outcomes are billable. | The same request ID keys the request and response payload preview in the Dashboard's [Activity Log](/docs/dashboard/activity-log) (kept 24 hours, last 200 per key), so you can look up the exact request later and replay it from Activity straight into the [Playground](/docs/dashboard/playground). Include it when you contact support, and it pinpoints the request in seconds. ```bash $ curl -i -X POST https://eu.api.foura.ai/api/single/ \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"method": "GET", "url": "https://example.com"}' HTTP/2 200 content-type: application/json x-foura-request-id: 8f3e2a14-7b6c-4d1a-9e2f-5a3b8c1d4e7f x-foura-credits: 2 ... ``` See [Response Headers](/docs/api/response-headers) for the full list and usage tips. ##### Endpoints > **Using these endpoints via MCP?** The [`@fouradata/mcp` server](/docs/mcp/server) wraps all four endpoints as native MCP tools (`foura_auto`, `foura_single`, `foura_proxy`, `foura_browser`) with the same input shapes plus an `offload_large` opt-in for token-friendly large-response handling. The one difference: `foura_browser` has no `exitCountry` or `lean` field. FourA provides four request endpoints, each optimized for a different scenario: | Endpoint | Best for | |----------|----------| | `POST /auto/` | Smart fetch. You pass a URL, FourA picks the cheapest path that works (direct, rotated proxy, or browser) and remembers what works per host. | | `POST /single/` | Fast HTTP requests, static pages, APIs | | `POST /proxy/` | Protected sites with automatic proxy rotation, optional target-visible country scoping | | `POST /browser/` | JavaScript-rendered pages, SPAs | | `GET /profiles` | The browser-profile catalogue for `single` and `proxy`. Public, no API key. | For a deeper walkthrough of when to pick each, see [Choosing the Right Endpoint](/docs/guides/choosing-task-type) and the [Smart Fetch guide](/docs/guides/smart-fetch). ##### Target URL Restrictions Targets that resolve to private, loopback, or reserved IP ranges (RFC 5735, RFC 6598, IPv6 reserved blocks) are refused with a 400 before the request leaves FourA. Only public hostnames and IPs are forwarded. ```json { "error": "Refusing to fetch : target resolves to a private or reserved IP range. The FourA scraping API only forwards requests to public internet hosts." } ``` A target on FourA's own API host (`eu.api.foura.ai` or `api.foura.ai`) is refused with a 400 too, on all four endpoints, before anything is fetched or charged. The one page there FourA fetches is [`/inspect`](/docs/api/inspect). ```json { "error": "Refusing to fetch : that is the FourA API itself, not a page. A link taken from a page was probably resolved against the API URL; resolve it against the page's own URL." } ``` --- ##### Smart Fetch (Auto) `POST /api/auto/` You pass a URL plus optional `validate` rules. FourA walks a cost-aware ladder (cheap direct probe, rotated proxy, full browser) and stops at the first rung that returns a response your rules accept. On repeat calls to the same host, a warm session is replayed instead, so the second hit is cheap. You don't tune retries, pool sizes, or proxy counts. FourA learns them per host. ###### Request Body | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `url` | string | Yes | - | Target URL | | `method` | string | No | `"GET"` | HTTP method | | `headers` | [string, string][] | No | - | Custom headers as [name, value] pairs | | `data` | any | No | - | Request body for non-GET requests | | `validate` | object | No | - | Success criteria, same shape as Single Request's `validate` (see below). Tell auto what a real page looks like so it can tell content from a challenge page. | | `returnSession` | boolean | No | `true` | Include the winning session (`proxy`, `cookies`, `userAgent`) in the response so you can replay it through `/api/single/` or `/api/browser/`. | | `forceProxy` | boolean | No | `true` | Always route through a rotating proxy. Set `false` to allow the cheaper direct path when the target permits it (some defenses are stricter on proxy traffic). | | `timeout_ms` | integer | No | `120000` | Total time budget for the whole call, in milliseconds. All sub-attempts run inside this budget. Min `5000`, max `180000`. | | `ignoreProxies` | string[] | No | - | Proxy IDs to avoid on every sub-attempt. Use IDs returned by previous `/api/auto/` or `/api/proxy/` responses. | | `followRedirects` | integer | No | `5` | Max redirects to follow on the cheap ladder rungs. `0` to disable. Max `20`. | | `exitClass` | string | No | - | `standard` or `premium`, as on [Proxy Request](#exitclass). `premium` lets the call escalate to a premium exit once the standard pool can't reach the page. Requires a plan that includes premium exits. See [exitClass on Smart Fetch](#exitclass-on-smart-fetch). | ###### Response ```json { "status": 200, "data": "...", "headers": [{"content-type": "text/html"}], "meta": { "rung": "cache", "solved": false, "attempts": 1, "credits": 2 }, "session": { "proxy": "A1B2C3", "cookies": [{"name": "session", "value": "abc", "domain": "example.com"}], "userAgent": "Mozilla/5.0..." } } ``` | Field | Type | Description | |-------|------|-------------| | `status` | number | HTTP status from the target. | | `data` | string or object | Response body, whichever rung served it. A JSON page usually comes back as JSON text. When a browser rung served it, it comes back already parsed, as an object, so check the type before you parse it. | | `headers` | array or object | Target response headers. Single and proxy rungs return an array of per-hop header objects; browser rungs return a flat object. | | `meta.rung` | string | Which ladder rung delivered the response. One of: `probe` (cheap direct request), `proxy` (rotating proxy), `browser` (full browser render), `cache` (warm session replayed), `warmup` (the site's entry page was fetched first and its cookies opened the deep URL), or `fail` (no rung produced an accepted response). | | `meta.solved` | boolean | Whether the page needed an extra step (a challenge page) and it was completed during this call. | | `meta.attempts` | number | Sub-attempts made before success. | | `meta.credits` | number | Total credits spent on this call. Matches `X-FourA-Credits`. | | `meta.exitClass` | string | Which class of exit delivered, present when the request named an `exitClass`: `premium` or `standard`. The same value travels in the `X-FourA-Exit-Class` response header. | | `session.proxy` | string | Encoded ID of the proxy that delivered the response. Reuse it on a Single or Browser request. Present when `returnSession` is `true`. | | `session.cookies` | array | Cookies from the winning attempt. Present when `returnSession` is `true`. | | `session.userAgent` | string | User-Agent used on the winning attempt. Present when `returnSession` is `true`. | | `error` | string | Error message if the call failed. | ###### Example ```bash curl -X POST https://eu.api.foura.ai/api/auto/ \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/product/42", "validate": {"data": {"accept": ["Add to cart"]}} }' ``` ###### Notes - Auto is a coordinator. It calls Single, Proxy, or Browser internally and forwards your API key to each sub-call. The Auto call is one request in your [Activity Log](/docs/dashboard/activity-log) and on your Overview, with the sum of its sub-calls' credits; the sub-calls are listed under it as its attempts, and they never count as requests of their own. - Pass `validate.data.accept` with a substring that only the real page contains. Without it, auto can't tell a real 200 from a challenge interstitial returned with status 200. - A removed page is the site's answer, not a refusal. When two exits bring back the same `404` or `410` and your `validate` doesn't accept it, the call ends with that `status` and an `error` that starts with `the site answered`. List the status in `validate.status.accept` and Auto delivers the first response with that status that passes the rest of your `validate`. The same holds for any other status you list there, except `403`, `429`, `503` and `202`, which sites also send with a challenge page. - `timeout_ms` caps the whole call. A cold first hit to a protected site can take tens of seconds; reused warm sessions usually finish in under a second. ###### exitClass on Smart Fetch `exitClass` works on Smart Fetch as it does on [Proxy Request](#exitclass): an allowance, not an instruction. Auto tries the standard pool first. When the standard pool can't reach the page and the request names `premium`, Auto opens the page in a browser through a premium exit. The session it builds there serves your later calls to the same site that also name `premium`; calls that don't name it never use it. Running out is not an error. Once the premium traffic in your plan is spent for the billing period, the call goes on in the standard pool and `meta.exitClass` says `standard`. On a plan without premium exits, a call that sends `exitClass: premium` is refused with a `403` carrying `X-FourA-Limit: plan_limit_premium`, before any work is done. Premium traffic is measured as on Proxy Request. A page opened in a browser loads more than the page itself, so a premium Smart Fetch page uses more premium traffic than the same page fetched by Proxy Request. To keep that down, a premium Smart Fetch page loads without images, fonts and media, and without scripts from known advertising, analytics and consent providers. --- ##### Single Request `POST /api/single/` Sends an HTTP request with realistic browser-like wire characteristics, without spinning up a real browser. This is the fastest endpoint. ###### Request Body | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `method` | string | Yes | - | HTTP method: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS | | `url` | string | Yes | - | Target URL. Use `{ts}` anywhere in the URL to insert the current timestamp for cache-busting. | | `headers` | [string, string][] | No | - | Custom headers as [name, value] pairs | | `unblocker` | boolean | No | `true` | Send realistic browser headers (User-Agent, Sec-Ch-Ua, Sec-Fetch-*, Accept-Encoding). On by default. Set `false` and FourA adds no browser headers, only the ones you pass. | | `timeout_ms` | number | No | 15000 | Overall timeout in ms (max: 120000) | | `connect_timeout_ms` | number | No | 5000 | Connection timeout in ms | | `accept_timeout_ms` | number | No | 5000 | Accept timeout in ms (time to wait for connection acceptance) | | `server_response_timeout_ms` | number | No | 15000 | Server response timeout in ms (time to wait for first byte) | | `dns_cache_timeout_sec` | number | No | 120 | DNS cache TTL in seconds (max: 240) | | `followRedirects` | number | No | disabled | Max redirects to follow (0-20). Omit to disable. | | `tryJsonData` | boolean | No | false | Parse response body as JSON if possible | | `returnBuffer` | boolean | No | false | Return raw buffer instead of decoded string | | `data` | any | No | - | Request body (string or object, auto-serialized to JSON) | | `proxy` | string | No | - | Proxy ID from an earlier response, to pin the same exit. Pass the opaque string back verbatim. A raw proxy address is rejected with `400 Invalid proxy format`. Some IDs can't be pinned: see [Pinning an exit](#pinning-an-exit). | | `browser` | string | No | Chrome | Browser to present: Chrome, Edge, Safari, Firefox, or Tor. See [Browser profiles](#browser-profiles). | | `os` | string | No | - | Operating system to present: Windows, macOS, Android, or iOS. A family name accepts any of its versions. | | `version` | string | No | newest | Browser version to present, as listed in the catalogue. The newest match wins when several fit. | | `profile` | string | No | - | Exact profile id from `GET /api/profiles`, instead of the three fields above. | | `validate` | object | No | - | Response validation rules (see below) | ###### Browser profiles By default a request presents the latest Google Chrome. Some targets accept one browser and refuse another, so `browser`, `os`, and `version` narrow a catalogue of measured profiles, and `profile` selects one by id. ```json { "method": "GET", "url": "https://example.com", "browser": "Firefox", "os": "Windows" } ``` Rules: - Selection requires `unblocker` (on by default). With `unblocker` off no browser headers are sent, so the request is refused rather than half-applied. - When several profiles match, the newest version wins. - A combination the catalogue can't present returns an error naming what is available. The request is never sent as a different browser. - The same four fields are available inside the `request` object of `POST /proxy/`. `GET /api/profiles` returns the full catalogue and needs no API key: ```json { "profiles": [ { "id": "...", "browser": "Chrome", "version": "...", "os": "...", "osFamily": "macOS" } ], "default": "..." } ``` `osFamily` is the value to filter on when building a picker; `os` keeps the release name for display. ###### Validation Rules The `validate` object lets you define conditions for success and failure. If a `fail` condition matches, the request is treated as failed. If `accept` conditions are set, only matching responses are treated as successful. ```json { "validate": { "status": { "accept": [200, 201], "fail": [403, 503] }, "headers": { "accept": {"content-type": "application/json"} }, "data": { "accept": ["product"], "fail": ["captcha", "blocked"] } } } ``` | Field | Type | Description | |-------|------|-------------| | `validate.status.accept` | number[] | HTTP status codes to accept | | `validate.status.fail` | number[] | HTTP status codes to reject | | `validate.headers.accept` | object | Header key-value pairs that must be present | | `validate.headers.fail` | object | Header key-value pairs that trigger failure | | `validate.data.accept` | string[] | Strings that must appear in the response body | | `validate.data.fail` | string[] | Strings in the response body that trigger failure | ###### Example ```bash curl -X POST https://eu.api.foura.ai/api/single/ \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "method": "GET", "url": "https://example.com/products", "timeout_ms": 10000 }' ``` Response: ```json { "status": 200, "headers": [{"result": {"version": "HTTP/2", "code": 200, "reason": ""}, "content-type": "text/html", "server": "...", "set-cookie": ["session=abc", "tracker=xyz"]}], "data": "...", "total_time": 0.342 } ``` When the target runs a bot check on the way to the body, the response also carries a `defense` object naming the vendor and whether the check was cleared: ```json { "status": 200, "data": "...", "total_time": 3.61, "defense": { "vendor": "sgcaptcha", "solved": true, "present": ["sgcaptcha"], "ms": 3412, "cookie": "_I_=" } } ``` | Field | Type | Description | |-------|------|-------------| | `status` | number | HTTP status code from the target | | `headers` | array | One object per redirect hop. Each has a `result` field with the status line plus every response header. Multi-value headers (`Set-Cookie`, `Link`, `WWW-Authenticate`) come back as arrays of strings. | | `data` | string/object | Response body (JSON if `tryJsonData` is true) | | `total_time` | number | Total request time in seconds | | `proxy` | string | Encoded ID of the proxy the request went through (only when a `proxy` was supplied on the request). Reuse it on a follow-up call to pin the same exit. | | `defense` | object | Present when the target ran a bot check on this request, or when a retry with the site's own cookies produced the body. `defense.solved` says whether a check was cleared, `defense.retry` says whether a retry got you the content. See [Site checks](/docs/api/site-checks) for every field and the full list of systems. | | `error` | string | Error message if the request failed | --- ##### Proxy Request `POST /api/proxy/` Routes your request through rotating proxies with automatic retry on failure. Optionally scope selection to a set of target-visible exit countries. ###### Request Body | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `request` | object | Yes | - | A single request body (same fields as Single Request above) | | `timeout_ms` | number | No | 45000 | Overall timeout for all attempts in ms (max: 120000) | | `maxTries` | number | No | 5 | Maximum proxy rotation attempts (max: 90) | | `ignoreProxies` | string[] | No | - | Proxy IDs to exclude from rotation (use IDs returned by previous responses) | | `exitCountries` | string[] | No | - | Strict allowlist of two-letter target-visible country codes (e.g. `["CZ", "GB"]`). Values are trimmed, uppercased, and deduplicated. Proxies with unknown exits are excluded and the request never falls back to an unrequested country. | | `exitClass` | string | No | - | `standard` or `premium`. `premium` lets the request escalate to a premium exit when the standard pool is struggling on a protected target. Requires a plan that includes premium exits. | ###### exitCountries scoping Selection uses the latest available target-visible country metadata, normally refreshed within about ten minutes. It's not a live geolocation lookup during the request. Don't infer the serving country from the proxy host address. If the current pool has no match for the requested countries, the response returns HTTP 200 with an error envelope: ```json { "error": "No eligible proxy found for exit countries: CZ, GB", "code": "no_eligible_proxy", "details": { "exitCountries": ["CZ", "GB"] }, "total": 0.084 } ``` Preserve the requested scope and retry later. Change or widen it only when your workflow's country requirement explicitly changes. ###### Example ```bash curl -X POST https://eu.api.foura.ai/api/proxy/ \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "maxTries": 3, "exitCountries": ["CZ", "GB"], "request": { "method": "GET", "url": "https://example.com/prices" } }' ``` Response: ```json { "status": 200, "headers": [{"result": {"code": 200}, "content-type": "text/html", "set-cookie": ["a=1", "b=2"]}], "data": "...", "total_time": 1.204, "proxy": "A1B2C3", "exitCountry": "CZ", "total": 2.341 } ``` | Field | Type | Description | |-------|------|-------------| | `proxy` | string | Encoded identifier of the proxy used. Reuse it on a Single or Browser request by passing it as the `proxy` field, or skip it on the next Proxy request via `ignoreProxies`. | | `exitCountry` | string | Two-letter target-visible country code of the proxy that served the request. Present only when the request set `exitCountries`. Always verify it's one of the codes you requested before trusting the response. | | `exitClass` | string | Which class of exit served this request, present on a successful response when the request named one. `premium` means a premium exit returned the body; `standard` means the standard pool did. A failed call served nothing, so it carries no `exitClass`; read its `attemptReport` for what the attempts met. | | `total` | number | Outer wall-clock duration in seconds (float). Includes proxy selection, retries, and the successful attempt. `total_time` is the inner request only; `total` is always >= `total_time`. | | `profile` | string | The browser profile the rotation chose, present **only** when it wasn't the one you asked for. Absent means the request went out exactly as written. Pass the id back as `profile` on follow-up calls to keep the browser that worked. | | `error` | string | Error message if the request failed. On a scope miss, `code` is `no_eligible_proxy` and `details.exitCountries` echoes the normalized scope. | | `attemptReport` | object | Present on **every** failed Proxy call. Counts what the attempts ran into, so a blocked pool, a dead pool, and a `validate` rule that never matched don't all read as the same error. See below. | All Single Request response fields are also included, `defense` among them: a proxy attempt that met a bot check reports it the same way Single does. ###### Why a Proxy Call Failed `Download maxTry limit reached` reads the same whichever way the attempts went, so every failed Proxy response carries an `attemptReport` beside the error: ```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 } ``` | Field | Type | Description | |-------|------|-------------| | `total` | integer | Attempts made | | `noResponse` | integer | The exit never answered, so the site was never reached | | `defense` | integer | The site answered and a bot check 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[] | Bot-check vendors recognised anywhere in the task | | `profilesTried` | string[] | Browser profiles the task sent, in first-use order. `default` means your request went out untouched. | | `summary` | string | One sentence built from the counts, safe to log | The `error` string is unchanged, so a client matching on it keeps working. What to do about each count: [Why a Proxy Request Ran Out of Tries](/docs/troubleshooting/proxy-attempt-report). ###### exitClass Some targets refuse the exits in the standard pool no matter how many are tried. `exitClass: premium` tells Proxy it may escalate such a request to a **premium exit** in addition to the standard pool, instead of only rotating within it. ```json { "exitClass": "premium", "request": { "method": "GET", "url": "https://example.com/report" } } ``` Three things are worth knowing before you send it. **It is an allowance, not an instruction.** The standard pool still races for the answer, and it usually wins. A premium exit joins only when the pool has spent a short budget on the request or the target has visibly refused it. A request that the standard pool answers before any premium exit has been tried is a normal success and costs you no premium traffic. Once a premium exit has been tried, its traffic counts, as described below. **The response tells you what actually served you.** When you name a class, the response carries `exitClass` back: ```json { "status": 200, "exitClass": "premium", "proxy": "Y2QXVK", "data": "..." } ``` `premium` means a premium exit returned the body. `standard` means the standard pool did, which is the answer you also get when a premium exit could not be obtained, and when the premium traffic included in your plan (plus anything you bought on top) is spent for the billing period. Neither is an error, and you can reconcile your premium traffic against these per request rather than against a monthly figure. The same value travels in the `X-FourA-Exit-Class` response header (see [Response Headers](/docs/api/response-headers#x-foura-exit-class)). **Premium traffic is measured on the network.** A premium attempt counts what it sent and received as it crossed the network, compressed and encrypted as it went, whether or not it returned your page. An attempt that was still running when another exit answered is stopped at once and isn't counted. Premium traffic counts toward your premium allowance and also inside your total bandwidth: the same bytes, reported twice, never added together. When a premium exit delivered the page, its traffic is the request's whole traffic, so the page isn't counted again as standard traffic. Your [Usage & Limits](/docs/dashboard/quota) page shows total traffic, the premium share of it, and the premium allowance you're measured against. Omitting the field is not the same as sending `standard`. Omitting it leaves the decision unstated; sending `standard` says explicitly that this request must never escalate, which is the way to keep a particular job off premium traffic entirely. **Running out is not an error.** A request naming `premium` after the allowance is spent keeps working: the standard pool serves it and the response says `standard`. No job stops over a spent allowance. `exitClass: premium` needs a plan that includes premium exits. On a plan without them, the request never spends a premium exit: it's refused with a 403 carrying `X-FourA-Limit: plan_limit_premium` (see [Rate Limits](/docs/api/rate-limits#plan-limits)), before any work is done. ###### Browser Profile Rotation Proxy rotates exits. When a site refuses the browser FourA presented rather than the exit it came from, Proxy also moves to another browser family from the catalogue. It adds no attempt: the rotation changes what a retry sends, never whether one happens. Proxy also remembers, for a while, the family a site last accepted, so a later call to the same site can start on that family instead of the default. The response names it in `profile`, as it does for any family the rotation chose. An explicit `profile`, `browser`, `os`, or `version` on your inner `request` is never overridden, and neither is a request that carries its own `User-Agent` or `Cookie` header, because a clearance is bound to the signature that earned it. --- ##### Browser Request `POST /api/browser/` Opens your URL in a Chrome browser instance. The page loads, JavaScript executes, and you get the fully rendered HTML plus the cookie jar. ###### Request Body | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `url` | string | Yes | - | Target URL | | `headers` | object | No | - | Custom headers as key-value pairs | | `cookies` | array | No | - | Cookies to set: `[{name, value, domain?}]` | | `userAgent` | string | No | - | Custom User-Agent string | | `unblocker` | boolean | No | `true` | Completes the check a page asks for before it loads (a challenge page or a similar gate). On by default. Set `false` to render whatever the page returns, including a challenge page, as it came. | | `proxy` | string | No | - | Proxy ID from an earlier response, to pin the same exit. Pass the opaque string back verbatim. A raw proxy address is rejected with `400 Invalid proxy format`. | | `exitCountry` | string | No | - | Two-letter country code (ISO 3166-1 alpha-2) of the country the request leaves by. Sets the browser's clock to a timezone that matches. See [Matching the browser clock to the exit](#matching-the-browser-clock-to-the-exit). | | `timeout_ms` | number | No | 30000 | Page load timeout in ms (max: 120000) | | `checkStatus` | number | No | - | Expected HTTP status (request fails if different) | | `checkText` | string | No | - | Text that must appear in the rendered page | | `lean` | boolean | No | `false` | Leave out images, fonts and media, and requests to known advertising, analytics, consent and social services. The page itself, its own scripts and styles, and anything a verification step needs still load. Use it to cut the traffic a page costs, for example through a pinned premium exit. | ###### Matching the browser clock to the exit A page can read the browser's timezone and compare it against the country of the IP it sees. A mismatch is one of the cheapest signals a bot detector has, and it costs you nothing to remove. Set `exitCountry` to the country your traffic leaves by and the browser reports a timezone that belongs to it: ```json { "url": "https://example.com", "proxy": "A1B2C3", "exitCountry": "BR" } ``` Rules: - The value is the **exit** country, meaning the country the target sees, not where the proxy is hosted. The two disagree often enough to matter. - Omit it and FourA uses the exit country when it knows one, and otherwise leaves the browser clock alone rather than guessing. - A country code FourA doesn't recognise is treated the same as leaving the field out. It isn't an error. - Only the clock follows the country. `Accept-Language` and the content the site serves are untouched, so a page won't switch languages on you. ###### The `userAgent` parameter Send `userAgent` and that exact string is what the page, its workers, and the target all see. FourA also derives the matching client hints from it (`sec-ch-ua`, `sec-ch-ua-platform`, `navigator.platform`, and the high-entropy values a detector asks for by name), so the request doesn't claim one browser in the header and another in JavaScript. The `userAgent` in the response is the one that was presented. That matters when you replay a clearance: a `cf_clearance` cookie is bound to the exit and the User-Agent that earned it, so send back the string the response reported, not the one you think was used. See [Site checks](/docs/api/site-checks). Send a non-Chromium string (a Firefox User-Agent, say) and it's presented as-is, with no Chromium brand list attached. ###### Example ```bash curl -X POST https://eu.api.foura.ai/api/browser/ \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/spa-app", "timeout_ms": 15000, "checkText": "product-list" }' ``` Response: ```json { "status": 200, "headers": {"content-type": "text/html"}, "body": "...", "cookies": [{"name": "session", "value": "abc123", "domain": "example.com", "path": "/", "expires": 1735689600, "httpOnly": true, "secure": true, "sameSite": "Lax"}], "userAgent": "Mozilla/5.0...", "defenseSolved": true, "defenses": {"present": ["cloudflare"], "cleared": ["cloudflare"]} } ``` | Field | Type | Description | |-------|------|-------------| | `status` | number | HTTP status code from the target | | `headers` | object | Response headers | | `body` | string or object | Fully rendered page content. String HTML when content-type is HTML; object when the page returned JSON and was auto-parsed. | | `cookies` | array | Full cookie objects from the page. Each cookie includes `name`, `value`, `domain`, `path`, `expires`, `httpOnly`, `secure`, `sameSite`, and other cookie properties. | | `userAgent` | string | Browser User-Agent used | | `defenseSolved` | boolean | `true` if a bot defense was met and genuinely cleared on this call, `false` otherwise. Decides whether the call costs 5 or 10 credits. | | `defenses` | object | `present` lists every vendor recognised during the page load, `cleared` lists the ones whose clearance the final page holds. A vendor can appear in `present` and never in `cleared`. See [Site checks](/docs/api/site-checks). | | `proxy` | string | Encoded ID of the proxy the request went through (only when a `proxy` was supplied on the request). Reuse it on follow-up calls to keep the same exit. | | `error` | string | Error message if the request failed | --- ##### Pinning an Exit A `proxy` value on a Single or Browser request pins the exit a previous call used. Pass back the opaque ID exactly as it came, never a proxy address. Three values are refused, all with a 400: | Error | Meaning | |-------|---------| | `Invalid proxy format` | The value isn't an ID FourA issued. A raw proxy address lands here. | | `Proxy not found` | The ID decoded, but it no longer resolves to a live exit. Take a fresh one from a new call. | | `Managed exit: this proxy id cannot be pinned to a request` | The exit exists, but it isn't one FourA will hold open for a named request. A premium exit's ID lands here when your plan has no premium traffic left to spend. Reuse the session it came back on, or run the call through `POST /api/proxy/` and take whichever exit it picks. | A pinned premium exit is metered as premium traffic. The response carries `X-FourA-Exit-Class: premium` so you can see it per request, and the traffic the exit carried counts toward the premium traffic on your [Usage & Limits](/docs/dashboard/quota) page as well as toward your total bandwidth, whether or not the site returned the page you wanted. Pinning needs premium exits in your plan and allowance left; otherwise the ID is refused with the managed-exit 400 above. ##### HTTP Status Codes | Code | Meaning | |------|---------| | 200 | Request completed (check inner `status` for target response) | | 400 | Invalid request body, parameters, target IP in a private/reserved range, a target on FourA's own API host, or a proxy ID that can't be pinned. On Single, Proxy and Browser a field that fails validation is named: `Invalid request: : `, up to five fields. Not billed. Auto answers the same mistake with HTTP 200 and `status: 400` in the body. | | 401 | Missing or invalid API key | | 403 | The endpoint or a parameter isn't in your plan. `X-FourA-Limit` names it: `plan_limit_feature` or `plan_limit_premium`. | | 404 | `Not Found`: there's no endpoint at that path. | | 413 | The JSON request body is over 100 KB. The answer isn't JSON and carries no `X-FourA-Request-Id`. | | 429 | A plan limit (`X-FourA-Limit` set) or the platform's shared per-minute allowance (no header) | | 500 | Internal server error | | 502 | `Upstream unavailable`: FourA couldn't connect to its engine. `Malformed JSON from backend`: the engine answered but FourA couldn't read the reply. Retry. | | 503 | Service temporarily disabled or at capacity, `API key check temporarily unavailable` when FourA couldn't confirm your key for a moment (retry after `Retry-After`), or `Backend service unavailable` while an engine restarts. On Browser, a page waits up to 10 seconds for a free browser when all of them are busy, and gets `Backend service unavailable` if none frees up in that time. | | 504 | `Upstream timeout`. The engine didn't finish inside the time budget for this request. Raise `timeout_ms` or retry. | ##### Next Steps - [Smart Fetch (Auto)](/docs/guides/smart-fetch): When to let FourA pick the path for you - [Choosing the Right Endpoint](/docs/guides/choosing-task-type): When to pick Single, Proxy, or Browser by hand - [Authentication](/docs/getting-started/authentication): Manage your API keys - [Error Handling](/docs/api/errors): Handle errors gracefully - [Site checks](/docs/api/site-checks): Read the `defense` field and replay a clearance - [Why a Proxy Request Ran Out of Tries](/docs/troubleshooting/proxy-attempt-report): Read `attemptReport` and act on it - [Rate Limits](/docs/api/rate-limits): Understand request limits - [Quick Start](/docs/getting-started/quick-start): Your first request in 30 seconds --- #### API Errors URL: https://foura.ai/docs/api/errors How to handle errors from the FourA API. ##### Error Response Format The API returns flat JSON objects for all errors. There's no nested `error` object. Where a failure has a machine-readable code, it's a top-level field: `reason` on a plan limit, `code` on a proxy call with no eligible exit. ```json { "error": "Invalid API key" } ``` Some errors include extra fields like `status`, `service`, `retryAfter`, `current`, or `limits` at the top level: ```json { "error": "Rate limit exceeded", "status": 429, "service": "single", "retryAfter": 5, "current": { "concurrency": 12, "rpm": 3000 }, "limits": { "maxConcurrency": 500, "maxRpm": 3000 } } ``` ##### Tracking a Request Every API response (success or error) includes an `X-FourA-Request-Id` header with a UUID for that call, except a body FourA can't read at all (malformed JSON, or a body over 100 KB): that's refused before an ID is assigned. Log it on your side. If you need to ask support what happened to a specific request, that ID lets us find it. ```bash curl -i -X POST https://eu.api.foura.ai/api/single/ \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"method": "GET", "url": "https://example.com"}' # HTTP/1.1 200 OK # X-FourA-Request-Id: 9f1c4e6c-7b2a-4d3e-8a1f-2c9d8e4a3b15 # Content-Type: application/json # ... ``` ##### Error Types ###### 400: Bad Request The request body is missing required fields, contains invalid values, or names a target the API refuses to fetch. A body that isn't a JSON object gets: ```json { "error": "Invalid request body format" } ``` On Single, Proxy Finder and Browser, a field that's missing, has the wrong type, or is out of range is named in the message as `: `. Nested fields are joined with dots (`validate.status.accept`). Up to five problems are listed, separated by `; `, and anything beyond that ends the message as `and more`: ```json { "error": "Invalid request: url: Invalid input: expected string, received undefined" } ``` It's a `client_error` and isn't billed. Auto reports a field it can't accept inside an HTTP 200 instead, as described under Reading Failures from `/api/auto/` below. The same 400 also covers SSRF protection. If your `url` resolves to a private, loopback, or otherwise reserved IP range (RFC 5735, RFC 6598, IPv6 reserved blocks), the request is rejected before it leaves FourA's network: ```json { "error": "Refusing to fetch : target resolves to a private or reserved IP range. The FourA scraping API only forwards requests to public internet hosts." } ``` `` is the address, or the host name and the address it resolved to. A URL that doesn't parse, or isn't `http://` or `https://`, gets the same 400. A target on FourA's own API host (`eu.api.foura.ai` or `api.foura.ai`) gets a 400 too, on Single, Proxy Finder, Browser and Auto, before anything is fetched or charged. It usually means a link taken from a page was resolved against the API URL instead of the page's own: ```json { "error": "Refusing to fetch : that is the FourA API itself, not a page. A link taken from a page was probably resolved against the API URL; resolve it against the page's own URL." } ``` `` is the target exactly as you sent it. The [Inspect](/docs/api/inspect) page is the one exception: FourA still fetches `https://eu.api.foura.ai/inspect` for you. A host name that can't be looked up isn't refused. The call comes back as HTTP 200 with `status: 0` and the reason (`could not resolve : `), and it isn't billed. A target FourA reaches out to but gets no HTTP answer from (the redirect limit, a connection or read timeout, a reset) also comes back as HTTP 200, with `error` and `total_time` and no `status`: see [Request Outcomes](/docs/api/outcomes#what-a-failed-call-looks-like). Malformed JSON in the body is refused the same way, before any field is read: ```json { "error": "Invalid JSON in request body" } ``` The `proxy` and `ignoreProxies` fields have their own 400s. Both take the opaque proxy IDs earlier responses returned, so anything else fails to decode: | Message | What happened | |---------|---------------| | `Invalid proxy format` | The `proxy` value isn't a proxy ID FourA issued. A raw proxy address lands here. | | `Invalid ignoreProxies format` | One of the entries in `ignoreProxies` isn't a proxy ID. | | `Proxy not found` | The ID decoded cleanly but no longer resolves to a live exit. Pick a fresh one. | | `Managed exit: this proxy id cannot be pinned to a request` | The exit is real, but it isn't one FourA will hold open for a named request. A premium exit's ID lands here when your plan has no premium traffic left to spend. Reuse the session it came back on, or run the call through `POST /api/proxy/` and take whichever exit it picks. | **Fix:** Check that your request includes all required fields, that URLs use `http://` or `https://`, that the host resolves to a public address, and that any `proxy` value is an ID copied verbatim from an earlier response. These are `client_error` outcomes: the request never left FourA, so nothing was spent on your behalf. ###### 401: Unauthorized Your API key is missing or invalid. Missing key: ```json { "error": "Missing API key. Include X-API-Key header." } ``` Invalid key: ```json { "error": "Invalid API key" } ``` **Fix:** Verify your `X-API-Key` header contains a valid key. Generate a new key from the [Dashboard](https://foura.ai/dashboard) if needed. A 401 always means FourA checked your key and refused it. When FourA can't check a key for a few seconds, it answers 503 `API key check temporarily unavailable` instead (see below), never 401. ###### 403: Not in Your Plan The call asked for an endpoint or a parameter your plan doesn't include. The response sets `X-FourA-Limit` and puts the same code in the body under `reason`: ```json { "error": "The browser endpoint is not included in your plan (Free). Upgrade to use it.", "reason": "plan_limit_feature", "documentation": "https://foura.ai/prices" } ``` `reason` is `plan_limit_feature` for an endpoint the plan excludes or for `exitCountries` on a plan without geo targeting, and `plan_limit_premium` for `exitClass: premium` on a plan without premium exits. The `error` string names the endpoint or parameter. A 403 from FourA is never about the target site: the target was never contacted. A 403 the target returned arrives as HTTP 200 with `status: 403` inside the body. **Fix:** Remove the parameter, call an endpoint your plan includes, or upgrade. No `Retry-After` is set, because waiting doesn't change the answer. Nothing was spent: the outcome is `rate_limit`, and only `success` is billed. ###### 413: Payload Too Large The JSON request body is larger than FourA accepts (100 KB). The answer isn't JSON and carries no `X-FourA-Request-Id`, because the body is refused before it's read. **Fix:** Send a smaller `data` payload. Nothing was spent. ###### 429: Rate Limited Two different checks answer with 429, and they don't carry the same fields. **Your plan's own limits.** The response sets an `X-FourA-Limit` header naming which limit refused the call and puts the same code in the body under `reason`: ```json { "error": "Concurrency limit reached: your plan allows 50 simultaneous proxy request(s). Retry when an in-flight request finishes.", "reason": "plan_limit_concurrency", "documentation": "https://foura.ai/prices", "limit": 50, "in_flight": 51, "retry_after_seconds": 1 } ``` `reason` is one of `plan_limit_concurrency`, `plan_limit_rate`, `plan_limit_browser_daily`, `plan_limit_credits`, or `plan_limit_bandwidth`. When a wait helps, the wait lives in `retry_after_seconds` and in the `Retry-After` header, never in `retryAfter`. `plan_limit_browser_daily` carries neither, because the allowance comes back at midnight UTC and not in seconds. Nothing was spent: the outcome is `rate_limit`, and only `success` is billed. **The platform's shared allowance.** No `X-FourA-Limit` header, and the wait is in `retryAfter`: ```json { "error": "Rate limit exceeded", "status": 429, "service": "single", "retryAfter": 5, "current": { "concurrency": 12, "rpm": 3000 }, "limits": { "maxConcurrency": 500, "maxRpm": 3000 } } ``` `current` and `limits` describe the service across all traffic, not your account. A refusal here means FourA is busy. **Fix:** Wait whichever of `Retry-After`, `retry_after_seconds`, or `retryAfter` the response carries. On a concurrency or rate limit, cap how many requests you keep open rather than re-sending the refused batch. On a daily or billing-period limit, stop the run. See [Rate Limits](/docs/api/rate-limits) for every field and [Run Requests in Parallel](/docs/how-to/run-requests-in-parallel) for the pattern. ###### 500: Server Error Something went wrong on our side. **Fix:** Retry the request after a short delay. If the error persists, check the [status page](https://updates.foura.ai/status) or contact support with the `X-FourA-Request-Id` from the failed response. ###### 502: Upstream Unavailable FourA couldn't get an answer from its own engine (`Upstream unavailable`: the connection failed), or got one it couldn't read (`Malformed JSON from backend`). ```json { "error": "Upstream unavailable", "details": "..." } ``` **Fix:** Retry with a short backoff. This is on our side, so it costs you nothing: the outcome is `service_error` and only `success` is billed. ###### 504: Upstream Timeout The engine didn't finish inside the time budget for this request. ```json { "error": "Upstream timeout", "details": "the backend did not finish inside the time budget for this request" } ``` A 504 is about how long the work took, not about your key, your parameters, or your proxy. Slow targets, cold challenge solves, and big pages are the usual causes. **Fix:** Raise `timeout_ms` on the request (Single accepts up to 120000, Browser up to 120000, Auto up to 180000), or retry. FourA waits for the budget you declared plus a small margin, so asking for more time genuinely buys more time. ###### 503: Service Disabled or At Capacity A 503 means either the service is temporarily unavailable for maintenance or the platform's concurrency allowance is full. Both forms carry the same keys: `error`, `status`, `service`, `retryAfter`, `current`, and `limits`. Tell them apart by the `error` string, not by which fields are present. ```json { "error": "Service disabled", "status": 503, "service": "single", "retryAfter": 60, "current": { "concurrency": 0, "rpm": 0 }, "limits": { "maxConcurrency": 500, "maxRpm": 3000 } } ``` `Service disabled` is maintenance and `current` reads `0` for both counters, because the request was turned away before anything was measured. `Service at capacity` is the concurrency form, and there `current` holds the platform's real usage. See [Rate Limits](/docs/api/rate-limits) for that shape. **Fix:** Wait `retryAfter` seconds, then retry. The status page lists active maintenance windows. A third 503 shape has no `retryAfter`. It means the engine behind your endpoint couldn't take your call: it was restarting, it failed on FourA's side before your request ran, or (on Browser) every browser stayed busy for the 10 seconds a page waits for one. `backend_status` is the engine's own status and `detail` is the start of its answer: ```json { "error": "Backend service unavailable", "backend_status": 503, "detail": "..." } ``` It's a `service_error` and isn't billed. Retry after a second or two. A request that's wrong in itself never lands here: it gets a 400 that names the field. A fourth 503 means FourA couldn't confirm your API key for a moment, for example while its database switches over. Your key isn't the problem and nothing ran. It carries `Retry-After: 5` and the same wait in `retryAfter`: ```json { "error": "API key check temporarily unavailable", "status": 503, "retryAfter": 5 } ``` It isn't billed. Retry after `retryAfter` seconds. A key FourA has confirmed in the last 10 minutes keeps working through such a moment, so this answer is rare. ##### Reading Failures from `/api/auto/` `POST /api/auto/` answers with HTTP 200 whenever the ladder ran, even when every rung failed. The real outcome lives in the body: ```json { "status": 403, "error": "exit blocked by the target defense", "attempts": 7, "meta": { "rung": "fail", "solved": false, "attempts": 7, "credits": 47 } } ``` `status` is the last status the target answered with, or 502 when no attempt reached it (504 when the time budget ran out first). A request field Auto can't accept (a `timeout_ms` under 5000 or over 180000, say) comes back the same way: HTTP 200 with `"status": 400` and the reason in `error`, before any attempt is made and at no cost. So don't branch on the transport status for Auto. Read `status` and `error` from the body instead. A genuine non-200 from `/api/auto/` means FourA rejected the call before the ladder started, or couldn't complete it: 400 (malformed JSON, a private or reserved target, or a target on FourA's own API host), 401, 413, 502, 503 or 504. One plan limit is answered at the door: `exitClass: premium` on a plan without premium exits gets a 403 with `X-FourA-Limit: plan_limit_premium`, before any work is done. Every other limit, yours or the platform's, comes back inside the 200 with its status in the body. When a site has failed several Auto calls made with your API key in a row, Auto answers that key's next calls to it straight away for a while without trying: `"error": "target temporarily unservable, retry later"`, `"status": 503` and a `retryAfter` in seconds. It costs nothing; wait `retryAfter` seconds. A plan limit met by one of the sub-calls also comes back as HTTP 200. The body is the refusal itself, with its `reason`, plus `status` and `meta`, and the response carries the same `X-FourA-Limit` header as a direct refusal: ```json { "status": 429, "error": "Credit limit reached: 75,000 of 75,000 credits used this billing period. Upgrade your plan or wait for the reset.", "reason": "plan_limit_credits", "documentation": "https://foura.ai/prices", "used": 75000, "hard_stop": 75000, "retry_after_seconds": 86400, "resets_at": "2026-10-01T00:00:00.000Z", "meta": { "rung": "fail", "solved": false, "attempts": 1, "credits": 0 } } ``` Which limits stop the ladder and which only close a rung is covered in [Smart Fetch (Auto)](/docs/guides/smart-fetch#when-your-plans-limits-meet-the-ladder). ##### Target-Side Failures Inside 200 OK Not every failure shows up as a non-2xx HTTP status. When the target answers HTTP 200 but FourA's answer carries an `error` (your `validate` rules rejected the body, for example) or the body is a check page FourA recognises, the outcome is `application_error`. When the target returns a non-2xx your `validate` rules don't accept, the outcome is `application_fail` and the body comes through unchanged. Neither case is billed: only `success` is. A Browser call that finds every browser busy waits up to 10 seconds for one to free up. If none does, it comes back as the 503 `Backend service unavailable` described above. Browser can also answer HTTP 200 with `"error": "No available browser slot"` when it couldn't get a browser at all. Neither is billed. Retry after a few seconds, or keep fewer Browser calls open at once. The [Outcomes](/docs/api/outcomes) reference covers the full taxonomy. A Single call through a `proxy` you pinned can also answer HTTP 200 with `"error": "The exit gave the same answer for different sites"` next to the body. FourA saw that exit hand the same page to unrelated sites, so the page is the exit's own, not the one you asked for. It's `application_error` and isn't billed. Take a fresh exit from `POST /api/proxy/`, which moves past such an exit on its own. The same applies when the exit's own network answers instead of the site. The call comes back with `"error": "The exit answered instead of the site ()"` or `"error": "Corporate web gateway intercepted the request ()"` (a sign-in or filtering page, or a redirect to one), or with `"error": "Honeypot echo proxy (plaintext request dump)"` when the exit sent back a copy of your own request. FourA checks for these whatever your `validate` rules say, `returnBuffer` downloads included. None of them is billed. Take a fresh exit the same way. ##### Response Encoding FourA auto-decodes response bodies to UTF-8. If the target serves `windows-1251`, `gbk`, `shift_jis`, `iso-8859-*`, or any other charset declared in the `Content-Type` header or an HTML `` tag, you receive a clean UTF-8 string in the `data` (single, proxy) or `body` (browser) field. For binary payloads (images, protobuf, raw audio), set `returnBuffer: true` on the request. Single and Proxy then return `data` as an object holding the raw bytes, `{"type": "Buffer", "data": []}`, with no charset transcoding applied. ##### Retry Strategy A practical retry policy: ```python import time import requests # Plan limits that no short wait will clear: stop the run instead of retrying. STOP_ON = { "plan_limit_feature", "plan_limit_premium", "plan_limit_browser_daily", "plan_limit_credits", "plan_limit_bandwidth", } def make_request(url, payload, api_key, max_retries=3): for attempt in range(max_retries): resp = requests.post( url, headers={"X-API-Key": api_key, "Content-Type": "application/json"}, json=payload, ) if resp.status_code == 200: return resp.json() body = resp.json() if resp.headers.get("content-type", "").startswith("application/json") else {} request_id = resp.headers.get("X-FourA-Request-Id", "?") limit = resp.headers.get("X-FourA-Limit") if limit in STOP_ON: raise RuntimeError(f"{limit} (request {request_id}): {body.get('error')}") # Plan limits send Retry-After and retry_after_seconds; platform limits send retryAfter. header = resp.headers.get("Retry-After") retry_after = ( int(header) if header and header.isdigit() else body.get("retry_after_seconds") or body.get("retryAfter") or 2 ** attempt ) if resp.status_code in (429, 503): time.sleep(retry_after) continue if resp.status_code >= 500: # 500, 502, 503, 504 are all ours to fix time.sleep(2 ** attempt) continue # 400/401/403/404 won't fix themselves raise RuntimeError(f"{resp.status_code} (request {request_id}): {body.get('error')}") raise RuntimeError(f"Exhausted {max_retries} retries") ``` ##### Proxy Failures Carry a Report A `POST /api/proxy/` call that runs out of attempts comes back as HTTP 200 with an error envelope, not as an HTTP error code. The error string is short and always the same shape, so an `attemptReport` object rides beside it with the counts: ```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 } ``` Log `attemptReport.summary` next to the error and you'll know whether the exits were blocked, dead, or delivering pages your own `validate` rules rejected. Field reference and what to do about each count: [Why a Proxy Request Ran Out of Tries](/docs/troubleshooting/proxy-attempt-report). ##### Related - [Rate Limits](/docs/api/rate-limits): Plan limits, concurrency, and RPM details - [Run Requests in Parallel](/docs/how-to/run-requests-in-parallel): Staying under your plan's concurrency - [Request Outcomes](/docs/api/outcomes): The seven outcome values explained - [Common Issues](/docs/troubleshooting/common-issues): Symptoms, causes, fixes - [Site checks](/docs/api/site-checks): When the body is a challenge page rather than an error - [Why a Proxy Request Ran Out of Tries](/docs/troubleshooting/proxy-attempt-report): Reading `attemptReport` --- #### Rate Limits URL: https://foura.ai/docs/api/rate-limits Every FourA API request passes three checks before it reaches an engine: the limits of your own plan, then the platform's shared allowance for the endpoint you called, then the platform's shared allowance for all traffic. Each check can refuse a request on its own, and each one answers with a different body. ##### The Three Checks, in Order 1. **Plan limits.** What your own plan allows: which endpoints and parameters it includes, how many requests may run at once per endpoint, how many per minute, how many browser requests per day, and the credits and bandwidth available in the billing period. 2. **Global platform limit.** Everything the API host you called is handling at that moment, whichever endpoint the traffic went to. A refusal here reports `"service": "api"`. 3. **Per-endpoint platform limit.** Traffic on the single, proxy, or browser service you called. Your own plan is judged first, and that ordering is the contract rather than an implementation detail. The shared allowances are common property, so a request the platform was always going to refuse must not spend them on the way to being refused. One account sending far more than its plan allows is cut before it touches anything anyone else draws on. Checks 2 and 3 count FourA's total traffic, not yours. Read a refusal from either one as "FourA is busy", not as "you sent too much". Check 1 is about your account alone, and nothing else on the platform moves it. A refusal from either shared check gives your account back everything its admission counted, the per-minute bucket and the browser day slot alike, because the request never reached a backend. It doesn't count toward the retry pause described under [Requests per minute](#requests-per-minute) either: FourA's capacity refused it, not your plan. `POST /api/auto/` holds no slot of its own. The Single, Proxy and Browser sub-calls it makes for you pass all three checks like any other request, so a parallel batch of auto calls counts against your plan through its sub-calls. (Your request counts and success rate count the auto call itself, once; the sub-calls are shown as its attempts.) ##### Plan Limits A plan limit answers with an `X-FourA-Limit` header naming which limit refused the call. The same code is in the body under `reason`, so you can branch on it without reading headers. Every plan-limit body carries `error`, `reason`, and `documentation`; the rest of the fields depend on the limit. | `X-FourA-Limit` | Status | What ran out | |-----------------|--------|--------------| | `plan_limit_feature` | 403 | The endpoint you called, or the `exitCountries` parameter, isn't in your plan | | `plan_limit_premium` | 403 | `exitClass: premium` isn't in your plan | | `plan_limit_concurrency` | 429 | Simultaneous requests on that endpoint | | `plan_limit_rate` | 429 | Requests per minute on that endpoint | | `plan_limit_browser_daily` | 429 | Browser requests for the day | | `plan_limit_credits` | 429 | Billed credits for the billing period | | `plan_limit_bandwidth` | 429 | Bandwidth for the billing period | The numbers behind each limit are your plan's, and the **Limits & Features** tab of [Usage & Limits](/docs/dashboard/quota) lists them next to your live usage. Don't hardcode them: every refusal carries the ceiling that refused it. A refused request spends nothing. The outcome is `rate_limit`, and only `success` is billed. ###### Endpoint or parameter not in the plan A 403 with `plan_limit_feature` means the call asked for something the plan doesn't include. The check runs before anything is counted, so the refused call doesn't touch your rate or daily counters. ```json { "error": "The browser endpoint is not included in your plan (Free). Upgrade to use it.", "reason": "plan_limit_feature", "documentation": "https://foura.ai/prices" } ``` The same code and status answer a `POST /api/proxy/` call that sets `exitCountries` on a plan without geo targeting. The `error` string names the parameter: ```json { "error": "Geo targeting (the exitCountries parameter) is not included in your plan (Free). Remove the parameter or upgrade.", "reason": "plan_limit_feature", "documentation": "https://foura.ai/prices" } ``` `plan_limit_premium` is the same shape for `exitClass: premium` on a plan without premium exits. A plan that includes premium exits but has spent this period's premium traffic isn't refused: the request is served from the standard pool and the response reports `exitClass: standard`. Neither case spends a premium exit. See [exitClass](/docs/api/endpoints#exitclass). Neither 403 sets `Retry-After`. Waiting doesn't change the answer. ###### Simultaneous requests Concurrency is counted per endpoint: your plan carries one ceiling for Single, one for Proxy, and one for Browser. The request that goes over comes back as 429 with `Retry-After: 1`: ```json { "error": "Concurrency limit reached: your plan allows 50 simultaneous proxy request(s). Retry when an in-flight request finishes.", "reason": "plan_limit_concurrency", "documentation": "https://foura.ai/prices", "limit": 50, "in_flight": 51, "retry_after_seconds": 1 } ``` `in_flight` counts the refused request too, so it reads at least one more than `limit`. The fix is to bound your own parallelism rather than to retry harder. Answering a 429 by re-sending the same batch immediately produces another 429 for every call in it. See [Run Requests in Parallel](/docs/how-to/run-requests-in-parallel) for a worked pattern. ###### Requests per minute Single and Proxy carry a per-minute allowance, measured over a sliding minute. Only admitted requests count toward it: a refused request is taken back out, so an account that asks steadily a little over its allowance is served its allowance rather than refused almost everything. ```json { "error": "Rate limit reached: your plan allows 600 single requests per minute. Cool down and retry.", "reason": "plan_limit_rate", "documentation": "https://foura.ai/prices", "limit_per_minute": 600, "current_rate": 613, "retry_after_seconds": 17 } ``` `retry_after_seconds` is how long until one more request would be admitted, if you send nothing else in between: at least 1 second and at most 120. The `Retry-After` header carries the same value. Retrying refused requests faster than that has a rule of its own. When the requests this allowance refused in the sliding minute go over twice the allowance, the call is refused with a 30-second pause instead: ```json { "error": "Rate limit cooldown: 1250 requests over your plan's 600 single requests per minute were refused in the last minute and kept arriving. Pause for 30 seconds.", "reason": "plan_limit_rate", "documentation": "https://foura.ai/prices", "limit_per_minute": 600, "current_rate": 540, "refused_last_minute": 1250, "cooldown": true, "retry_after_seconds": 30 } ``` Refusals during the pause aren't counted, so the pause ends on its own as the minute moves on, even for a client that keeps retrying. To tell the pause from the ordinary allowance, read `cooldown` rather than the `error` text. ###### Browser requests per day Browser has no per-minute allowance. Its plan limit is a number of browser requests per day, counted from midnight UTC, and the meter counts every browser request admitted, not only successful ones. ```json { "error": "Daily browser limit reached: your plan allows 300 browser requests per day (resets at midnight UTC).", "reason": "plan_limit_browser_daily", "documentation": "https://foura.ai/prices", "limit_per_day": 300, "used_today": 301 } ``` This refusal carries no `retry_after_seconds` and no `Retry-After` header, because the wait is hours rather than seconds. Treat it as a stop and schedule the next run for midnight UTC. ###### Credits for the billing period Only billed credits count, which means only successful requests. When the billed total reaches the credits available to you this period, further requests are refused until the period resets or you buy more. ```json { "error": "Credit limit reached: 75,000 of 75,000 credits used this billing period. Upgrade your plan or wait for the reset.", "reason": "plan_limit_credits", "documentation": "https://foura.ai/prices", "used": 75000, "hard_stop": 75000, "retry_after_seconds": 86400, "resets_at": "2026-10-01T00:00:00.000Z" } ``` `hard_stop` is the billed-credit count at which requests stop for this period. Read it from the body rather than computing it: it already includes any credits you bought on top of the plan. ###### Bandwidth for the billing period Plans that carry a bandwidth cap refuse requests once the standard traffic this period reaches it. Premium traffic has its own allowance and doesn't count toward this cap. Bought bandwidth counts the same as included bandwidth, and the `error` string quotes what's available to you, not what the plan alone includes. ```json { "error": "Bandwidth limit reached: 50 GB is available to you this billing period.", "reason": "plan_limit_bandwidth", "documentation": "https://foura.ai/prices", "used_bytes": 53687091200, "limit_bytes": 53687091200, "retry_after_seconds": 86400, "resets_at": "2026-10-01T00:00:00.000Z" } ``` On both period limits, `retry_after_seconds` is capped at 24 hours; `resets_at` is the exact instant the period rolls over. ###### Plan limit fields | Field | Type | Present on | Description | |-------|------|-----------|-------------| | `error` | string | all | Human-readable message, including the number you're held to | | `reason` | string | all | `plan_limit_` plus the limit name. Same value as the `X-FourA-Limit` header. | | `documentation` | string | all | Link to the plans page | | `retry_after_seconds` | number | concurrency, rate, credits, bandwidth | How long to wait. Same value as the `Retry-After` header. | | `limit` | number | concurrency | Simultaneous requests the plan allows on that endpoint | | `in_flight` | number | concurrency | Requests running on that endpoint for your account, including the refused one | | `limit_per_minute` | number | rate | Requests per minute the plan allows on that endpoint | | `current_rate` | number | rate | Requests counted in the sliding minute, including the refused one | | `refused_last_minute` | number | rate pause | Requests the per-minute allowance refused in the sliding minute. Only on the 30-second pause. | | `cooldown` | boolean | rate pause | `true` on the 30-second pause for retrying too fast. Absent on an ordinary per-minute refusal. | | `limit_per_day` | number | browser daily | Browser requests the plan allows per day | | `used_today` | number | browser daily | Browser requests counted today, including the refused one | | `used` | number | credits | Billed credits so far this period | | `hard_stop` | number | credits | Billed credits at which requests stop this period | | `used_bytes` | number | bandwidth | Standard traffic so far this period, in bytes. Premium traffic isn't included. | | `limit_bytes` | number | bandwidth | Bytes available this period | | `resets_at` | string | credits, bandwidth | ISO 8601 timestamp of the period end | Plan limits use `retry_after_seconds`. The platform limits below use `retryAfter`. A retry helper needs to read both, or read the `Retry-After` header, which plan limits set, and so does the 503 `API key check temporarily unavailable` (see [Errors](/docs/api/errors)). ##### Platform Limits The platform checks track two things per service and one more across all of them: - **Concurrency**: how many requests FourA is running at the same time. - **RPM**: how many requests FourA has taken in the last 60 seconds. Both counters are shared by everyone using that service. `current` and `limits` in the responses below describe the platform, not your account. If you want your own number, read `in_flight` from a plan-limit response, or open **Usage & Limits** in the [dashboard](https://foura.ai/dashboard). ###### 429: RPM Exceeded ```json { "error": "Rate limit exceeded", "status": 429, "service": "single", "retryAfter": 5, "current": { "concurrency": 12, "rpm": 3000 }, "limits": { "maxConcurrency": 500, "maxRpm": 3000 } } ``` The service took its allowed requests for the last minute. Wait `retryAfter` seconds. ###### 503: Concurrency Exceeded ```json { "error": "Service at capacity", "status": 503, "service": "proxy", "retryAfter": 2, "current": { "concurrency": 500, "rpm": 1200 }, "limits": { "maxConcurrency": 500, "maxRpm": 3000 } } ``` The service is running as many requests as it's allowed to run at once. This clears in seconds. ##### Service Disabled When a service is temporarily taken offline for maintenance, the API returns 503 with a different error message: ```json { "error": "Service disabled", "status": 503, "service": "single", "retryAfter": 60, "current": { "concurrency": 0, "rpm": 0 }, "limits": { "maxConcurrency": 500, "maxRpm": 3000 } } ``` This isn't a rate limit. The service is temporarily unavailable. Check the `retryAfter` value and retry after that many seconds. This typically resolves within minutes. Both 503 shapes carry the same keys, so branch on the `error` string and never on which fields are present. `Service disabled` is maintenance, `Service at capacity` is concurrency. On the maintenance shape, `current.concurrency` and `current.rpm` are always `0`: the request was turned away before anything was measured. ##### Platform Limit Fields | Field | Type | Description | |-------|------|-------------| | `error` | string | Human-readable error message | | `status` | number | HTTP status code (429 or 503) | | `service` | string | Which service refused the call: single, proxy, browser, or api | | `retryAfter` | number | Recommended wait time in seconds before retrying | | `current.concurrency` | number | Requests the service was running platform-wide when it refused | | `current.rpm` | number | Requests the service took platform-wide in the last 60 seconds | | `limits.maxConcurrency` | number | The service's platform-wide concurrency allowance | | `limits.maxRpm` | number | The service's platform-wide per-minute allowance | ##### Handling Every Refusal With One Helper `Retry-After` is set on the plan limits that are worth waiting for, `retry_after_seconds` is in their bodies, and `retryAfter` is in the platform bodies. Read all three in that order, and stop on the plan limits that no wait will clear: ```python import time import requests # Plan limits that a short wait never clears. STOP_ON = { "plan_limit_feature", "plan_limit_premium", "plan_limit_browser_daily", "plan_limit_credits", "plan_limit_bandwidth", } def wait_seconds(resp, attempt): header = resp.headers.get("Retry-After") if header and header.isdigit(): return int(header) try: body = resp.json() except ValueError: return 2 ** attempt return body.get("retry_after_seconds") or body.get("retryAfter") or 2 ** attempt def fetch(url, api_key, max_retries=5): for attempt in range(max_retries): resp = requests.post( "https://eu.api.foura.ai/api/single/", headers={"X-API-Key": api_key, "Content-Type": "application/json"}, json={"method": "GET", "url": url}, ) limit = resp.headers.get("X-FourA-Limit") if limit in STOP_ON: raise RuntimeError(f"stopped by plan limit {limit}: {resp.json().get('error')}") if resp.status_code in (429, 503): time.sleep(wait_seconds(resp, attempt)) continue return resp raise RuntimeError("Max retries exceeded") ``` A daily allowance doesn't come back for hours, and a period allowance doesn't come back for days, so treat them as a stop rather than a wait. Read `resets_at` from the body if you want to schedule the next run. ##### Tips - Cap the number of requests you keep in flight instead of retrying a refused batch. A retry storm turns one 429 into many. - Read `X-FourA-Limit` first. It tells you in one string whether the limit is yours or the platform's, and no platform refusal sets it. - Don't hardcode the numbers. Every plan-limit response carries the ceiling that refused it, and [Usage & Limits](/docs/dashboard/quota) shows all of them. - `retryAfter` on platform limits is fixed by kind: 2 seconds for concurrency, 5 for RPM, 60 for maintenance. - Match on `error` to tell the two 503s apart. Both shapes carry `current` and `limits`, so a check for "are those fields there?" reads maintenance as a concurrency problem. - A 403 with `X-FourA-Limit` is about your plan, not about the target site. The target never answered. ##### The Proxy Port Has Its Own Numbers Everything above is about the JSON API. Traffic you send through `proxy.foura.ai` is held to a separate set of plan numbers, in a different unit: tunnels open at once, tunnel openings a minute, and the standard traffic for the billing period. Those refusals arrive as an HTTP status with an `X-Foura-Error` header rather than as a JSON body, because a `CONNECT` has no body to put one in. See [Proxy Port](/docs/api/proxy-port#responses) for the status table and [How Your Plan Is Metered](/docs/guides/plans-and-metering) for which pool the port's gigabytes come out of. ##### Related - [Run Requests in Parallel](/docs/how-to/run-requests-in-parallel): A worked bounded-concurrency pattern - [Usage & Limits](/docs/dashboard/quota): Every plan limit next to your live usage - [API Endpoints](/docs/api/endpoints): Full parameter reference - [Error Handling](/docs/api/errors): All error types and responses - [Response Headers](/docs/api/response-headers): `X-FourA-Limit`, `Retry-After`, and the rest - [Troubleshooting](/docs/troubleshooting/common-issues): Common problems and fixes --- #### Inspect Endpoint URL: https://foura.ai/docs/api/inspect A public diagnostic endpoint that returns your client IP and proxy detection details. Use it to verify whether your outbound traffic is going through a proxy before you start sending requests through FourA. ##### Request `GET /inspect` This endpoint is public. No API key, no `X-API-Key` header. ```bash curl https://eu.api.foura.ai/inspect ``` ##### Response ```json { "clientIp": "203.0.113.42", "proxyDetection": { "isProxy": false, "proxyType": "none", "anonymityLevel": "none", "confidence": "medium", "detectedHeaders": [], "explanation": "Direct connection (no client-side proxy detected). Note: Elite proxies cannot be ruled out without additional data." }, "proxyChain": { "originalClientIp": "203.0.113.42", "proxyCount": 0, "hasPrivateIps": false }, "timestamp": "2026-04-29T08:07:04.683Z" } ``` ###### Top-Level Fields | Field | Type | Description | |-------|------|-------------| | `clientIp` | string | Original client IP (leftmost address in the forwarded chain) | | `proxyDetection` | object | Proxy detection and anonymity analysis | | `proxyChain` | object | Client-side IP chain breakdown | | `timestamp` | string | ISO 8601 timestamp of when the request was inspected | ###### `proxyDetection` | Field | Type | Description | |-------|------|-------------| | `isProxy` | boolean | True if client-side proxy headers were detected | | `proxyType` | string | One of: `transparent`, `anonymous`, `distorting`, `none` | | `anonymityLevel` | string | One of: `transparent`, `anonymous`, `none` | | `confidence` | string | Detection confidence: `high`, `medium`, or `low` | | `detectedHeaders` | string[] | Header names that signaled a proxy (e.g. `via`, `forwarded`) | | `explanation` | string | Plain-English summary of the result | ###### `proxyChain` | Field | Type | Description | |-------|------|-------------| | `originalClientIp` | string | Same as top-level `clientIp` | | `proxyChain` | string[] | List of proxy IPs between you and FourA, if any | | `proxyCount` | number | Number of proxies detected in the chain (0 = direct) | | `via` | string[] | Contents of the `Via` header, if present | | `hasPrivateIps` | boolean | True if any private/internal IPs appear in the chain | ##### Proxy Types | Type | What it means | |------|---------------| | `transparent` | Forwards your real IP and identifies itself as a proxy | | `anonymous` | Hides your real IP but identifies itself as a proxy | | `distorting` | Sends a fake client IP to the destination | | `none` | No proxy headers seen: a direct connection, or a proxy that sends none | ##### When to Use This - Confirm your outbound traffic isn't passing through a corporate proxy or tunnel you didn't expect. - Debug why your real IP shows up in target site logs even when you thought you were anonymous. - Verify a residential or rotating proxy is masking your origin before you send paid requests through FourA. This endpoint inspects your connection to FourA. It does not test FourA's own proxy network. To verify which IP a FourA-routed request comes from, use the [Proxy endpoint](/docs/api/endpoints) and read the response. ##### Limits - No authentication required. - Subject to a generous public rate limit. If you call it more than a few times per second from the same IP, expect throttling. - The response reflects only headers and IPs. It cannot detect a perfectly transparent elite proxy that strips all proxy headers. ##### Related - [API Endpoints](/docs/api/endpoints): The authenticated request endpoints - [Authentication](/docs/getting-started/authentication): How to authenticate the rest of the API - [Common Issues](/docs/troubleshooting/common-issues): Debug request failures --- #### Request Outcomes URL: https://foura.ai/docs/api/outcomes Every request to the FourA API is classified into exactly one outcome, and so is every tunnel through the proxy port. The outcome is computed once, at the end of the call, and recorded against the credential that made it. Your dashboard, activity feed, and billing all read the same field. Only `success` costs credits. Premium traffic is counted apart from credits and doesn't follow the outcome: see [Billing Implications](#billing-implications). ##### The Seven Outcomes These are the seven a request can end in. A tunnel uses five of them: see [Tunnels Use the Same Vocabulary](#tunnels-use-the-same-vocabulary) below. | Outcome | Layer | What it means | |---------|-------|--------------| | `success` | n/a | A valid response was delivered. Counts against your billable quota. | | `application_error` | target | The target returned HTTP 200, but the body carried an error field, or the body is a bot-check page FourA recognises. | | `application_fail` | target | The target returned a non-2xx that your `validate` rules did not accept, or no response at all, including a target host name that can't be resolved. | | `client_error` | caller | Your request was rejected before it left FourA. Bad parameters, malformed proxy value, SSRF-guarded URL. | | `rate_limit` | FourA | The request was refused before it ran: by one of your plan's limits (a 403 for an endpoint or parameter the plan doesn't include, a 429 for a spent allowance), or by the platform's shared RPM or concurrency allowance. | | `service_error` | FourA | The engine answered with a server error, or its body wasn't valid JSON. | | `service_fail` | FourA | FourA's own network failed: its engine didn't answer in time or the connection dropped, or you disconnected. | The layer column tells you who's responsible: - **target** outcomes are about the site you called. Your request reached FourA fine, and FourA reached the target fine. The target itself returned an error. - **caller** outcomes mean your request never had a chance. Fix the request shape. - **FourA** outcomes are on us. Retry, and check the [status page](https://updates.foura.ai/status) if they persist. A target site returning `403` is `application_fail`, not `client_error`. Your call was well-formed. The site just said no. ##### What a Failed Call Looks Like A call that reached FourA and ran comes back as HTTP 200 whatever happened next. Only a refusal before the fetch (400, 401, 403, 413, 429) or a failure on FourA's side (5xx) uses another HTTP status. So the outcome is read from the body, not from the transport status. | Body | What happened | Outcome | |------|---------------|---------| | `status: 200`, `headers`, `data`, no `error` | The page was delivered. | `success` | | `status` other than 200, `headers`, `data`, no `error` | The target answered and you sent no `validate`. Its answer is in `data`. | `application_fail` | | `status`, `headers`, `data` and `error` | The target answered and your `validate` rules rejected it, for example `Status 404 is not in accept list 200`. | `application_fail` | | `error` and `total_time` only, no `status`, no `data` | The target never answered with HTTP: the redirect limit was reached (`Number of redirects hit maximum amount`), the connection or the read timed out, the connection was reset, or the reply was empty. The text is the engine's own reason. | `application_fail` | | `status: 0` and `error`, nothing else | The host name can't be resolved: `could not resolve : `. No engine ran, and on Auto there is no `meta`. Retrying won't change it until DNS does. | `application_fail` | A redirect loop, as the fourth row sees it: ```json { "error": "Number of redirects hit maximum amount", "total_time": 0.808 } ``` On `POST /api/proxy/` a call that runs out of attempts carries `error: Download maxTry limit reached` and an `attemptReport` with the counts (see [API Errors](/docs/api/errors)). On `POST /api/auto/` a ladder that ran and failed carries `status`, `error`, `attempts` and `meta.rung` set to `fail`. `X-FourA-Credits` reports the work done, on a failure too. Billing counts only `success`, so none of the failed shapes above is charged. ##### Success Is `validate`-Aware Without `validate`, the API marks a request `success` only when the target returns HTTP 200. With `validate`, success follows the rules you declared. If you tell the API that 200 and 403 are both acceptable for a given request, a 403 comes back as `success`. The body still reaches you unchanged. ```bash curl -X POST https://eu.api.foura.ai/api/single/ \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "method": "GET", "url": "https://target.example/feed", "validate": { "status": { "accept": [200, 403] } } }' ``` In this call, a 403 response counts as `success` and bills as one request. A 500 response counts as `application_fail` and is not billed. The same logic applies to `validate.headers` and `validate.data`. Any response the engine accepts against your rules comes back as `success` regardless of HTTP status. One answer is never `success`, with or without `validate`: an HTTP 200 whose body is a bot-check page FourA recognises, such as a visual verification task or a page that only asks the browser to run JavaScript. That request is `application_error` and isn't billed. The body still reaches you unchanged, and the [`X-FourA-Check-Page`](/docs/api/response-headers) header names the check page. ##### Billing Implications | Outcome | Billable | Counts toward quota | |---------|----------|--------------------| | `success` | Yes | Yes | | `application_error` | No | No | | `application_fail` | No | No | | `client_error` | No | No | | `rate_limit` | No | No | | `service_error` | No | No | | `service_fail` | No | No | Only requests that delivered the data you asked for are billed. Failures on FourA's side, the target's side, or your own side are all free. The table is about credits. Premium traffic is counted apart from them: a request that tried a [premium exit](/docs/api/endpoints#exitclass) counts the traffic that attempt carried, whatever the outcome, because the exit was used either way. An attempt that was still running when another exit answered is stopped at once, and the traffic it carried until then counts too. Standard traffic doesn't follow the outcome either: on a plan with a bandwidth cap, every request's traffic counts toward it. A request refused by one of your own plan's limits counts no traffic. ##### Tunnels Use the Same Vocabulary A tunnel through the [proxy port](/docs/api/proxy-port) ends in one of these outcomes too, so one set of labels covers both products. Only five of the seven can happen, because the two `target` outcomes need FourA to have seen the target's answer, and a tunnel's answer is your own encrypted traffic. | Outcome | On a tunnel, this means | |---------|-------------------------| | `success` | The tunnel opened and your tool got it. | | `client_error` | FourA won't open that tunnel: a private or reserved address, or a port it doesn't serve. | | `rate_limit` | One of your plan's numbers was reached (tunnels open at once, tunnel openings a minute, the standard traffic for the period, premium traffic you don't have), or the port itself was at its capacity or opening rate. | | `service_error` | FourA had no exit for what you asked. Usually temporary. | | `service_fail` | The target couldn't be reached through any exit FourA tried: DNS, timeout, connection refused. | | `application_error` | Never occurs on a tunnel. | | `application_fail` | Never occurs on a tunnel. | A refusal also carries a short reason, and the dashboard shows it in your own terms rather than ours. An option FourA can't honour is answered `400` on the connection itself and writes no row, so it never shows up here at all. | Reason on screen | What ran out | |------------------|--------------| | port not in plan | Your plan doesn't include the proxy port | | tunnels at once | Every tunnel your plan allows at once was in use | | openings per minute | Your plan's tunnel openings for this minute were used up | | traffic used up | Your plan's traffic for this period is used up | | premium not available | Premium traffic isn't available on your plan right now | | port was full | The port itself was at capacity or opening rate. Try again in a moment. | | port not served | FourA doesn't open tunnels to that port | | private address | Private and reserved addresses can't be reached | Nothing about a tunnel is billed in credits, because a tunnel has no request to charge one to. The port meters bytes instead. See [How Your Plan Is Metered](/docs/guides/plans-and-metering). ##### Reading Outcomes in the Dashboard Every request your API key makes shows up on the [Activity](/docs/dashboard/activity-log) feed with its outcome label. The [Metrics](/docs/dashboard/metrics) and [Overview](/docs/dashboard/overview) pages aggregate the same field for donut charts and timelines. When you filter Activity by outcome, you can also focus on a single endpoint (Auto, Single, Proxy Finder, Browser) to see whether a class of failure is specific to one of them. Switch the page's **Product** to Proxy and the same outcome pills filter your tunnels. ##### Retry Heuristics A first-pass retry policy keyed on outcomes: | Outcome | Retry safe? | When | |---------|-------------|------| | `success` | n/a | You have the response. | | `application_error` | Sometimes | Read the target's error body. Some are transient, most aren't. If `X-FourA-Check-Page` is set, the site served a check page: send the URL to Auto, which treats a check page as a step to pass, not as the answer. | | `application_fail` | Sometimes | If the target is rate-limiting you, slow down. If it's blocking you, switch to the Proxy or Browser endpoint. | | `client_error` | No | The request will fail again the same way. Fix the input. | | `rate_limit` | Depends | Honor the wait the response gives you: `Retry-After`, `retry_after_seconds`, or `retryAfter`. On `plan_limit_browser_daily`, stop until midnight UTC; on `plan_limit_credits` or `plan_limit_bandwidth`, stop until `resets_at`; on `plan_limit_feature` or `plan_limit_premium`, change the request. | | `service_error` | Yes | Short exponential backoff. | | `service_fail` | Yes | Same as `service_error`. | ##### Related - [API Errors](/docs/api/errors): HTTP-level error responses - [Proxy Port](/docs/api/proxy-port): The status codes a tunnel's refusal arrives as - [Rate Limits](/docs/api/rate-limits): What triggers `rate_limit`, and the two shapes it comes back in - [Metrics](/docs/dashboard/metrics): Where you see outcomes broken down - [Activity Log](/docs/dashboard/activity-log): Per-request outcome history --- #### Response Headers URL: https://foura.ai/docs/api/response-headers Every response from the FourA API includes a small set of custom headers. They're useful for tracing, support, billing reconciliation, and post-hoc analysis. ##### Headers FourA Sets | Header | Set on | Description | |--------|--------|-------------| | `X-FourA-Request-Id` | Every `/api/*` response, including errors and 401s, except a body FourA can't read at all (`400 Invalid JSON in request body`, `413`), which is refused before an ID is assigned | A UUID identifying this request. Log it on your side. | | `X-FourA-Credits` | Every `/api/*` response that reached the backend | Credits spent on this call. Returned on success and on failure (the work was done either way). | | `X-FourA-Limit` | Every `403` or `429` raised by one of your plan's limits | Which limit refused the call: `plan_limit_` followed by `feature`, `premium`, `concurrency`, `rate`, `browser_daily`, `credits`, or `bandwidth`. | | `Retry-After` | Plan-limit `429`s that a wait clears: concurrency, rate, credits, bandwidth | Seconds to wait, as an integer. Matches `retry_after_seconds` in the body. | | `X-FourA-Exit-Class` | Every `/api/proxy/` or `/api/auto/` call that named an `exitClass` and delivered a page, and every Single or Browser call served through a premium exit | `premium` or `standard`: the class of exit that delivered the body. A failed Proxy call delivered nothing and carries none. | | `X-FourA-Check-Page` | Single, Proxy Finder and Browser responses whose HTTP 200 body is a bot-check page FourA recognises | The check page's name, for example `amazon-captcha`. Such a request isn't billed: see [Request Outcomes](/docs/api/outcomes). | | `Content-Type` | Every response | Always `application/json` for the envelope. The target's content-type comes back inside the envelope's `headers` field. | ##### X-FourA-Request-Id Each call to `POST /api/auto/`, `POST /api/single/`, `POST /api/proxy/`, or `POST /api/browser/` is tagged with a UUID. The header is set even when authentication fails, so you can correlate misconfigured calls too. ```bash curl -i -X POST https://eu.api.foura.ai/api/single/ \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"method": "GET", "url": "https://example.com"}' ``` ``` HTTP/1.1 200 OK X-FourA-Request-Id: 9f1c4e6c-7b2a-4d3e-8a1f-2c9d8e4a3b15 X-FourA-Credits: 2 Content-Type: application/json ... ``` ###### When to use it - **Support tickets**: include the request ID and we can find the exact call in our records. - **Your own logs**: store it next to your application log line. If a customer complaint says "the data was wrong at 14:32", you can replay the exact request. - **Dashboard tracing**: the same ID appears in the [Activity feed](/docs/dashboard/activity-log) for keys you manage, so you can open the matching row and inspect the captured request and response. ###### Example: log on your side ```python import logging import requests log = logging.getLogger(__name__) def fetch(url, api_key): resp = requests.post( "https://eu.api.foura.ai/api/single/", headers={"X-API-Key": api_key, "Content-Type": "application/json"}, json={"method": "GET", "url": url}, ) request_id = resp.headers.get("X-FourA-Request-Id", "no-id") credits = resp.headers.get("X-FourA-Credits", "0") log.info("foura request_id=%s url=%s status=%s credits=%s", request_id, url, resp.status_code, credits) resp.raise_for_status() return resp.json() ``` ```javascript async function fetchPage(url, apiKey) { const resp = await fetch('https://eu.api.foura.ai/api/single/', { method: 'POST', headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify({ method: 'GET', url }) }); const requestId = resp.headers.get('X-FourA-Request-Id') || 'no-id'; const credits = resp.headers.get('X-FourA-Credits') || '0'; console.log(`foura request_id=${requestId} url=${url} status=${resp.status} credits=${credits}`); return resp.json(); } ``` ##### X-FourA-Credits `X-FourA-Credits` reports the credit cost of the call you just made. It's a meter, not a bill: the header reflects what the work spent regardless of outcome. The dashboard's billing layer only counts billable outcomes against your plan (see [Request Outcomes](/docs/api/outcomes) for which outcomes are billable). ###### Cost reference | Engine | Base | With `unblocker` | |--------|------|------------------| | Single | 1 | 2 | | Proxy | 2 | 4 | | Browser | 5 | 10 (when a defense was solved) | `/api/auto/` is one request on your dashboard, with a credit cost that is the sum of the sub-calls it made internally (a single replay on a warm target can finish at 2; a cold solve on a hard site can spend much more). The `X-FourA-Credits` value on the auto response equals `meta.credits` in the body and tracks the full ladder cost. ###### Why both a header and a body field? The header is convenient: you can read it before parsing the body, log it next to your request line, or sum it across many calls without JSON parsing. On Auto, the body's `meta.credits` holds the same number. Single, Proxy and Browser bodies don't carry it, so on those responses the header is the only place to read it. Each call's [Activity](/docs/dashboard/activity-log) row shows it too. ##### X-FourA-Limit `X-FourA-Limit` appears only when one of your plan's limits refused the call. The platform's shared rate limits never set it, so the header is the fastest way to tell "my plan stopped this" from "FourA is busy" without parsing the body. ``` HTTP/1.1 429 Too Many Requests X-FourA-Limit: plan_limit_concurrency Retry-After: 1 X-FourA-Request-Id: 9f1c4e6c-7b2a-4d3e-8a1f-2c9d8e4a3b15 Content-Type: application/json ``` Two of the seven values come with a 403 rather than a 429: `plan_limit_feature` (the endpoint or the `exitCountries` parameter isn't in your plan) and `plan_limit_premium` (`exitClass: premium` isn't in your plan). Neither sets `Retry-After`, because waiting doesn't change the answer. ```python STOP_ON = { "plan_limit_feature", "plan_limit_premium", "plan_limit_browser_daily", "plan_limit_credits", "plan_limit_bandwidth", } resp = requests.post(url, headers=headers, json=payload) limit = resp.headers.get("X-FourA-Limit") if limit in STOP_ON: stop_the_run(limit) # hours or days away, not seconds elif limit: time.sleep(int(resp.headers.get("Retry-After", 1))) ``` The seven values and the body fields that come with each are in [Rate Limits](/docs/api/rate-limits). ##### X-FourA-Exit-Class `X-FourA-Exit-Class` names the class of exit that delivered the body: `premium` when a premium exit did, `standard` when the standard pool did. It appears on a `POST /api/proxy/` or `POST /api/auto/` response that delivered a page whenever the request named an `exitClass`, where the body carries the same value (`exitClass` on Proxy, `meta.exitClass` on Auto), and on a Single or Browser response whenever the `proxy` you pinned was a premium exit, where the body has no field for it. A failed Proxy or Auto call served nothing, so it carries neither the header nor the field. ``` HTTP/1.1 200 OK X-FourA-Exit-Class: premium X-FourA-Credits: 2 X-FourA-Request-Id: 2c1d0f9e-4a7b-4c3e-9d2a-8b1f6e5c4d3a Content-Type: application/json ``` Traffic through a premium exit counts toward your premium traffic as well as your total bandwidth. It's measured on the network and includes premium attempts that didn't return your page, so a request answered with `standard` can still have used some premium traffic, on an attempt that failed before the standard pool answered. This header names the class that delivered, not whether premium traffic was used: the **premium** mark on an [Activity](/docs/dashboard/activity-log) row and the [Usage & Limits](/docs/dashboard/quota) page show what was counted. What `exitClass` does and when a premium exit is used: [exitClass](/docs/api/endpoints#exitclass). ##### Cache Behavior The API doesn't set `Cache-Control`. A response can carry a weak `ETag` computed from its body, but nothing is cached: every call hits the backend. If you need caching, add it on your side. ##### Target Response Headers The headers the target site returned are not on the FourA API response. They come back inside the JSON envelope as the `headers` field. For the Single and Proxy endpoints, this is an array of per-hop header objects (one entry per redirect step). For the Browser endpoint, it's a flat object of the final response headers. ```json { "status": 200, "headers": [ { "Content-Type": "text/html; charset=utf-8", "Server": "..." } ], "data": "...", "total_time": 0.42 } ``` If you need a specific target header, read it from the envelope's `headers` field, not from the HTTP response of the API call itself. ##### Related - [API Endpoints](/docs/api/endpoints): Request and response envelope shapes - [API Errors](/docs/api/errors): How error responses are structured - [Request Outcomes](/docs/api/outcomes): Which outcomes are billable - [Activity Log](/docs/dashboard/activity-log): Per-request history keyed by request ID - [Rate Limits](/docs/api/rate-limits): What each `X-FourA-Limit` value means --- #### Site checks URL: https://foura.ai/docs/api/site-checks When a target runs a bot check on the way to the page you asked for, FourA tells you. Every request that meets one comes back with a field naming the system, whether the check was cleared, and (on a clear) the clearance you can replay so the next call skips it. This page is the reference for those fields. For strategy, see [Protected sites](/docs/guides/protected-sites). ##### Where the Field Lives | Endpoint | Field | Present when | |----------|-------|--------------| | `POST /api/single/` | `defense` (object) | A bot check was recognised on the response | | `POST /api/proxy/` | `defense` (object) | Same, reported by the attempt that answered | | `POST /api/browser/` | `defenseSolved` (boolean) and `defenses` (object) | Always, on a page that loaded. `defenseSolved` is `false` and `defenses` is empty when nothing was recognised. | | `POST /api/auto/` | `meta.solved` (boolean) | On every answer once the ladder has started. `true` when a check was cleared somewhere on the ladder. A body that fails validation or a host that doesn't resolve is answered before the ladder, without `meta`. | Absence means nothing was recognised. Don't read a missing `defense` as a failure. On Single and Proxy, reporting needs `unblocker`, which is on by default. With `unblocker: false` you asked for the page exactly as it came, so Single hands back the challenge untouched and Browser renders it without solving. ##### `defense` on Single and Proxy ```json { "status": 200, "data": "...", "total_time": 3.61, "defense": { "vendor": "sgcaptcha", "solved": true, "present": ["sgcaptcha"], "ms": 3412, "hashes": 1048576, "complexity": 20, "cookie": "_I_=" } } ``` | Field | Type | Description | |-------|------|-------------| | `vendor` | string | The system this record is about: the one that was cleared, or the main one met. See the vendor list below. | | `solved` | boolean | `true` means the check was cleared and `data` is the real page. `false` means `data` may be the challenge page. | | `present` | string[] | Every system recognised on this response. Can hold more names than `vendor`, and can hold names nobody clears yet. | | `ms` | number | Milliseconds spent clearing the check. Only on a clear. | | `hashes` | number | How much computational work the challenge asked for. Only on a clear. | | `complexity` | number | The difficulty the challenge declared. Only on a clear, and only where the challenge reports one. | | `answers` | number | How many accepted answers were supplied, for challenges that want several rather than one. Only on a clear. | | `retry` | string | Present when the body came back from a retry rather than from a clear: `refusal-cookies` or `amazon-continue`. See below. | | `cookie` | string | The jar to replay: the clearance a clear earned, or the session a refusal handed out. | `solved: false` is the case worth branching on. FourA never presents a challenge page as content, so the flag is your signal that the body needs escalation rather than parsing. ###### `retry: "refusal-cookies"` Some sites don't run a puzzle. They refuse the first request, set cookies on the refusal, and serve the real page to anyone who sends those cookies back. eBay's item pages are the reference case. When that happens, FourA sends them back for you and hands you the page. The response then carries `retry: "refusal-cookies"`: ```json { "status": 200, "data": "...", "defense": { "vendor": "akamai", "solved": false, "present": ["akamai"], "retry": "refusal-cookies", "cookie": "bm_sv=...; dp1=..." } } ``` Read it like this: - **`solved` stays `false`.** Answering a handshake isn't clearing a challenge, and it never changes what the call costs. You're billed for the request you made. - **`data` is real content**, not a challenge page. This is the one case where `solved: false` doesn't mean the body needs escalation, which is why the field exists. - **`cookie` is the session the site handed out.** Replay it the same way you'd replay a clearance and the follow-up pages skip the refusal. - A retry and a clear can both happen on one request. If the retry's answer turned out to be a challenge that FourA can clear, you get `solved: true` with the vendor's own fields **and** `retry: "refusal-cookies"` beside them. `vendor` reads `unknown` when a retry produced the content and no system was recognised on the way. `present` is then an empty array. ###### `retry: "amazon-continue"` Amazon sometimes answers with a one-button robot check: a small page that asks you to click to continue shopping, with the answer already filled in. With `unblocker` on (the default), Single and Proxy press that button on the same exit, inside your `timeout_ms`, and ask for your page again with the cookies it set. When that opens the page, the response carries `retry: "amazon-continue"`: ```json { "status": 200, "data": "...", "defense": { "vendor": "akamai", "solved": false, "present": ["akamai", "amazon"], "retry": "amazon-continue", "cookie": "ak_bmsc=...; ..." } } ``` Read it like a refusal-cookies retry. `solved` stays `false`, `data` is the real page, you're billed for the request you made, and `cookie` is the jar to replay on follow-up pages. Look for `amazon` in `present` rather than in `vendor`: the check page often sets a cookie FourA also recognises, and `vendor` names the first system on the list. The picture version of the same check, where a person has to read the answer, is reported and left as it came. So is a press that only brings the check back. Either way that page isn't billed (see [What It Costs](#what-it-costs)). ##### `defenses` on Browser ```json { "status": 200, "body": "...", "userAgent": "Mozilla/5.0...", "defenseSolved": true, "defenses": { "present": ["cloudflare"], "cleared": ["cloudflare"] } } ``` | Field | Type | Description | |-------|------|-------------| | `defenseSolved` | boolean | `true` when a system was met during the load and its clearance is held on the final page. This is the flag that decides whether the call costs 5 or 10 credits. | | `defenses.present` | string[] | Every system recognised at any point during the page load, not just on the final response. A check is something that happened, and by the time the real page arrives the challenge response is long gone. | | `defenses.cleared` | string[] | The systems whose clearance the final page holds. | A name in `present` that never reaches `cleared` is a system FourA recognised without earning a clearance for it: one it can't complete yet, or one of the few Browser handles another way (see [What Gets Cleared Today](#what-gets-cleared-today)). Those never raise the price of a call. ##### Vendors | `vendor` value | The system | |----------------|-----------| | `cloudflare` | Cloudflare challenges and bot management | | `sgcaptcha` | SiteGround's site check | | `datadome` | DataDome | | `perimeterx` | PerimeterX | | `akamai` | Akamai Bot Manager | | `incapsula` | Imperva Incapsula | | `awswaf` | AWS WAF challenge | | `ebay-splashui` | eBay's own challenge | | `reddit` | Reddit's own check and refusal pages | | `amazon` | Amazon's robot check | | `hcaptcha` | hCaptcha | | `recaptcha` | reCAPTCHA | | `unknown` | No system was recognised. Only appears alongside `retry`, where the record exists to report the retry rather than a vendor. | ###### What Gets Cleared Today | Endpoint | Clears | |----------|--------| | Single, Proxy | `sgcaptcha`, `ebay-splashui`. Both are computational rather than visual, so no browser is involved. | | Browser | `cloudflare`, `sgcaptcha` | A few more are handled without a clear. Single and Proxy press Amazon's one-button robot check and report it as `retry: "amazon-continue"`. Browser presses the same button, and it waits while Akamai's sensor page, AWS WAF's challenge page or DataDome's device check loads the next page by itself, then returns that page. If that page never arrives, Browser returns the one it was holding, inside your `timeout_ms`. On Browser those names show in `defenses.present`, never in `cleared`, and the call costs 5. Everything else on the list is recognised and reported, and nothing more. That split moves as FourA learns to clear more of them, so read `solved` rather than assuming from this table. Two notes on the edges: - `hcaptcha` and `recaptcha` are also ordinary form widgets. They're only reported when the response actually blocked you (403, 429, or 503), so a checkout page with a verification widget in a form doesn't report a defense. - Being behind Cloudflare isn't a defense. `cloudflare` appears when there's a real challenge or bot-management artifact on the response, not because a site uses Cloudflare. ##### Replaying a Clearance `defense.cookie` is the whole point of the field. A clearance is bound to the exit that earned it and the User-Agent that earned it, so replay it through the same pair and the check doesn't run again. ```python import requests API = "https://eu.api.foura.ai" H = {"X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json"} # 1) First call pays for the clear. first = requests.post(f"{API}/api/proxy/", headers=H, json={ "maxTries": 5, "request": {"method": "GET", "url": "https://example.com/catalog"}, }).json() defense = first.get("defense", {}) if defense.get("solved"): clearance = defense["cookie"] exit_id = first["proxy"] # 2) Follow-up pages skip the check: same exit, same clearance. for page in range(2, 6): r = requests.post(f"{API}/api/single/", headers=H, json={ "method": "GET", "url": f"https://example.com/catalog?page={page}", "proxy": exit_id, "headers": [["Cookie", clearance]], }).json() print(page, r["status"]) ``` The first call carries the cost of the clear. Every replay is an ordinary request at the ordinary price. Three things break a replay: 1. **A different exit.** Pin the proxy ID the clearing response returned. See [Reuse a Proxy Across Requests](/docs/how-to/reuse-a-proxy). 2. **A different User-Agent.** Browser responses return the `userAgent` they used. Send it back with the cookie. 3. **Expiry.** Clearances have their own lifetimes, set by the target. SiteGround's runs around 30 days for the whole site; a Cloudflare clearance is usually much shorter. Treat a clearance as a cache: when replays start returning challenges again, run one fresh call and take the new one. ##### What It Costs A cleared check changes the price on Browser only: | Engine | Base | Cleared defense | |--------|------|-----------------| | Single | 1 (2 with `unblocker`) | No change | | Proxy | 2 (4 with `unblocker`) | No change | | Browser | 5 | 10 | Browser charges 10 only when the solver was on and a system was genuinely cleared. A system that was recognised and not cleared costs 5, the same as a page with no check on it at all. A check page FourA recognises served with HTTP 200 (for example Amazon's robot check or Reddit's verification page) isn't billed on any endpoint; the response names it in `X-FourA-Check-Page`. ##### Pair It With `validate` `defense` tells you a check was met. `validate` tells FourA what the real page looks like, which is what lets a request fail rather than hand you an interstitial that happens to carry HTTP 200. ```json { "method": "GET", "url": "https://example.com/product/42", "validate": { "data": {"accept": ["Add to cart"], "fail": ["Just a moment"]} } } ``` On `POST /api/auto/`, `validate` is what stops the ladder from accepting a challenge page and calling it done. ##### Related - [Protected sites](/docs/guides/protected-sites): Which engine to reach for at each protection level - [API Endpoints](/docs/api/endpoints): Request and response reference for all four endpoints - [Reuse a Proxy Across Requests](/docs/how-to/reuse-a-proxy): Pin the exit a clearance is bound to - [Smart Fetch (Auto)](/docs/guides/smart-fetch): How `meta.solved` fits into the ladder - [Response Headers](/docs/api/response-headers): Where the credit cost of a call shows up --- #### Proxy Port URL: https://foura.ai/docs/api/proxy-port Send your own traffic through FourA's exits from any client that accepts a proxy URL: a browser, a download tool, a scraper you already run. The proxy port opens a tunnel to the target you name and copies bytes both ways, so it carries what the JSON API doesn't: streams, large downloads, and whole browser sessions. ##### Connecting | Setting | Value | |---------|-------| | Host | `proxy.foura.ai` | | Port | `34004` | | Protocol | HTTP CONNECT with Basic authentication | | Target ports | `443` and `80` | Credentials come from the dashboard. Create a proxy user under **Proxy** in the sidebar and you get a generated username and password. They're separate from your API key on purpose: a proxy handshake travels in the clear on every connection, and a proxy user can be rotated without touching your API integration. See [Proxy Users](/docs/dashboard/proxy). ```bash curl -x http://USERNAME:PASSWORD@proxy.foura.ai:34004 https://example.com ``` ```python import requests proxy = "http://USERNAME:PASSWORD@proxy.foura.ai:34004" r = requests.get("https://example.com", proxies={"https": proxy, "http": proxy}) print(r.status_code) ``` The port speaks CONNECT only. An `https://` target is tunnelled by every client. A plain `http://` target works when your client tunnels it too (curl: `--proxytunnel`); a client that forwards the plain request to the proxy instead gets `405 Method Not Allowed`. ##### Options Ride in the Username Everything after the credential is a list of `-key-value` pairs, in any order: ``` USERNAME-network-premium-country-de-session-a1 ``` Values are lowercase words. A dash inside a value starts a new option, so `-city-new-york` reads as two options and is refused. An unknown key is refused too, never dropped: a misspelt `-contry-de` comes back as a 400 that names the supported keys, rather than being served from the wrong country. | Option | Values | What it does | |--------|--------|--------------| | `network` | `shared` (default, alias `pool`), `premium` (alias `residential`) | Which network the address comes from. Shared is FourA's own exits, included in your plan. Premium is home addresses, drawn from the premium traffic your plan includes plus any you bought. | | `fallback` | `premium` (alias `residential`), `off` | Shared network only. Take a premium exit when the shared network can't carry the request. Off unless asked for, and it only fires when your plan includes premium traffic and allowance remains. | | `country` | ISO 3166-1 alpha-2, repeatable | The exit country the target sees. Repeat it for a multi-country scope: `-country-de-country-fr`. | | `session` | 1 to 32 letters, digits, or underscores | Connections sharing a name share an address (sticky). Omit it for a new address per connection (rotating). | | `state` | a word | Region. Premium only. | | `city` | a word | City. Premium only. | | `asn` | a number | Network, by AS number. Premium only. | | `os` | `windows`, `android`, `ios`, `mac` | The kind of device the address belongs to. Premium only. | | `lifetime` | `3` to `1440` | Minutes a sticky premium address is held. Premium only. | Every option except `network`, `fallback`, and `session` accepts `any`, which clears a value saved on the proxy user for this one connection. Three combinations are refused with a 400 rather than partly honoured: - `state`, `city`, `asn`, `os`, or `lifetime` on the shared network. The shared network is chosen by country and nothing finer; add `-network-premium` to target a city. - `-fallback-premium` together with `-network-premium`. A fallback has nothing to do on the network it falls back to. - Finer targeting with a fallback armed. A fallback may or may not fire, so a city it could only sometimes honour is refused. ###### Sessions On the shared network, a named session keeps the same exit for as long as you keep using it, on a sliding ten-minute window. If that exit stops working, the session moves to another one rather than failing. A browser driven through the port should always carry a session name: a page load opens dozens of connections, and without a session each one leaves from a different address. On the premium network, a named session holds its address for `lifetime` minutes, or the network's default when you don't set one. ###### Saved defaults Targeting saved on a proxy user in the dashboard applies to every connection made with it, so the username can stay short. Anything you put in the username wins for that one connection, and `any` takes a saved value back. A saved premium default spends only when your plan allows it; otherwise the connection is served from the shared network and the tunnel still opens. ###### CONNECT headers A client that can add headers to the CONNECT request may send the same options as headers. A header wins over the same option in the username. | Header | Same as | |--------|---------| | `X-Foura-Network` | `-network-` | | `X-Foura-Fallback` | `-fallback-` (`premium` or `off`) | | `X-Foura-Country` | `-country-`, comma-separated for several | | `X-Foura-Session` | `-session-` | | `X-Foura-State` | `-state-` | | `X-Foura-City` | `-city-` | | `X-Foura-Asn` | `-asn-` | | `X-Foura-Lifetime` | `-lifetime-` | ```bash curl -x http://USERNAME:PASSWORD@proxy.foura.ai:34004 \ --proxy-header "X-Foura-Country: de" \ https://example.com ``` ##### Responses A tunnel that opens answers `200 Connection established`. Every refusal made after your credentials are checked carries an `X-Foura-Error` header with a one-line reason, so read that header before anything else. | Status | When | |--------|------| | 400 Bad Request | A malformed request, or an option FourA can't honour. `X-Foura-Error` spells out the option and the supported values. | | 403 Forbidden | The target port isn't served, the target isn't a public internet host, the port isn't included in your plan, or you named `-network-premium` and your plan has no premium traffic left or doesn't include the premium network. | | 405 Method Not Allowed | A plain request instead of CONNECT. | | 407 Proxy Authentication Required | Missing or wrong credentials. The answer never says which half was wrong. | | 408 Request Timeout | The client connected and sent nothing for 15 seconds. | | 431 Request Header Fields Too Large | The CONNECT request's headers ran past 16 KB. | | 429 Too Many Requests | One of your plan's numbers: tunnels open at once, tunnel openings a minute, or the standard traffic for this billing period. Also when the port itself is at its capacity or opening rate. `X-Foura-Error` says which. | | 502 Bad Gateway | No working exit was found for that target within the selection budget, or FourA's own resolver couldn't look the host up. The two are different messages in `X-Foura-Error`. | | 503 Service Unavailable | The proxy port is switched off. | ##### Limits - Tunnels open at once and tunnel openings a minute come from your plan, and they are counted for the account rather than per proxy user: issuing a second credential does not buy a second allowance. Your numbers are on [Usage & Limits](/docs/dashboard/quota). - Finding an exit takes up to 45 seconds before the port gives up with a 502. A session that already has an exit usually connects in well under a second. - A tunnel with no bytes in either direction is closed after two idle minutes. - When the port is updated, tunnels that are already open get just under three minutes to finish and are then closed, while new connections keep opening. A long download or stream should reconnect when its tunnel closes. - A proxy user you disable or delete in the dashboard stops authenticating within a minute. ##### Metering Bytes through the port count toward the bandwidth on your [Usage & Limits](/docs/dashboard/quota) page, and bytes through a premium exit count toward your premium traffic as well: the same bytes, reported twice, never added together. When a tunnel opens, the traffic of premium exits the port tried but didn't use counts as premium traffic too. Unused shared-network tries aren't counted, and nothing is counted when no tunnel opens. The standard traffic the port may use is your plan's. On a plan sold by credits the port has its own included gigabytes, and the Billing page sells more of them; on a plan sold by traffic it draws on the same pool as the API. When that runs out, new connections are refused until the period resets or you add more, and the usage mails say when. Once the premium allowance is spent, the three ways onto that network are answered differently, on purpose. A connection that names `-network-premium` is refused with a 403 saying so, because a caller who asked to spend and was quietly served from somewhere else would keep asking and reconcile a premium line that never appears. A fallback is not taken, and a premium default saved on the proxy user is downgraded to the shared network, which still opens the tunnel: neither of those chose to spend on this connection. ##### Related - [Proxy Users](/docs/dashboard/proxy): Create proxy users and build a connection string in the dashboard - [Proxy Request](/docs/api/endpoints#proxy-request): The JSON API alternative, with retries and validation built in - [Reuse a Proxy Across Requests](/docs/how-to/reuse-a-proxy): Keeping one exit on the JSON API - [Usage & Limits](/docs/dashboard/quota): Bandwidth and premium traffic against your plan --- ### Dashboard #### Dashboard Overview URL: https://foura.ai/docs/dashboard/overview The FourA Dashboard is where you manage API keys and proxy users, run test requests, monitor performance, handle billing, and configure your account. This page walks through the main Overview screen and the sidebar sections. ##### Accessing the Dashboard Sign in at [foura.ai/dashboard](https://foura.ai/dashboard). You need a FourA account: [sign up here](https://foura.ai) if you don't have one. ##### Two Products, One Set of Pages Overview, Metrics, and Activity each open on a **Product** switch with two choices: | Product | What it counts | Unit | |---------|----------------|------| | API | The requests you send to `/api/single`, `/api/proxy`, `/api/browser`, and `/api/auto` | Requests and credits | | Proxy | The tunnels your own tools open through `proxy.foura.ai` | Tunnels and bytes | A tunnel carries whatever your client sends through it, and what happens inside a `CONNECT` is your own encrypted traffic. So the Proxy views count tunnels and bytes and never requests or credits. Read [Proxy Port](/docs/api/proxy-port) for the product itself. The switch is always on one of the two, it's remembered between visits, and it's in the address as `#overview/proxy`, `#metrics/proxy`, or `#activity/proxy`. Paste one of those links and the page opens on that product. Don't confuse the **Proxy** product with the **Proxy Finder** endpoint chip. Proxy Finder is `POST /api/proxy/`, one of the API's four endpoints. The Proxy product is the proxy port. ##### Overview Page, API Product The landing page after sign-in shows your headline numbers for the period you pick. ###### Before Your First Request Until your first request arrives, the API view shows sample data, with a **Sample** tag on each panel and chart, under a **This is sample data.** banner with two steps: **Create API key**, whose new key opens with a test command, and **Open Playground** to send a request. On a phone or tablet the steps point you to the Playground, because a phone can't run the command. Your first real request replaces the sample data for good. The Proxy view has its own **No traffic yet.** banner with **Create proxy user** and **Open Proxy**, shown until one of your proxy users carries traffic. Closing either banner hides both. ###### Summary Cards Six panels across the top: | Panel | What it shows | |------|---------------| | Concurrency | Requests running at the same time right now, plus the average and max for the period | | Requests | Total calls, then Single, Proxy, Browser, and Auto, each with its share of the total underneath. An Auto call counts once, under Auto. | | Auto attempts | Always shown, with zeros when Auto ran nothing in the period. The Single, Proxy, and Browser requests Auto made behind your Auto calls, with the attempts per Auto call. They never count as requests or in the success rate, and their credits are inside Auto's credits. They do count against your Single, Proxy, and Browser rate and concurrency limits. | | Credits | Billed credits as the value, spent credits underneath, for the total and for each product, Auto included | | Bytes | Data transferred: In is what you sent, Out is what came back, and Premium is the part of that traffic that went through a premium exit, whether or not that exit delivered the page. Premium sits inside the two totals and is never added to them. | | Response Time | Average, min, and max across the period | **Billed and spent are different numbers.** Only a successful request is billed, so billed is what you pay for. Spent is every outcome, because a failed request still consumes a proxy or a browser. Reading one under the other's name is how the same account ends up looking like two different accounts on two pages. See [Request Outcomes](/docs/api/outcomes) for which outcomes bill. The Requests and Credits panels label the four endpoints Single, Proxy, Browser, and Auto. The **Endpoints** filter row above them calls Proxy **Proxy Finder**, which is the same thing: `POST /api/proxy/`. The four don't always add up to the total. A small number of calls carry none of them, and they're counted in the total and in no endpoint. ###### Timeline Charts Below the panels, six timeline charts plot request volume, credit spend, outcome breakdown, response time, bandwidth, and concurrency. Every chart has a toggle in its header that swaps the plot for the underlying table. ##### Overview Page, Proxy Product Switch **Product** to Proxy and the same page answers in tunnels. Four panels: | Panel | What it shows | |------|---------------| | Tunnels at Once | Tunnels open at the same moment. Current covers the last few seconds and refreshes every five; Avg and Max cover the period. Your plan limits this across all your proxy users together. | | Tunnels | Success and Failed, each with its share of the total. Failed is every other outcome, and the Outcome row says which. | | Traffic | In is what your tools sent, Out is what came back, and Premium is the part that left through the premium network, counted inside the totals rather than added to them. | | Setup Time | Avg, Min, and Max wait before a tunnel was handed over, on successful tunnels only. It's the wait before your first byte, not the speed of the site behind the tunnel. | Five timeline charts sit below them: Tunnel Timeline, Outcome Timeline, Setup Time Timeline, Traffic Timeline, and Tunnels at Once Timeline. Each one takes the same chart or table toggle as the API charts. Three panels dim while an Outcome pill is on, and the reason is in each case that the number doesn't exist for that outcome. Tunnels at Once dims under any outcome. Setup Time and Traffic dim under a failure outcome and carry a **Success only** badge, because a failed tunnel was never handed over and carried nothing. ###### Period and Detail **Period** is how far back you're looking. Eight pills plus Custom: | Pill | Window | |------|--------| | 30M | Last 30 minutes | | 3H | Last 3 hours | | 12H | Last 12 hours | | 24H | Last 24 hours | | 7D | Last 7 days | | 30D | Last 30 days | | 90D | Last 90 days | | 1Y | Last 12 months | **Custom** opens a dialog with four presets, This month, Last month, Last 6 months, and All time, plus a From and To date pair. A date range runs to the end of the day you picked, never past this moment. **Detail** is how wide each bar is: per minute, every 5 minutes, every 30 minutes, hourly, every 6 hours, daily, weekly, or monthly. It defaults to **Auto**, which picks the coarsest step that still puts at least fifteen bars on the chart, so a year reads as a shape instead of 365 hairs. The Auto option names the step it chose. Period and Detail apply to both products. Detail steps that reach further back than FourA keeps that resolution are greyed out rather than hidden: | Resolution | How far back it goes | |-----------|---------------------| | Per-request | 7 days | | 5-minute | 30 days | | Hourly | 12 months | | Daily | The whole history | Pick a period older than a level allows and the finer options grey out. Auto never picks one of them, so a chart is never empty because the data was aggregated away. ###### Filters Filters stack and combine, and Period and Detail sit alongside them. | Filter | Product | What it does | |--------|---------|--------------| | Product | both | API or Proxy | | Endpoints | API | Scope to one endpoint: Single, Proxy Finder, Browser, or Auto. An Auto call counts once, under Auto; the Single, Proxy and Browser requests its ladder made behind it are shown by name in the Auto attempts panel, never as requests of their own. | | Outcome | both | Isolate one category. The API offers all seven; the Proxy product offers the five a tunnel can end in. | | Owner | both | Everything, Personal, or one of your organizations. Appears only when you belong to at least one. | | Key | API | Restrict to a single API key | | Proxy user | Proxy | Restrict to a single proxy user | The Concurrency panel and chart on the API side don't take the Endpoints or Outcome filter. Both dim while either is on, and the chart says so in its header. ###### The Owner Filter The **Owner** dropdown answers "whose traffic am I looking at". It offers **Everything**, **Personal**, and one entry per organization you belong to, and the same control on Metrics, Activity, API Keys, and Proxy follows your choice, so the pages never disagree. Usage & Limits and Billing deliberately don't take it. They answer "what bills to me", which for you is your personal keys and proxy users plus those of organizations you own. See [Organizations](/docs/dashboard/teams-and-orgs). ##### Sidebar Sections Eleven pages and a **Docs** link, in four groups: Dashboard, Tools, Access, and Account. ###### Dashboard: Overview, Metrics, Activity The three pages that answer "what happened". All three carry the Product switch. **Overview** is the page above, and always the starting point after sign-in. **Metrics** holds the Usage panel: a breakdown grouped by API key, client IP, or domain on the API side, and by proxy user, target, exit country, client IP, or outcome on the Proxy side, with cards for the whole period above a paged table. See the [Metrics guide](/docs/dashboard/metrics). **Activity** is a live feed of the last hour. On the API side each row is one request: method, target domain, status, outcome, duration, transfer size, and credits. On the Proxy side each row is one tunnel: proxy user, target, exit, outcome, setup, duration, bytes, and client IP. See the [Activity Log](/docs/dashboard/activity-log). ###### Tools: Playground, Extension, Docs Test live requests against your real API key without writing any code. Pick Auto, Single, Proxy, or Browser, set a URL, tweak flags and timeouts, and hit Send. Save common configurations as presets, replay any of your last 20 runs from History. Usage counts against the key's quota, the same way a production call would. See the [Playground guide](/docs/dashboard/playground). **Extension**, marked **Beta**, is where you download the FourA Chrome extension. Its popup sends Chrome through one of your proxy users, and its FourA tab in DevTools turns any request a page makes into a ready API call. See [Chrome Extension](/docs/dashboard/chrome-extension). **Docs** opens this documentation in a new tab, so the dashboard page you're on stays put. The theme button at the top of the sidebar cycles between your device's setting, light, and dark. The choice is shared with foura.ai and the docs, so a theme picked on any of them carries over to the dashboard. ###### Access: API Keys, Proxy **API Keys** is where you create, view, and manage the keys your applications use to call the API. Keys use the `pk_live_` prefix. See [Managing API Keys](/docs/dashboard/api-keys). **Proxy** is where you create proxy users and copy a connection string for any client that accepts a proxy URL. Tunnels and traffic through the port count against your plan, the same as the API. See [Proxy Users](/docs/dashboard/proxy) and the [Proxy Port](/docs/api/proxy-port) reference. ###### Account: Organizations, Usage & Limits, Billing, Settings **Organizations** share API keys and proxy users with colleagues under one owner. Add people by email, set their role, and hand the organization over when it needs to change hands. Usage on an organization's credential counts against the organization owner's plan. See [Organizations](/docs/dashboard/teams-and-orgs). **Usage & Limits** shows your plan, what you've used this billing period, and every limit you're measured against, with a per-key, per-endpoint, and per-proxy-user breakdown. See [Usage & Limits](/docs/dashboard/quota). **Billing** manages your subscription, buys more credits or traffic, keeps payment methods and invoices, and shows this period's credits, traffic, and premium traffic against your plan. See [Billing & Subscriptions](/docs/dashboard/billing). **Settings** holds your display name, password, active sessions, and connected sign-in providers. Email changes aren't available in the dashboard: contact support. ##### Tips - Use the **Playground** before writing code. If you don't know which engine a new site needs, start with Auto and let it pick. - Compare billed against spent on the Credits card. An endpoint where the two are far apart is failing often, and the [attempt report](/docs/troubleshooting/proxy-attempt-report) says why. - On the Proxy product, a block of Rate Limited in the Outcome Timeline is your plan's number, not the target refusing you. The Outcome view on [Metrics](/docs/dashboard/metrics) names which one. - Keep your API keys and proxy users secure. Disable either immediately if you suspect it's been exposed. ##### Next Steps - [Authentication](/docs/getting-started/authentication): API key creation and management details - [Quick Start](/docs/getting-started/quick-start): Make your first request from the CLI - [How Your Plan Is Metered](/docs/guides/plans-and-metering): Credits, traffic, and which ceiling applies to you - [Usage & Limits](/docs/dashboard/quota): Track credits and traffic against your plan - [API Endpoints](/docs/api/endpoints): Use the API directly --- #### Creating a Task URL: https://foura.ai/docs/dashboard/creating-a-task This guide walks you through creating an API key and sending your first request using the FourA Dashboard. ##### Step 1: Open the Dashboard Navigate to the [Dashboard](https://foura.ai/dashboard) and sign in with your FourA account. ##### Step 2: Create an API Key Go to the **API Keys** page and click **Create API key**. Once you have a key, the button above the list reads **Create Key**. Give the key a descriptive name (e.g., "production-scraper" or "dev-testing"). The **API Key Created** dialog shows the plain key with a copy button, and under **Test your first request** a ready-to-run curl command with **Copy command**. On a phone or tablet, **Try it in the Playground** takes you there instead. If you miss it, click the eye icon on the key's row later to reveal it again. Legacy keys created before reveal shipped can't be brought back; regenerate them once to switch over. Full flow: [Managing API Keys](/docs/dashboard/api-keys). Your key looks like this: `pk_live_a1b2c3d4e5f6...` ##### Step 3: Choose an Endpoint FourA has four endpoints for different scenarios: ###### Auto (`POST /api/auto/`) The smart default. Pass a URL, ideally with a `validate` rule, and FourA walks a ladder from a rotating proxy to a full browser through a proxy, stopping at the first answer that passes. Set `forceProxy: false` to let it try a direct request first. Best when you're targeting a new site and don't yet know what it needs. ###### Single (`POST /api/single/`) Sends a fast HTTP request. Best for static HTML pages and API endpoints. Response time: typically under 2 seconds. ###### Browser (`POST /api/browser/`) Runs a Chrome browser instance to render JavaScript. Best for SPAs, lazy-loaded content, and pages that require JS execution. Response time: 2 to 10 seconds. ###### Proxy (`POST /api/proxy/`) Routes the request through rotating proxies with automatic retry. Best for sites that check who is asking, or that answer differently by country. ##### Step 4: Send Your First Request Open a terminal and run: ```bash curl -X POST https://eu.api.foura.ai/api/single/ \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"method": "GET", "url": "https://example.com"}' ``` ##### Step 5: Read the Response ```json { "status": 200, "headers": [ { "result": { "version": "HTTP/2", "code": 200, "reason": "" }, "content-type": "text/html; charset=UTF-8", "content-length": "1256" } ], "data": "...", "total_time": 0.45 } ``` The `headers` field is an array of objects, one per redirect hop. Each entry has a `result` with the status line plus every response header the target returned. Key fields: - **status**: HTTP status code from the target site - **data**: the response body (HTML, JSON, or raw text) - **total_time**: request duration in seconds ##### Step 6: Try a Browser Request If the target page uses JavaScript to render content, use the browser endpoint instead: ```bash curl -X POST https://eu.api.foura.ai/api/browser/ \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/products", "timeout_ms": 15000}' ``` The browser response uses `body` instead of `data`: ```json { "status": 200, "headers": {"content-type": "text/html"}, "body": "..." } ``` ##### Common Issues | Problem | Solution | |---------|----------| | Not sure which engine to use | Start with the auto endpoint. It picks the right path and remembers what worked. | | Empty content | Switch from single to browser endpoint: the page likely needs JS rendering | | Verification page in response | Switch to the proxy endpoint for automatic IP rotation | | Timeout | Increase `timeout_ms` or verify the URL is correct | | Lost your key | Click the eye icon on the key's row in **API Keys** to reveal it, or regenerate if the key is legacy | ##### Next Steps - [Dashboard Overview](/docs/dashboard/overview): Full dashboard walkthrough - [Playground](/docs/dashboard/playground): Test requests in the dashboard before writing code - [Smart Fetch (Auto)](/docs/guides/smart-fetch): Deep dive on the auto endpoint - [Choosing the Right Endpoint](/docs/guides/choosing-task-type): Detailed comparison - [Common Issues](/docs/troubleshooting/common-issues): Solve problems quickly --- #### Managing API Keys URL: https://foura.ai/docs/dashboard/api-keys The API Keys page lets you create, view, and manage the keys your applications use to call the FourA API. ##### Viewing Your Keys The page shows every key you have access to: your personal keys, and every key owned by an organization you belong to. Membership is what grants access, so a plain member sees the organization's keys too. | Column | What it shows | |--------|---------------| | Name | Display name and, if you set one, the description | | Owner | `Personal`, or the organization's name | | Key Prefix | Masked prefix (`pk_live_••••••••`) with a Reveal button, or a lock icon on a legacy key | | Status | Active or Inactive | | Created | When the key was created | | Last Used | When the key last made a request | Search by name or description, filter to Active or Inactive, and sort by any header. Sort direction cycles descending, ascending, then off. ###### The Owner Filter If you belong to at least one organization, an **Owner** dropdown appears next to the search box: **Everything**, **Personal**, or one entry per organization. It narrows the list to keys with that owner, and the same control on Overview, Metrics, Activity, and Proxy follows your choice, so the five pages always agree about whose traffic you're looking at. ##### Creating a Key Click **Create Key** above the list (**Create API key** on the empty page before your first key) and fill in: | Field | Required | Description | |-------|----------|-------------| | Name | Yes | A descriptive label, for example "production-scraper" or "staging-test" | | Description | No | Notes about what this key is used for | | Owner | Yes if you're in an organization | Personal, or any organization you belong to. The picker only appears when you have at least one. | Any member of an organization can create a key it owns, whatever their role. That's the point of the setting: usage on an organization key bills the organization's owner, so a developer doesn't need a personal key that quietly bills them instead of the company. The full key is shown once right after creation in a copy-ready dialog. You can bring it back later with **Reveal**. Keys use the `pk_live_` prefix and look like `pk_live_a1b2c3d4e5f6...`. You'll get an email confirming the key was created. Your plan sets how many active keys you can hold: your personal keys plus the keys of organizations you own. A key created for an organization counts against its owner's plan. At the limit, creating a key is refused with a message that names your plan's number, so disable or delete a key you no longer use, or upgrade. ##### Revealing a Key Click the eye icon next to a key's masked prefix to see its full secret again. The dashboard asks you to confirm, then opens a dialog with the plain key, a copy button, and a short countdown. After the countdown, the dialog wipes the secret from the page. Every reveal is recorded in the audit log, because it exposes a live credential. Anyone the key is shared with can reveal it: its personal owner, or any member of the organization that owns it. ###### Legacy Keys Keys created before the reveal feature shipped show a lock icon instead of the eye and can't be revealed. Only their hash was stored, so the original secret is unrecoverable. To switch a legacy key over, open the actions menu and choose **Regenerate**. The new secret is revealable from then on. ##### What Each Role May Do On a personal key you can do everything. On an organization key, three levels apply: | Action | Member | Admin | Owner | |--------|--------|-------|-------| | Use the key and reveal it | Yes | Yes | Yes | | Rename it, edit the description | Yes | Yes | Yes | | Enable or disable it | No | Yes | Yes | | Regenerate the secret | No | Yes | Yes | | Delete it | No | Yes | Yes | | Move it out of the organization | No | No | Yes | Disable and Edit stay on the row but are greyed out with a note saying why. Regenerate, Transfer, and Delete don't appear in the menu when your role can't use them. The API refuses the same actions, so an integration can't route around the interface. The split follows what each action costs the people sharing the key. Renaming is harmless. Regenerating or disabling stops every colleague's integration the instant it happens. Moving a key out takes it away from the company and puts its usage on the taker's own plan, so that one is the owner's alone. ##### Editing a Key Click **Edit** to update the name and description. Ownership isn't changed on this dialog: use **Transfer**. **View Metrics** in the actions menu opens Overview filtered to that key. ##### Activating and Deactivating Use **Disable** or **Enable** on the row. Deactivating a key blocks every API request that uses it, and you can reactivate it at any time. The change takes effect within a minute. ##### Regenerating a Key If you suspect a key has been exposed, open the actions menu and choose **Regenerate**. This creates a new secret for the same key ID. The old secret stops working within a minute. Name, owner, and every other setting stay the same. The new secret stays revealable after the dialog closes, so you don't have to copy it perfectly on the first try. ##### Transferring a Key **Transfer** in the actions menu moves a key between personal and organization ownership without regenerating the secret. The key ID, its metrics, and its activity history are preserved. Only the owner changes, and with it who the usage bills to. | Move | Who can do it | |------|---------------| | Personal key into an organization | Anyone who belongs to that organization | | Organization key out, to yourself | The organization's owner | | Between two organizations | The owner of the organization the key is leaving | A key that moves into an organization joins its default "Everyone" team, which is what makes it visible to the members. A key that moves out leaves every team it was in. ##### Deleting a Key Open the actions menu and choose **Delete**. Deleting a key you've used before disables it: the key stops authenticating and stays on the list as **Inactive**, so its Activity and Metrics history keep its name. Keys that have never been used are removed outright. You'll get an email when a key is deleted. ##### Organization Keys and Billing Usage on an organization-owned key counts against the **organization owner's** plan, whoever fired the request. The owner's [Usage & Limits](/docs/dashboard/quota) page counts those keys alongside their personal ones and splits the two. ##### Related - [Authentication](/docs/getting-started/authentication): How API keys work with the API - [Organizations](/docs/dashboard/teams-and-orgs): Roles, members, and shared access - [Usage & Limits](/docs/dashboard/quota): Which keys count against your plan - [Dashboard Overview](/docs/dashboard/overview): All dashboard sections --- #### Metrics URL: https://foura.ai/docs/dashboard/metrics The Metrics page (sidebar > Metrics) gives you a deeper analytical view of your usage. It holds one panel, Usage, which breaks your traffic into groups and puts the totals for the whole period above a page of rows. For summary cards and timeline charts, use the [Dashboard Overview](/docs/dashboard/overview) page. ##### Product The page opens on a **Product** switch: **API** for the requests you send to the API endpoints, **Proxy** for the tunnels your own tools open through `proxy.foura.ai`. Each product has its own Usage panel with its own views, scopes, and columns, because a tunnel has no request count and no credits. The switch is remembered and deep-linkable as `#metrics/proxy`. ##### Filters Above the panel: Product, Endpoints (API only), Owner, then Key (API) or Proxy user (Proxy), then Period. Owner and Period are the same controls as on Overview and they share their state, so switching between the two pages never changes what you're looking at. ###### Owner Filter **Everything**, **Personal**, or one of your organizations. It only appears once you belong to at least one. Picking an owner also rebuilds the Key and Proxy user dropdowns, so a credential from the previous owner can't survive the switch and silently return nothing. When the owner you picked has no API keys at all, stopped keys included, the line under the period says **You have no API keys here yet. Create one to see its usage.** The panels then show zeros because there's nothing to count. See [Managing API Keys](/docs/dashboard/api-keys). ###### Key and Proxy User Filters Scope the page to a single credential. Only credentials you have access to appear: your own, plus those of organizations you belong to. ###### Period Eight pills, 30M, 3H, 12H, 24H, 7D, 30D, 90D, and 1Y, plus **Custom** for a calendar month, the last 6 months, all time, or an exact pair of dates. ###### Endpoints On the API product, pick Single, Proxy Finder, Browser, or Auto to scope the cards and the table to one endpoint. An Auto call counts once, under Auto. The Concurrency scope isn't available while an endpoint is picked. ##### The Usage Panel The panel answers two questions at once. **Cards above the table** describe the whole period: how many groups it holds and the totals across all of them. **The table below** is one page of those groups, sorted by the column you pick. The two are never about different traffic: the cards run the same window and the same filters as the rows. The group count is written out in full rather than rounded, because an exact number is an answer to "how many domains did we reach this month" and a rounded one isn't. Changing the view, the scope, or the period starts the set again from the first page. Page 4 of domains is not page 4 of client IPs. Click a column header to sort. On the API product's Domain and Client IP views, the sort runs over the whole list before it's cut into pages, so the top row really is the largest, and the table starts again from the first page. The API Key view holds every key on one list and sorts it in place. Sorting goes back to the scope's own order when you switch view or scope, or reload the page: the most requests first, or under Routes the most browser attempts by Auto. Ctrl or Cmd click a header to reset. The first column's header also holds a filter box (**Filter API Keys...**, **Filter IPs...**, or **Filter Domains...**) that narrows the rows by name. On the Client IP and Domain views it searches only the page you're on, so a group on another page won't show up. Esc clears it. ##### Usage, API Product Three views: | View | Groups data by | |-----|----------------| | API Key | Each key you have access to | | Client IP | Source addresses making the requests | | Domain | Target domains in your requests | Scope chips change the columns and the cards: | Scope | What it shows | |-------|---------------| | Bandwidth | Request count, bytes in, bytes out, and the premium share of those bytes (traffic served through a premium exit, inside the totals rather than added to them) | | Response Time | Request count, min, average, max latency | | Concurrency | Request count plus concurrent request counts (API Key view only) | | Outcomes | Request count plus a per-outcome breakdown, with the outcome donut | | Budget | Request count, billed credits, and spent credits. To see one endpoint, pick it in the Endpoints row | | Routes | Request count, your requests per route, what Auto ran behind its calls, and the step that answered each Auto call (see below) | The Budget scope keeps billed and spent credits apart. **Billed credits** count only requests with the `success` outcome: that's what your plan pays for. **Spent credits** are the credits the API reports on every request (see [Response Headers](/docs/api/response-headers)), failed requests included, and they're never billed. The Budget cards show **Billed credits** with **of N spent** under it, and **Per request**, the billed credits per request on average. The table has a **Billed credits** and a **Spent credits** column, and you can sort by either. ###### Routes Scope Routes splits the requests of each key, client IP, or domain by route and shows what Auto did for its calls. Its columns come in three groups, each with a caption above it: | Group | Columns | What they count | |-------|---------|-----------------| | Requests by route | Single, Proxy Finder, Browser, Auto | Your requests, by the route you sent them to. Together they make Requests. An Auto call counts once, under Auto. Under each number: its share of Requests. | | Auto attempts | Single, Proxy Finder, Browser | The requests Auto ran on each engine behind its calls, failed ones included. Under each number: attempts per Auto call. | | Auto answered by | Saved session, Plain request, Proxy Finder, Browser, Failed | The step that delivered each Auto call. Together they make Auto. Under each number: its share of Auto. | Saved session is a session Auto already had for the host. Browser includes the calls where a browser opened the page first and a replay of its session delivered it. Failed includes calls refused before Auto ran. To see where Auto uses a real browser, read the Browser column under **Auto attempts**. A browser usually opens a page once, and Auto then reuses that session with lighter requests, so this column is usually far larger than Browser under **Auto answered by**. Routes opens sorted by it, so the domains that need a browser most come first; groups with no browser attempts follow by request count. The cards above the table show the Auto attempts in the period with the attempts per Auto call, and every browser opened, directly or by Auto. Auto attempts never count in Requests or the success rate. They do count against the Single, Proxy Finder, and Browser rate and concurrency limits of your plan, as on the Overview's Auto attempts card. With an endpoint picked in the Endpoints row, Routes shows that route only: Browser then lists the browser requests you sent yourself, and the ones Auto ran stay under Auto. ###### Outcome Donut Under the **Outcomes** scope, a donut sits inside the panel and shows the success and error breakdown for the selected period. Hover any slice for the exact request count and percentage. It's drawn only under that scope, since a picture of the whole window on top of a table answering a different question reads as two unrelated things. ##### Usage, Proxy Product Five views: | View | Groups data by | |-----|----------------| | Proxy user | Each proxy user you have access to | | Target | The host each tunnel reached | | Exit country | The country the tunnel left from | | Client IP | The address the connection came from | | Outcome | Outcome and its reason | Four scopes: | Scope | Columns | |-------|---------| | Traffic | Tunnels, Bytes In, Bytes Out, Premium | | Setup | Tunnels, Setup Min, Setup Avg, Setup Max, Duration Avg, Duration Max | | Outcomes | Tunnels plus one column per outcome, with the outcome donut | | Budget | Tunnels, Standard, Premium, Total | Setup is the wait before a tunnel was handed over. Duration is how long it then stayed open. Premium is the part of the traffic that left through the premium network, counted inside the total and never added to it. No scope has a credits column or a request count. What travels inside a `CONNECT` is your own encrypted traffic, so the port counts tunnels and bytes and nothing else. ##### Outcome Types Every API request is classified into exactly one outcome. Only `success` is billed. | Outcome | Layer | Meaning | |---------|-------|---------| | `success` | n/a | The request delivered a valid response. With no `validate` rules, that means HTTP 200. With `validate` rules, any response your rules accepted, whatever the status. | | `application_error` | target | The target returned HTTP 200, but the body carried an error field. | | `application_fail` | target | The target returned a non-2xx your `validate` rules didn't accept, or no response at all. | | `client_error` | caller | Your request was rejected before it left FourA: bad parameters, a malformed proxy value, or a URL that resolves to a private or reserved address. | | `rate_limit` | FourA | The request was rejected by your RPM or concurrency limit. See [Rate Limits](/docs/api/rate-limits). | | `service_error` | FourA | The engine answered with a server error, or with a body we couldn't parse. | | `service_fail` | FourA | A network failure: timeout, connection refused, DNS error, client disconnect. | The layer column tells you who's responsible. `target` means the site you called, `caller` means your request was bad, `FourA` means we couldn't process it. If you use `validate.status.accept` to allow specific non-200 codes (for example `[200, 403]`), those come back as `success` instead of `application_fail`. The classification follows the engine's verdict on your rules, not the raw HTTP code. A tunnel through the proxy port is classified into the same vocabulary, minus the two `target` outcomes: a tunnel's answer is your own encrypted traffic, so FourA never sees whether the site behind it was happy. For the full taxonomy and how it maps to billing, see [Request Outcomes](/docs/api/outcomes). ##### Related - [Dashboard Overview](/docs/dashboard/overview): Summary cards, timeline charts, Period and Detail (Detail is on Overview only) - [Request Outcomes](/docs/api/outcomes): The outcome values explained in detail, for requests and tunnels - [Response Headers](/docs/api/response-headers): Where credits come from - [Proxy Port](/docs/api/proxy-port): The product the Proxy views report on - [Organizations](/docs/dashboard/teams-and-orgs): What the Owner filter covers - [API Errors](/docs/api/errors): How errors come back over the wire --- #### Activity Log URL: https://foura.ai/docs/dashboard/activity-log The Activity Log shows your most recent traffic. It loads when you open the page; click the refresh button beside the title to fetch the newest rows. Use it to debug individual calls, check response codes, and verify that a new integration is working. ##### Product The page opens on a **Product** switch: **API** for the requests you send to the API endpoints, **Proxy** for the tunnels your own tools open through `proxy.foura.ai`. Both feeds cover the last hour, and both take the same limit selector. The switch is remembered and deep-linkable as `#activity/proxy`. The rest of this page describes the API feed first, then the Proxy feed. ##### What You'll See on the API Feed The log displays requests made within the last hour, with the most recent at the top. Nine columns carry the whole row: | Column | What it shows | |-------|-------------| | Time | When the request was made | | Key | Which API key was used | | Request | The call itself: a colored endpoint mark (Single, Proxy, Browser, or Auto), the HTTP method, and the target domain. Sorts by domain. | | Status | Two values in one cell, separated by a slash: the HTTP status of your call to FourA, then the status the target returned. A dash means the target never answered. | | Outcome | Request classification (Success, Client Error, Rate Limited, and so on) | | Duration | Total response time in milliseconds | | Bytes | Transfer both ways in one cell: down arrow for the request payload, up arrow for the response. A row that used premium traffic carries a **premium** mark on this cell, including one where a premium exit was tried and the standard pool answered; that traffic counts toward your premium traffic. | | Credits | Credits spent on the call (see [Response Headers](/docs/api/response-headers)) | | Client IP | The address that made the request. Requests sent from the Playground or FourA's hosted MCP show **FourA** instead of an address. | Click any column header to sort the table by that column; direction cycles descending, ascending, then off. Ctrl or Cmd click resets to the default (newest first). ##### Filtering Four controls narrow the log. ###### By Owner If you belong to at least one organization, an **Owner** dropdown appears first: **Everything**, **Personal**, or one of your organizations. Changing it rebuilds the key dropdown next to it, so a key belonging to the previous owner can't survive the switch and silently return nothing. The same control on Overview, Metrics, and API Keys follows your choice. See [Organizations](/docs/dashboard/teams-and-orgs). ###### By API Key Use the API key dropdown to show requests from one key. Only keys you have access to appear: your personal keys and the keys of organizations you belong to. ###### By Endpoint With Product set to API, filter to one endpoint with the **Single / Proxy Finder / Browser / Auto** chips. Useful when you want to debug browser-only failures separately from single requests. **Auto** lists your Auto calls, one row each. ###### By Limit The activity log defaults to 50 entries. Use the limit selector to change how many entries are shown: | Limit | Notes | |-------|-------| | 10 | Quick scan | | 50 | Default | | 100 | Extended view | | 200 | Maximum | All entries are from the last hour. For historical data, use the [Metrics](/docs/dashboard/metrics) section, which aggregates data over days and weeks. ##### Auto Calls An Auto row is one call. Its second line says how many attempts Auto made and how many succeeded, and its Credits and Bytes are those attempts' totals. For a few seconds after a call ends it reads **attempts settling…**, and the row fills itself in. A call refused before Auto tried anything reads **no attempts recorded**. Open the row to see an **Attempts** table: Engine, Rung, Status, Outcome, Time, Bytes, Credits. Attempts count against your Single, Proxy Finder, and Browser limits, never in your request count or success rate. ##### Opening a Request Click any row to open a detail panel with the full request and response payload preview. The panel opens on a meta grid built from the row itself, so it stays useful even after the stored payload has aged out: time, API key, client IP, target, HTTP status, app status, outcome, **Gateway total** (the whole call as FourA saw it), **Upstream fetch** (the time spent on the attempt that answered), bytes, credits, and the request's `X-FourA-Request-Id`. When they apply, it also shows the proxy chain of a Proxy Finder call (every proxy attempt, retries included), the proxy used, the response body size, and the engine's error. Below the grid, one pane shows at a time, selected by tab: | Tab | What it shows | |-----|---------------| | Request | Pretty-printed JSON of the exact body sent | | Response headers | The headers the target returned, one block per redirect hop. When the target redirected, the tab shows the number of hops. | | Response body | The stored body preview, with truncated and binary badges where they apply | | Other fields | Anything else the engine returned beyond status, timings, proxy, and headers. Hidden when there's nothing extra. | A **Copy** button copies whichever pane is open. Payloads are kept 24 hours, capped at the last 200 per API key. Rows older than that show the row only, without a payload. ###### Body Pane Messages The body pane uses different placeholder text depending on what happened: | Message | What it means | |---------|---------------| | `(no body — the request failed: )` | The request errored before the target returned a body | | `(no body captured for this request)` | Payload aged out or wasn't stored | | `(empty body — the server returned 0 bytes)` | The target returned a real empty response | ##### Open in Playground The detail dialog has an **Open in Playground** button. Click it to load both the archived request and the archived response into the [Playground](/docs/dashboard/playground) form. From there you can tweak parameters and replay against the live API, or just inspect what came back without re-running the request. The button is disabled for Auto calls and for non-replayable payloads (oversized request stubs and non-API routes), with a hint explaining why. ##### The Proxy Feed Switch **Product** to Proxy and the feed becomes one row per tunnel, also from the last hour. A tunnel is one connection your tool opened; what it sent inside is your own encrypted traffic, so the row describes where the connection went, how it left, how long it took to open, and how much it carried. | Column | What it shows | |-------|-------------| | Time | When the tunnel ended, with a relative age beside it | | Proxy user | Which proxy user carried it | | Target | The host and port the tunnel went to | | Exit | Shared or Premium, plus the exit's country when we know it | | Outcome | Success, Client Error, Service Error, Service Fail, or Rate Limited, with the reason in your own terms on a refusal | | Setup | Time from your tool connecting to the tunnel being handed over | | Duration | How long the tunnel stayed open | | Bytes | Both directions in one cell, with a **premium** mark when the exit was on the premium network | | Client IP | The address the connection came from | There are no Status, Credits, or payload columns, and no detail dialog. A `CONNECT` has no status from the target and no body for FourA to store. The **Owner** dropdown and the limit selector work the same way. In place of the key dropdown, a **Proxy user** dropdown scopes the feed to one credential. Every column sorts. A row that reads Rate Limited is your plan's number, not the target refusing you. The reason names which one: tunnels at once, openings per minute, traffic used up, premium not available, or the port not being in your plan. See [Proxy Port](/docs/api/proxy-port#responses). ##### Using the Request ID Every API response carries an `X-Foura-Request-Id` header. Log it on your side, and you can paste it into a support ticket to point at the exact request in the Activity Log. The ID is the same one used in this dialog and matches the `X-Foura-Request-Id` returned by the API. See [Response Headers](/docs/api/response-headers) for details. ##### Related - [Metrics and Analytics](/docs/dashboard/metrics): Aggregated performance data over longer periods, for both products - [Proxy Port](/docs/api/proxy-port): The product the Proxy feed reports on - [Playground](/docs/dashboard/playground): Replay requests from Activity - [Response Headers](/docs/api/response-headers): Where the request ID and credits come from - [API Endpoints](/docs/api/endpoints): Request and response shapes - [Organizations](/docs/dashboard/teams-and-orgs): What the Owner filter covers - [Troubleshooting](/docs/troubleshooting/common-issues): Common problems and solutions --- #### Usage & Limits URL: https://foura.ai/docs/dashboard/quota The Usage & Limits page in the dashboard shows your plan, what you've used this billing period, and every limit you're measured against. ##### Where to Find It Sign in to the [dashboard](https://foura.ai/dashboard) and open **Usage & Limits** in the sidebar. Four tabs: Overview, API keys, Proxy users, and Limits & Features. ##### Overview Tab - **Your plan and billing period**: one card with the plan's name, what it includes in a line (credits a month on a plan sold by credits, gigabytes a month on one sold by traffic), and the exact start and end dates of the window you're in with a bar showing how far through it you are. Paid plans reset on your billing date each month. Annual plans get monthly cycles anchored to activation. The Free plan anchors to your signup date. - **Credits this period**: five stats side by side. Billed, Spent, Included, Remaining, and Overage. Only successful requests are billed and count against your plan. Spent is the total resource use including failed requests, which we never charge for. A usage bar under the stats colors amber at 80% and red at or over the included allowance. The line under it gives your **Hard stop**: your plan's credits plus any you bought, and requests past it are refused. [Automatic top-up](/docs/dashboard/automatic-top-up) can buy more before you reach it. **Overage** reads **Hard stop** on a plan with no overage allowance. The panel isn't there at all on a plan sold by traffic, which doesn't bill credits. See [How Your Plan Is Metered](/docs/guides/plans-and-metering). - **Traffic this period**: four cards, **Total**, **API**, **Proxy port**, and **Premium**, for everything the account moved this billing period, in and out together. Premium is part of the total, never extra to it. Where your plan caps standard traffic, a bar shows your use against that cap, and where it includes premium traffic, a second bar does the same for premium. - **Per-endpoint breakdown**: a card each for Single, Proxy Finder, and Browser shows this period's **Requests** and **Traffic**, **Billed credits** on a plan sold by credits, and live **In flight now** and **Rate last min** (Browser: **Today**) next to your plan's numbers. An endpoint your plan excludes reads **Not in your plan** with a link to upgrade. An **Auto** card shows your Auto calls (Requests, Traffic, Billed credits) and **Attempts** by engine. Auto has no limits of its own; its attempts count against the Single, Proxy Finder, and Browser limits. - **Proxy Port**: a panel under the three endpoint cards, because the port is a different product with a different unit. Four cards: **Tunnels**, **Traffic through the port**, **At once**, and **Openings per minute**. A plan that doesn't include the port reads **Not in your plan** with a link to Billing. Three things are missing from the Proxy Port panel on purpose. There are no credits, because a tunnel has no request to charge one to. There's no live in-flight figure. And there's no request count at all: what happens inside a `CONNECT` is your own encrypted traffic, so a request count here would be a number you could never reconcile against your own logs. Where the traffic ceiling is shared with the API, the panel says **shared with the API** next to it rather than quietly showing the other product's number. ##### API Keys Tab A sortable table of every key that answers to your plan: Key, Status, Billed credits, Spent credits, Requests, Traffic, Premium, Share, and Last used, with a search box. Keys of organizations you own count against your plan too. Deleting a key never deletes its usage history. This page ignores the dashboard's **Owner** filter on purpose. It answers "what bills to me", which is your personal keys and proxy users plus those of organizations you own. A member of somebody else's organization never sees the owner's quota. See [Organizations](/docs/dashboard/teams-and-orgs). ##### Proxy Users Tab What each proxy user moved this billing period. | Column | What it shows | |--------|---------------| | Proxy user | The name, its username underneath, and the owning organization when there is one | | Status | Active or disabled | | Tunnels | Tunnels the port answered for this credential | | Traffic | Standard traffic it carried | | Premium | The part that left through the premium network, or **none** | | Share | This credential's share of your port traffic | | Last used | When it last carried traffic | A credential that moved nothing is still listed. "Issued and unused" is a different fact from "doesn't exist", and it's the one support gets asked about. No column counts requests, for the same reason the Proxy Port panel has none. ##### Limits & Features Tab - **Plan limits**: one row per limit with your current value, what's included, and the status. Status reads **OK**, **Near** (80% or more), **At limit**, or **Over**. At limit is red where your next request is refused (credits, traffic, browser a day, rate, concurrency), and amber on API keys and team members, where only the next one added is stopped. | Row | What it counts | |-----|----------------| | API keys | Active keys you answer for, split into personal and organization | | Team members | The largest organization you own, its owner included. An organization you only belong to counts against its owner, not you. | | Organizations you own | How many you own, and whether your plan covers having one at all | | Credits (this period) | Billed credits against what's available: what the plan includes plus any credits you bought, shown apart (`75,000 (50,000 incl + 25,000 bought)`) | | Standard traffic | The API and the proxy port together, minus premium, against your plan's cap plus bought traffic. Reads **Unlimited** with no cap. | | Premium traffic | Premium-network traffic with its share of all traffic, against the premium traffic your plan includes plus any you bought. Running out reads **spent**, never over: API requests keep working on the standard pool and the response says `exitClass: standard`. On the proxy port, a connection that asks for the premium network is refused with a 403 that says why, and only a saved default moves to the shared network. A plan with none included reads **top-up only**. | | Per endpoint | Requests per minute, concurrency, and browser requests per day for Single, Proxy Finder, and Browser, each with its live counter next to the plan's number. An endpoint your plan excludes reads **Not in plan**. | - **Plan features**: the capabilities included in your plan, such as geo targeting (`exitCountries`) and premium exits (`exitClass`). Features not in your plan show as excluded so you can see what an upgrade would add. The numbers in this tab are the ones the API holds you to. A refusal from any of them arrives with an `X-FourA-Limit` header naming the row: see [Rate Limits](/docs/api/rate-limits). ##### Freshness Numbers on this page come from the billing pipeline, and the page refreshes them every 30 seconds while it's open. If a request finished a moment ago, give the page one refresh to catch up. Live counters for concurrency, per-minute rate, and browser-per-day sit next to your caps and update in near real time. ##### Related - [Billing & Subscriptions](/docs/dashboard/billing): Manage your plan, buy more credits or traffic, payment methods, and invoices - [Automatic Top-Up](/docs/dashboard/automatic-top-up): Buy more of an allowance before it runs out - [How Your Plan Is Metered](/docs/guides/plans-and-metering): Credits, traffic, and which ceiling applies to you - [Proxy Port](/docs/api/proxy-port): Tunnels at once, openings a minute, and what a refusal looks like - [Metrics](/docs/dashboard/metrics): Timelines, outcome breakdown, and per-key charts - [Activity Log](/docs/dashboard/activity-log): The last 200 requests per key with payloads - [Organizations](/docs/dashboard/teams-and-orgs): Members, roles, and who the usage bills to - [Rate Limits](/docs/api/rate-limits): What triggers rate-limit responses --- #### Billing & Subscriptions URL: https://foura.ai/docs/dashboard/billing The Billing section is where you manage your subscription, buy more of what your plan meters, and keep payment methods, invoices, and standing invoice details in order. Open it from the **Billing** link in the dashboard sidebar. The page has four tabs: Plan & Usage, Payment Methods, Invoices, and Billing details. ##### Plan & Usage ###### Current plan The first card names the plan you're on and its status (**Active**, **Trial**, **Payment processing**, **Payment failed**, **Payment failed, update card**, **Unpaid**, **Cancelled**, **Paused**, or **Expired**), with the renewal date underneath. A plan an operator placed with a start date in the future reads **Starts** with that date instead of **Renews**. The buttons on the card depend on where you are: | Button | When it appears | |--------|-----------------| | Change plan | You're on an active plan. Opens the plan picker, with proration. | | Cancel | You're on an active plan. | | Resume | You cancelled, and the period hasn't ended yet. | | Confirm *plan* | FourA placed you on a paid plan you haven't paid for yet. Opens checkout so you pick how to pay. | | Start *plan* | FourA offered you a plan that carries no charge, such as a trial. Starts it without a card. | | Your custom plan: *plan* | A custom plan is assigned to your account and you're not on it yet. | ###### Usage cards Cards below the plan, one per thing your plan meters. Each shows what you've used this billing period against what's available to you, which is what the plan includes plus anything you bought on top, and every card that sells something keeps its **Buy** button. If your plan doesn't sell that top-up, the button opens a short note saying so, with **Contact us**. Where it's on sale, the price per 1M credits or per GB sits beside the button. Which cards you see depends on how your plan is sold. See [How Your Plan Is Metered](/docs/guides/plans-and-metering) for the whole model. Both kinds of plan show the same four cards: - **Credits used this period**: billed credits, successful requests only, with **Buy credits**. Spent credits from failed requests are never charged. A plan sold by traffic shows **Requests this period** here instead, because a request costs nothing by itself. - **Standard traffic this period**: everything that left through the standard pool, the API and the proxy port together. Premium is counted separately and isn't in this figure. On a plan sold by credits the card reads **No cap on this plan** and sells nothing. On a plan sold by traffic it carries your cap, says **The API and the proxy port together**, and has **Buy bandwidth**. - **Premium traffic this period**: traffic that left through a premium exit (`exitClass: premium` on the API, or `-network-premium` on the proxy port), with **Buy premium**. It has its own allowance and isn't counted again in the standard figure. - **Proxy port traffic**: on a plan sold by credits, standard traffic through `proxy.foura.ai` against the gigabytes your plan includes for the port, with **Buy traffic**. On a plan sold by traffic, what the port moved; more is bought on the Standard traffic card. The card appears only if your plan includes the port. The two doors answer differently when the premium allowance runs out, and each card says so. On the API, requests keep working on the standard pool and the response says `exitClass: standard`, so the card reads **spent, serving from the standard pool**. On the proxy port, a connection that explicitly asks for the premium network is refused instead, so nothing is spent that you didn't agree to. Usage bars colour amber at 80% and red at or over the available amount. The Plan & Usage tab links to [Usage & Limits](/docs/dashboard/quota) for the per-endpoint, per-key, and per-proxy-user breakdown and every plan limit. ###### Buying more Click **Buy credits**, **Buy bandwidth**, **Buy premium**, or **Buy traffic** on the matching usage card. The dialog prices the amount you enter on the server, shows subtotal, VAT and total, and charges a saved card or a new one entered right there. The allowance is yours a moment after the charge goes through. What you buy lasts twelve months as a balance. Each period your plan's own allowance is used first, and only what you go over draws on the balance. The **Top-ups** table under the cards lists each purchase on its own row: **Bought** (date and amount paid), **What it adds**, **Used from it**, **Left** (**spent** when used up), and **Usable until**. What you go over draws on the purchase that expires first. The table is hidden when your plan sells no top-ups. ##### Billing Details The **Billing details** tab holds the standing invoice profile applied to every future invoice: - **Invoice email**: where PDF invoices are sent. Defaults to your account email. - **Company name**: appears on invoices when set. - **VAT / Tax ID**: EU VAT IDs are validated live against the VIES database. A green **Valid VAT** badge shows below the field once the check clears; a grey note reading **Format looks OK; live check unavailable right now.** shows when VIES can't be reached, and a number VIES rejects is marked in red with the reason. - **Company representative**: the person who represents the company. Bulgarian invoices print it as MOL in the recipient box. Leave it empty if it doesn't apply to you. - **Address**: street, city, postal code, and country. Country is a real dropdown. Saved details apply to the next invoice you generate. An invoice that's already been issued keeps the details it was frozen with, with one exception: you can correct an invoice until the end of the month it was issued in. See [Correcting an Invoice](#correcting-an-invoice). ##### Payment Methods Manage the cards on file for your account. - **View cards**: every saved payment method with card brand, last four digits, and expiry. - **Add a card**: click **Add Card**, enter the card, and click **Save card**. You won't be charged now. The card is stored with the payment provider. - **Set default**: click **Set default** on the card's row. The default card is used for all future charges. - **Remove a card**: click **Remove** on the card's row. ##### Invoices The Invoices tab lists your 50 newest invoices: the ones issued automatically when a payment goes through, and any invoice FourA issued to you by hand. | Column | Description | |--------|-------------| | Number | The invoice number | | Date | When the invoice was issued | | Amount | Total charged | | Status | Paid, open, or void | | (no heading) | A **PDF** button that opens the invoice PDF, with **Update details** or **Editable until** beside it while the invoice is still correctable | ###### Correcting an Invoice An invoice freezes your billing details at the moment it's issued. If you fix a typo in your company name or add a VAT ID afterwards, the invoice that already went out still shows the old values. You can correct it, but only until the end of the month it was issued in. - If your current billing profile differs from what the invoice froze, the row shows an **Update details** button. - If the invoice is still inside its window but there's nothing to change, the row reads **Editable until** with the date instead, so you can see the window exists before you need it. - After that date the invoice is final. Only invoices issued automatically for a payment can be corrected here. An invoice FourA issued to you by hand shows neither **Update details** nor **Editable until**. Click **Update details** and a dialog lists exactly which fields change and what they change from. The invoice number, the dates, and the amounts never move. Confirming is your explicit consent to the correction, and it's recorded. If your change adds or edits a tax ID, the dialog says so: a tax ID can change how VAT applies to a sale, so that case gets a human look and we get in touch if anything needs adjusting. ##### Upgrading Your Plan On the Free plan, click **Upgrade plan** on the Plan & Usage tab to open the plan picker. On a paid plan, use **Change plan**. The picker includes: - **Public plans tab**: side-by-side comparison of the standard plans with features and pricing. - **Your custom plans tab**: only appears if support has assigned one or more custom plans to your account. - **Billing interval toggle**: monthly or annual. The annual badge shows the exact saving for the plan in view, computed from the two prices. A plan sold at one interval only shows that interval and no toggle. - **Card entry**: if you don't have a card on file, you enter one here. - **Proration preview**: when switching between paid plans, you see the exact prorated amount (or credit) before confirming. If you don't have an active subscription, **Upgrade plan** opens the same picker so you can subscribe directly. An upgrade at the same billing interval takes effect as soon as it's paid. A downgrade, or any change of billing interval (even to a bigger plan), waits for the end of the current billing period. ##### Confirming a Plan FourA Placed You On Support can place your account on a plan from an agreed date, so the plan's limits apply before any money moves. Until you pay, the Plan & Usage card shows **Confirm *plan*** with the monthly price. Click it and the checkout page reads **Confirm your plan**: your plan is already active, and you choose how to pay for it. Pick monthly or annual, enter a card if you have none on file, and the button charges the full period (**Confirm and pay**). A placed plan that carries no charge needs no confirmation. It's simply yours. On a trial FourA placed you on, picking a different plan under **Change plan** opens checkout as a new subscription. You enter a card, a full period is charged from today, and the trial ends once the payment goes through. If a plan FourA placed can't be changed from the dashboard, the change page says why before you click, and the button stays off. ##### Changing Plans To switch between paid plans, go to **Billing > Plan & Usage** and click **Change plan**. You see the same picker as the upgrade flow, including proration. Each plan is priced at the interval it's sold at, and a plan sold at one interval only says so (**sold annually only**) rather than quoting a price it doesn't have. When the plan you're on is also sold at the other interval, the list ends with **Switch to annual billing** (with the saving) or **Switch to monthly billing**. The new interval starts at the end of the current period. - **Upgrades** at the same billing interval apply immediately with a prorated charge on today's card. - **Downgrades** apply at the end of the current billing period. While a downgrade or a change of billing interval waits for the period end, the plan card says **Changes to on ** and shows **Keep **, which cancels the change. If your bank asks you to confirm a payment, the status reads **Payment needs your confirmation**, and a **Confirm payment** button walks you through your bank's check. If your bank asks for 3D Secure on the proration charge, the dialog hands you the authentication step before anything changes. The new plan lands once you finish it, not before. Close that step without completing it and you stay on your current plan. ##### Canceling Click **Cancel** on the Plan & Usage tab and confirm with **Cancel subscription**. After canceling: - Your plan stays active until the end of the current billing period. - You won't be charged again. - You can resume before the period ends by clicking **Resume** on the Plan & Usage tab. ##### Related - [Dashboard Overview](/docs/dashboard/overview): Real-time stats and navigation - [Usage & Limits](/docs/dashboard/quota): Every plan limit vs your current usage - [How Your Plan Is Metered](/docs/guides/plans-and-metering): Credits, traffic, and which ceiling applies to you - [Rate Limits](/docs/api/rate-limits): What the API answers when an allowance runs out - [API Keys](/docs/dashboard/api-keys): Manage your keys - [Metrics](/docs/dashboard/metrics): Request analytics and usage breakdown --- #### Organizations URL: https://foura.ai/docs/dashboard/teams-and-orgs An organization groups credentials under one owner so several people can share them: API keys for the JSON API, and proxy users for the [proxy port](/docs/api/proxy-port). Everything an organization's credentials spend counts against the plan of whoever owns the organization, not against the person who fired the request. ##### Where to Find It Sign in to the [dashboard](https://foura.ai/dashboard) and open **Organizations** in the sidebar. Every organization you belong to is a row: | Column | What it shows | |--------|---------------| | Organization | The name. Click it, or **Open**, to go to the organization's own page. | | Your role | Owner, Admin, or Member | | Members | How many people are in it, the owner included | | Keys | How many API keys the organization owns | | Created | When it was created | | Actions | **Open** | ##### Creating an Organization Click **New organization**, enter a name between 2 and 60 characters, and confirm. You become the owner. The name can use letters, numbers, spaces and . , & + ( ) - _ ', up to eight words, with no web address, and the same rule applies when you rename it. It has to contain at least one Latin letter (a to z) or digit, and it can't match an existing organization's name (only Latin letters and digits are compared, ignoring case; everything else is ignored). Creating an organization needs a plan that covers more than one person. The **Organizations you own** row on [Usage & Limits](/docs/dashboard/quota) shows whether yours does. ##### Roles There are three, and every organization has exactly one owner. | Role | What it can do | |------|----------------| | Member | Create and use the organization's API keys and proxy users, rename its API keys, and see the organization's members and teams | | Admin | Everything a member can do, plus add and remove people, change their roles, manage every organization credential, and rename the organization | | Owner | Everything an admin can do, plus hand the organization over to someone else. The owner carries the plan every organization credential bills against. | The owner can't be removed, can't leave, and can't be demoted. Ownership moves one way: by transfer. Being in the organization is what grants access to its credentials. A member with no admin rights can still create a key or a proxy user the company pays for, which is the point: a developer writing collectors shouldn't need a personal credential that quietly bills them instead of the company. ##### The Organization Page **Open** on any row takes you to that organization's page. Six cards sit at the top: Members (with the owner, admin, and member split underneath), Your role, Owner, API keys, Proxy users, and Teams. Below them, two tabs: - **Members**: the people in the organization, plus the controls your role gives you. - **Teams**: the teams you belong to inside this organization, read-only. Owners and admins also get a **Rename** button in the page header. ##### Adding People Owners and admins get an **Add a member** card above the Members table. Enter an email address, pick **Member** or **Admin**, and submit. What happens next depends on the address, and the dashboard tells you which it was: - The address already has a FourA account: they join immediately and get an email naming the organization, who added them, and what their role can do. - The address doesn't: FourA emails them an invitation and they join the moment they sign in with that address. Nothing to click, and the invitation works whether they sign up fresh or already had an account they'd never opened. Invitations expire after 14 days. Re-adding the same address doesn't stack a second invitation. FourA sends one invitation per address and organization every 24 hours (a withdrawn invitation counts too): a second try inside that window is refused with **We already sent an invitation to this address today. Try again tomorrow.**, and the invitation already sent stays valid. Each person can send 20 invitations in 24 hours across all their organizations. The owner gets an email whenever somebody joins, because the new person spends the owner's credits. Nobody gets an email confirming their own action. ###### Pending Invitations While at least one invitation is open, an **Invitations** table sits above Members, visible to owners and admins. It lists the address, the role waiting for them, who invited them, and when. Expired invitations stay listed and are marked, so you can see why nobody arrived. Use the action on the row to withdraw one. ##### Changing a Role Owners and admins change a member between **Member** and **Admin** with the dropdown in the Role column. Three rules: - You can't change your own role. Ask another admin, or leave. - The owner's role isn't editable here. Use **Make owner** instead. - Every change asks first and says what the person will be able to do afterwards. ##### Removing Someone, and Leaving Owners and admins get **Remove** on every row but their own and the owner's. Your own row carries **Leave** instead, whatever your role. Removing someone takes them out of the organization's teams too, so they immediately stop seeing its credentials. It doesn't touch the credentials themselves: an organization's key or proxy user belongs to the organization, not to whoever made it. The owner can neither be removed nor leave. Hand the organization over first. ##### Handing the Organization Over Only the owner can do this. On the Members tab, click **Make owner** on the person taking over and confirm. The organization moves to their plan: from that moment every request its keys make and every tunnel its proxy users open counts against their credits, their traffic, their limits, and their invoice. The outgoing owner becomes an admin and keeps everything an admin can do. ##### Which Keys You See Every key an organization owns is visible to everyone in the organization. What differs is what you may do with it: | Action | Member | Admin | Owner | |--------|--------|-------|-------| | Use the key, reveal it, create new organization keys | Yes | Yes | Yes | | Rename it, edit its description | Yes | Yes | Yes | | Regenerate, disable, delete | No | Yes | Yes | | Move the key out of the organization | No | No | Yes | Renaming a key is harmless. Regenerating or disabling one stops every colleague using it the instant it happens, which is why those sit with admins. Moving a key out takes the asset out of the company and moves its usage onto the taker's own plan, which is why that sits with the owner alone. See [Managing API Keys](/docs/dashboard/api-keys) for the full walkthrough. ##### Which Proxy Users You See A proxy user follows the same three levels, for the same reasons. | Action | Member | Admin | Owner | |--------|--------|-------|-------| | Use it, read its password, create new ones | Yes | Yes | Yes | | New password, disable, delete, change saved targeting | No | Yes | Yes | | Transfer it out of the organization | No | No | Yes | Saved targeting sits with admins rather than members because re-pointing it silently re-points every connection the whole organization makes with that credential. A new password sits there because it breaks every config file the credential is already pasted into. A company's downloader belongs on an organization's proxy user. A personal one stops working the day that person leaves, and it bills them. See [Proxy Users](/docs/dashboard/proxy) for the walkthrough. ##### The Owner Filter Once you belong to an organization, an **Owner** dropdown appears above Overview, Metrics, Activity, API Keys, and Proxy. It has three kinds of option: **Everything**, **Personal**, and one entry per organization. The choice sticks between visits and applies to every one of those five pages. [Usage & Limits](/docs/dashboard/quota) and [Billing](/docs/dashboard/billing) deliberately don't take the filter. They answer "what bills to me", which is a different question from "what can I see": a member never sees the owner's quota. ##### Billing Attribution Every request made with an organization-owned key, and every tunnel opened with an organization-owned proxy user, bills the **organization owner**, whoever fired it. That's the whole reason to move a credential into an organization: it changes who the usage counts against for credits, traffic, plan limits, and invoices. The owner's [Usage & Limits](/docs/dashboard/quota) page counts organization credentials alongside their personal ones, on the API keys tab and the Proxy users tab, and splits the two so it's clear where the spend came from. ##### Teams Every organization gets a default team called **Everyone**. New keys are filed into it automatically and everyone who joins the organization is added to it, which is what makes membership give access to the keys. The Teams tab lists the teams you belong to, with your role in each, its member count, and the keys it lets you use (**Keys you can use**). Creating and editing teams isn't in the dashboard yet. ##### Related - [Managing API Keys](/docs/dashboard/api-keys): Create, transfer, and manage keys inside an organization - [Proxy Users](/docs/dashboard/proxy): Create and transfer proxy users inside an organization - [Usage & Limits](/docs/dashboard/quota): Team members, organizations, and what your plan allows - [Billing & Subscriptions](/docs/dashboard/billing): How the owner's plan covers organization-key usage - [Dashboard Overview](/docs/dashboard/overview): All dashboard sections --- #### Account Settings URL: https://foura.ai/docs/dashboard/account-settings The Settings page lets you manage your profile, security, connected accounts, and active sessions. Open it from the sidebar in the dashboard. It has four tabs: **Profile**, **Security**, **Connections**, and **Sessions**. ##### Profile | Field | Description | |-------|-------------| | Full Name | Your name, shown in the dashboard and in emails. Up to 60 characters and five words: letters, numbers, spaces and . ' - ( ), with no web address. Edit it and click **Save Changes**. If the name doesn't fit the rule, the reason shows under the field. | | Email Address | Read-only: managed via your authentication provider | | Member Since | When your account was created | Email address changes aren't supported through the dashboard. Contact support if you need to update your email. ##### Security ###### Changing Your Password If you signed up with email and password, you can change it here. Enter your current password and a new one: at least 8 characters, with an uppercase letter, a lowercase letter, and a number. Changing or setting your password signs out every other session on your account, on every device. The browser you made the change in stays signed in. You get 5 tries at your current password in 15 minutes. After that the form tells you how many minutes to wait, or you can reset the password from the sign-in page. ###### Setting a Password (OAuth Users) If you signed up with Google, the button reads **Set Password**. Setting one enables email-based login as an alternative, and no current password is required the first time. The Password row shows whether a password is set and when it was last changed. ###### Password Reset In the **Change Password** form, click **Forgot your password?** under Current password, then **Send Reset Email** to receive a reset link by email. Once you set a new password from that link, every session that signed in before the reset ends, and you sign in again with the new password. ##### Connections The **Connected Accounts** card lists Google as **Connected** or **Not connected**. Click **Disconnect** to remove it as a sign-in option. Make sure you have a password set before disconnecting your only sign-in provider. ##### Sessions **Active Sessions** lists every session across your devices, 10 per page: | Column | Description | |--------|-------------| | Device | The browser and system the session runs on | | IP Address | Where the session originated | | Last Active | When the session was last used | | Expires | When the session automatically ends | | Status | **Current** for this browser, **Active** for the others | ###### Signing Out Sessions Select sessions and click **Revoke Selected**, click **Sign Out Others** to end every session except this one, or **Sign Out** to end this one. **Sign Out Others** and **Sign Out** end those sessions on their next request. **Revoke Selected** stops the selected sessions from signing in again, but a dashboard already open on that device stays open until its session expires (the **Expires** column). To cut a device off at once, use **Sign Out Others** or change your password. ##### Related - [Dashboard Overview](/docs/dashboard/overview): All dashboard sections - [Authentication](/docs/getting-started/authentication): How API keys and authentication work --- #### Playground URL: https://foura.ai/docs/dashboard/playground The Playground (sidebar > Playground) lets you run live API requests against your real key without writing any code. It's the fastest way to try a new target site, debug a tricky response, or compare Auto, Single, Proxy, and Browser side by side. Open it at [foura.ai/dashboard#playground](https://foura.ai/dashboard#playground). ##### What It Does One form. Four engines. Real traffic. - **Auto**: smart fetch. You pass a URL plus a `validate` rule and FourA picks the cheapest path that works. - **Single**: direct HTTP fetch with realistic browser-like wire characteristics - **Proxy**: managed rotating proxy fetch, optionally scoped to target-visible countries - **Browser**: opens the URL in a Chrome browser instance for JS-rendered sites Requests run against the API key you pick at the top of the page. Usage counts against that key's quota the same way a production call would, so don't burn through your plan in testing. ##### Picking a Key The API key dropdown lists every active key you can use: your own under **My Keys**, then one group per organization you belong to. Any member can run an organization's key, and a request on it counts against the organization owner's plan. Pick the one you want the request to bill against. If you don't have any active keys yet, an inline prompt links you to the **API Keys** page to create one. ##### Choosing a Mode A top **Mode** row toggles between **Auto** and the manual engines. When Auto is selected, the form switches to the minimal Auto surface (URL plus `validate` plus a few knobs). Both rows are always shown: **Mode:** Auto, and **Product:** **Single**, **Proxy**, **Browser**. Picking one deselects the other. Switching products swaps which fields are visible and which engine the request hits. The current selection is preserved when you reload the page. | Mode | When to use it | |------|----------------| | Auto | New target or mixed-protection site. Auto picks the cheapest path and remembers what works. | | Single | Fast HTTP fetch. Best first pick for a known host. | | Proxy | Same fetch with automatic proxy rotation. Set `exitCountries` when you need a target-visible country. | | Browser | Loads the page in a Chrome browser instance. Use when the data appears only after JavaScript runs. | ##### Building the Request ###### URL Row The top row holds the HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS), the target URL, and the **Send** button. Single, Proxy, and Auto honor every method. Browser ignores the method (Chrome always issues GET for navigation) and the body. ###### Request Tabs Below the URL row, five tabs let you fill in everything else: | Tab | What it controls | |-----|------------------| | UI | Form fields for timeouts, redirects, flags, proxy, browser-specific options, and validate rules | | Body | Free-form body for POST / PUT / PATCH requests | | Headers | Custom request headers as key-value pairs | | Cookies | A cookie jar kept per host in this browser. **Use jar** attaches matching cookies to the request and **Save response cookies** stores the ones a response sets. Both are on by default, and the tab previews what this request will send | | Raw | The exact JSON payload that will be sent, as a read-only preview with **Copy JSON**, and the curl reproducer under it | Whatever you change in UI / Body / Headers / Cookies is reflected in Raw. You can't type into Raw: change the request on the other tabs. A red dot appears on any tab or collapsible section that holds a value different from the engine's defaults, so you can spot at a glance what you've customized. ###### UI Pane Sections The UI tab groups settings into collapsible sections. Empty fields fall back to the engine's schema default. Sections that don't apply to the current Mode are hidden. - **Timeouts**: `timeout_ms`, `connect_timeout_ms`, `accept_timeout_ms`, `server_response_timeout_ms`, `dns_cache_timeout_sec`. Auto exposes only `timeout_ms` (the total budget). - **Redirects**: toggle and set `followRedirects` (0-20). Single and Proxy. Browser follows redirects on its own. - **Flags**: `unblocker` for Single, Proxy, and Browser (`unblocker` on Browser completes the checks a page asks for); `tryJsonData` and `returnBuffer` for Single and Proxy. Auto exposes `forceProxy` and `returnSession` instead. - **Proxy**: pick a specific proxy ID for Single or Browser, or set `maxTries`, the Proxy outer timeout, `exitCountries`, `exitClass`, and `ignoreProxies` for the Proxy engine. Auto also exposes `ignoreProxies` and `exitClass`. The `exitClass` select has three states: unset sends no field at all, `standard` says the request must never escalate, and `premium` lets it escalate to a premium exit when the standard pool is struggling. Unset and `standard` are different requests, so leave the select empty unless you mean one of the two. Premium needs a plan that includes premium exits: see [exitClass](/docs/api/endpoints#exitclass). - **Browser profile**: three cascading dropdowns, **os**, **browser**, and **version**, listing what FourA can actually present. They show up in Single and Proxy mode. Leave them empty for the newest Chrome. Each select narrows the other two, so a combination that resolves to nothing never appears. The section needs `unblocker` on: with it off no browser headers are sent, a profile would only half-apply, and the API refuses the request instead. - **Browser**: browser-only options such as `checkStatus` and `checkText`. - **Validate**: **status accept** and **status fail** take comma-separated status codes (`validate.status`), and **body accept** and **body fail** take substrings with `|`-separated alternatives (`validate.data`). Available for Single, Proxy, and Auto. Browser uses `checkStatus` and `checkText` instead. The form has no field for header rules (`validate.headers`). Once a run returns a proxy that worked, a **Working proxies** section appears at the end of the UI tab. It lists up to 20 proxy IDs, newest first, each with its exit country and time. **use** puts one into the `proxy` field on Single or Browser (the Proxy engine finds its own), and **×** drops it from the list. ###### Exit Country Scoping (Proxy Mode) The **exitCountries** field on Proxy accepts a comma-separated list of two-letter target-visible country codes (`CZ, GB`). Values are trimmed, uppercased, and deduplicated on submit. Selection is a strict allowlist: proxies with unknown exits are excluded and the request never falls back to another country. If the current pool has no match, the response returns `code: "no_eligible_proxy"` with the requested scope echoed back in `details.exitCountries`. Preserve the scope and retry later. When a proxy call succeeds under scoping, the response strip shows `exit ` next to the proxy ID so you can verify the served country matches what you asked for. ###### Toolbar Reset The **Reset** button on the toolbar (next to **History** and **Saved**) clears the playground back to a clean slate. Because it's destructive, it opens a confirm dialog that lists exactly what will be wiped: all three product forms (Single, Proxy, Browser), any saved cookies in the jar, any carried proxies, and the current response. Saved presets and the selected API key are kept. Click **Reset everything** to confirm; anything else cancels. ##### Sending and Canceling Click **Send** to fire the request. The right column flips to a loading state with a spinner and a **Cancel** button while the call is in flight. Click Cancel (or hit the button again on mobile) to abort. A canceled request restores the idle placeholder with "Request canceled." instead of rendering an error. The response card switches to the result the moment the request completes (or fails). On a phone, or in any window narrow enough to put the response under the form, the page brings the response card into view when it arrives low on the screen. Auto runs can take longer than the manual engines because the ladder may climb several rungs on a cold target. ##### Reading the Response The response column mirrors the request layout with its own tabs: | Tab | What it shows | |-----|---------------| | Body | Parsed body. Switches between JSON, HTML, and Text views depending on what came back. | | Headers | Response headers, one per line. | | Cookies | Cookies returned by the target, in both parsed (host-grouped) and raw (Set-Cookie text) views. The parsed view shows an `HO` badge on host-only cookies; domain cookies are unmarked. | | Raw | The full JSON envelope returned by the API. | The response toolbar has **Copy** and **Download** for the whole response, and **Find in response** (Ctrl+K or Cmd+K) to search the open tab, with Enter and Shift+Enter to step through the matches. Body, Headers, and Cookies also have their own Copy and Download for just that tab. A meta strip above the tabs shows the upstream HTTP status, the total time, the proxy ID that handled the call, and (for a scoped Proxy call) the two-letter `exit `. The strip also shows the response size and, when the API reports them, the credits the request spent. For Auto runs, the rung and the attempts are in the sentence under the strip. ###### What the Call Needed A sentence under the meta strip says what carried the page, in words. For an Auto run it names the rung (a session FourA already had for the host, a plain request, a rotating proxy, a real browser, or a browser first and then a cheap replay), whether a challenge was solved, how many attempts it took, and what it cost. When the request set `exitClass`, it also says whether a premium exit or the standard pool delivered it. When one of your plan's limits refused the call, the sentence says so first: "Stopped by your plan, not by the site", followed by which limit (today's browser requests used up, too many requests in flight, this period's credits spent, and so on) and a link to [Usage & Limits](/docs/dashboard/quota). The line is built from the `X-FourA-Limit` code the API returned, so a hard page that fails tells you whether the site stopped it or the plan did. ###### Carry Values Between Runs After any run that returned reusable session data, a small **Carry** control on the response toolbar shows what's available: - **Auto** runs made with `returnSession` offer the session's `userAgent` and `proxy`. The session's cookies show on the response's **Cookies** tab, and go into the cookie jar when **Save response cookies** is on. - **Browser** runs offer the response `userAgent`, plus the proxy ID if one was used. - **Proxy** runs offer the returned proxy ID, and the browser profile when the rotation picked one you didn't ask for. Click **Carry** and pick where to apply each value in one click: `userAgent` becomes a `User-Agent` header on Single or Proxy, and the proxy ID drops into the `proxy` field on Single or Browser. Fields that receive a carried value show the "modified" red dot so you can see what changed. A carried **browser profile** fills the three os, browser, and version selects and turns `unblocker` on, the same rule that applies when you pick a profile by hand. It's offered only once the profile catalogue has loaded, since the form is three selects and not an id field. The profile is the one value that says the request which *worked* was not the request you typed: Proxy reports `profile` only when it moved to a browser family you didn't ask for. Replay without it and you replay the version that failed. See [Why a Proxy Request Ran Out of Tries](/docs/troubleshooting/proxy-attempt-report). ###### Expand to Full Screen The expand icon on the response toolbar lifts the response card out of the split layout and into a full-screen overlay. Use it for deep JSON trees, long Set-Cookie dumps, or wide HTML bodies where the half-width column gets cramped. The page itself stops scrolling while the overlay is open. Click the icon again (or press Escape) to collapse. ##### The curl Reproducer On the request's **Raw** tab, under the JSON, a curl block shows the exact command line equivalent of the request you're building, with a **Copy curl** button. Copy it to repro the request from a terminal, share it with a teammate, or paste it into a bug report. For revealable keys, a **Reveal key** button next to the snippet drops the real plain-text key straight into the curl so you can copy-and-run as is. Click again to hide. Legacy keys (created before the reveal feature shipped) keep a `PASTE_PLAINTEXT_FOR_` placeholder; regenerate the key from the **API Keys** page to make it revealable. The reveal is audit-logged on the server every time, and the plain key only lives in memory for the current page session. ##### Saving Presets If you find yourself reconfiguring the same target repeatedly, save it. Click **Save** on the request tabs row to store the current configuration as a named preset. Open **Saved** in the toolbar to see your presets. Click **Load** to fill the form, or **Delete** to remove one. A request opened from the FourA Chrome extension's DevTools tab loads with the extension's key selected when that key is in your account, and the page says so. Otherwise it asks you to pick a key. A replayed request that doesn't set `unblocker` runs with it on, as the API does. | Preset field | What it stores | |--------------|----------------| | Name | A short label (up to 100 characters) | | Description | Optional notes (up to 500 characters) | | Endpoint | Which engine the preset is for (auto / single / proxy / browser) | | Config | The full request payload, including UI fields, headers, cookies, and body | Presets are scoped to your user account and aren't shared with team members. ##### Replaying from History Every request you run is logged. Open **History** in the toolbar to see your last 20 runs, sorted newest first. Each row shows the endpoint, target URL, status, and time. Click **Replay** on any row to load that request back into the form, then **Send** to run it again. History is automatically scoped to your account: you only see your own runs. ##### Opening from Activity The [Activity Log](/docs/dashboard/activity-log) detail dialog has an **Open in Playground** button. Click it and the Playground loads with both the archived request and the archived response. The form fills in from the stored payload, and the response card shows what the API returned at that moment with an "archived" badge on the proxy meta strip ("archived