API documentation
Send a URL and your proxy. Orpheus detects the anti-bot challenge, clears it, and returns the page — with a session you can reuse for the next requests.
Quick start
- Sign in to the console and create an API key in the API keys tab. Copy it: it is shown only once.
- Have a sticky residential or ISP proxy ready (one exit IP kept for at least 10 minutes).
- Call
POST /v1/fetchwith the URL and the proxy.
curl -X POST https://orpheus-api.lapetiteagora.fr/v1/fetch \ -H "Authorization: Bearer $LPA_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://www.example.com/", "proxy": "http://user:pass@proxy.example:8080" }'
import os, requests resp = requests.post( "https://orpheus-api.lapetiteagora.fr/v1/fetch", headers={"Authorization": f"Bearer {os.environ['LPA_KEY']}"}, json={ "url": "https://www.example.com/", "proxy": "http://user:pass@proxy.example:8080", }, timeout=70, ) page = resp.json() print(page["status"], page["solved"], page["session_id"])
const resp = await fetch("https://orpheus-api.lapetiteagora.fr/v1/fetch", { method: "POST", headers: { Authorization: `Bearer ${process.env.LPA_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ url: "https://www.example.com/", proxy: "http://user:pass@proxy.example:8080", }), }); const page = await resp.json(); console.log(page.status, page.solved, page.session_id);
Set a client timeout of about 70 seconds: clearing a challenge can take a few seconds, sometimes more.
Authentication
Every request carries your API key in the Authorization header:
Authorization: Bearer lpa_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Keys are created and revoked in the console. Treat them like passwords: keep them server-side, never in a browser or a public repository. A revoked key, or a suspended account, gets 401 invalid_key immediately.
Fetch a page
Request body (JSON)
| Field | Type | Description |
|---|---|---|
urlrequired | string | The page to fetch, http or https. |
proxy | string | Required to start a session. http://user:pass@host:port, https://… or socks5://…. Omit it (or send the same value) when you pass a session_id. |
session_id | string | Reuse a session returned by a previous call. See Sessions. |
method | string | GET (default), POST, PUT, PATCH, DELETE, HEAD or OPTIONS. |
headers | object | Extra request headers, as {"Name": "value"} strings. The browser headers (User-Agent, client hints…) are already set. |
body | string | Request body for POST/PUT/PATCH. |
body_encoding | string | text (default) or base64 for a binary body. |
cookies | object · array | Cookies to add to the session before the request. See Cookies. |
preset | string | Browser fingerprint, set when the session is created. Default chrome-latest-windows (keep it unless you know why). |
Response
The API answers 200 as soon as the target site answered — whatever the site's own status. Read the site's status in status.
{
"status": 200,
"headers": { "content-type": ["text/html; charset=utf-8"] },
"body": "<!doctype html>…",
"encoding": "text",
"url": "https://www.example.com/",
"session_id": "s_Ij4oudr1bYPzUA8U3sdlACAf",
"solved": ["datadome"],
"cookies": [{ "name": "datadome", "value": "…", "domain": ".example.com", "path": "/" }],
"elapsed_ms": 2343
}| Field | Description |
|---|---|
status | HTTP status returned by the target site. |
headers | Response headers; each value is a list of strings. |
body · encoding | The page. encoding is text (UTF-8) or base64 for binary content (images, PDF…). |
url | Final URL, after redirects. |
session_id | Send it back to keep the same identity. Also returned with most errors. |
solved | Protections cleared during this call: akamai, datadome, incapsula, kasada, cloudflare, ticketmaster. Empty when nothing had to be solved. |
cookies | All cookies of the session after the call. |
elapsed_ms | Time spent on our side, including solving. |
Sessions
Anti-bot cookies are bound to the browser identity that obtained them: IP, fingerprint, cookie jar. A session keeps that identity for you, so a challenge cleared once stays cleared.
- The first call (with a
proxy, withoutsession_id) creates a session and returns itssession_id. - Send that
session_idon the next calls: same proxy, same fingerprint, same cookies. Later pages usually come back in a few hundred milliseconds, withsolved: []. - A session expires after 30 minutes without requests. Each request extends it.
- A session keeps its proxy for life. Sending a different
proxywith asession_idreturns409 proxy_mismatch: start a new session instead. - One request at a time per session. A second concurrent request waits up to 10 seconds, then gets
409 session_busy. To go faster, run several sessions in parallel, each with its own proxy.
import os, requests API = "https://orpheus-api.lapetiteagora.fr/v1/fetch" HEADERS = {"Authorization": f"Bearer {os.environ['LPA_KEY']}"} def fetch(**payload): r = requests.post(API, headers=HEADERS, json=payload, timeout=70) data = r.json() if r.status_code != 200: raise RuntimeError(data["error"]) return data # 1. Home page first: creates the session and clears the challenge home = fetch(url="https://www.shop.example/", proxy="http://user:pass@proxy.example:8080") # 2. Then the product page, on the same identity product = fetch( url="https://www.shop.example/product/42", session_id=home["session_id"], headers={"Referer": "https://www.shop.example/"}, ) print(product["status"], product["solved"])
# 1. Home page: note the session_id in the response curl -s -X POST https://orpheus-api.lapetiteagora.fr/v1/fetch \ -H "Authorization: Bearer $LPA_KEY" -H "Content-Type: application/json" \ -d '{"url":"https://www.shop.example/","proxy":"http://user:pass@proxy.example:8080"}' # 2. Product page on the same session curl -s -X POST https://orpheus-api.lapetiteagora.fr/v1/fetch \ -H "Authorization: Bearer $LPA_KEY" -H "Content-Type: application/json" \ -d '{"url":"https://www.shop.example/product/42","session_id":"s_…", "headers":{"Referer":"https://www.shop.example/"}}'
Errors
Errors use a non-200 status and the same shape. When a session exists, its session_id is included.
{
"error": { "code": "proxy_blocked", "message": "…" },
"session_id": "s_…"
}| Status | Code | What to do |
|---|---|---|
| 400 | invalid_request | Fix the request: missing url, missing proxy for a new session, invalid field, private or unreachable address. |
| 401 | invalid_key | Missing, wrong or revoked key, or suspended account. |
| 402 | quota_exceeded | Monthly quota reached. It resets on the 1st (UTC). |
| 404 | session_not_found | The session expired (30 min idle) or was closed. Start a new one. |
| 409 | proxy_mismatch | A session keeps its proxy. Start a new session for another proxy. |
| 409 | session_busy | Another request is using this session. Wait, or use another session. |
| 413 | response_too_large | The page is larger than 5 MB. |
| 422 | proxy_blocked | The site blocked this IP. Change proxy and start a new session. |
| 429 | too_many_concurrent | Too many requests in flight for your account. Retry when one finishes. |
| 502 | challenge_failed | The challenge could not be cleared. Retry once; if it persists, change proxy. |
| 502 | solver_error · transport_error | Temporary solver or network issue (often the proxy). Retry with a short delay. |
| 504 | timeout | No answer in time. Retry; the session stays usable. |
| 500 | internal_error | Our side. Retry, and tell us if it persists. |
Retry rule of thumb: retry 429, 502, 504 and 500 with a short backoff. Never retry 422 on the same session: switch proxy.
Limits
| Limit | Value |
|---|---|
| Monthly requests (beta) | 20,000 per account, reset on the 1st (UTC). Every call to /v1/fetch that reaches the site counts, even if solving then fails; calls refused upfront (400, 401, 402, 404, 409, 429) don't. |
| Concurrent requests | 5 per account by default (see your Account tab). |
| Time per request | About 55 seconds, solving included. |
| Response size | 5 MB of page content. |
| Session lifetime | 30 minutes after the last request. |
| Cookies | 50 per request, 4,096 characters per value. |
Usage, solved challenges and your last requests are in the console.
Other endpoints
Requests used this month, your quota, and the daily breakdown for the last 30 days (requests, successes, errors, bytes, solves by protection).
curl -s https://orpheus-api.lapetiteagora.fr/v1/usage -H "Authorization: Bearer $LPA_KEY"Closes a session right away (otherwise it expires on its own). Returns {"deleted": true}, or 404 if it no longer exists.
curl -s -X DELETE https://orpheus-api.lapetiteagora.fr/v1/sessions/s_… -H "Authorization: Bearer $LPA_KEY"Best practices
- Sticky proxies only. A rotating proxy changes IP between requests and invalidates the cookies you just obtained. Aim for sessions of 10 minutes or more.
- One session per identity. Reuse it for every page of a flow; open more sessions (with more proxies) to scale.
- Browse naturally. Load the home or category page first, then the product page with a
Refererheader. Direct hits on deep pages get flagged more often. - Don't hammer one session. Clear the challenge with one request, then go on; avoid bursts from the same IP.
- On
proxy_blocked, drop the session and its proxy for a while; a burned IP stays burned. - Respect the sites you access. You are responsible for following their terms and the laws that apply to you.