API reference
Endpoints, limits, idempotency rules and error codes of the Struqter API.
This page is the short reference. For explanations and complete examples, read the guide.
Authentication
Section titled “Authentication”Every /v1 endpoint requires your API key:
Authorization: Bearer <api-key>Your onboarding details contain the base URL and the key. Keep the key server-side.
Endpoints
Section titled “Endpoints”| Method | Path | Purpose |
|---|---|---|
POST |
/v1/scrape |
Retrieve a page synchronously; returns the target body |
POST |
/v1/jobs |
Queue the same request as an asynchronous job |
GET |
/v1/jobs/{request_id} |
Job status, with a download URL once it succeeds |
GET |
/v1/jobs/{request_id}/result |
Authenticated download of a job result |
GET |
/v1/usage |
Usage for a time window |
GET |
/v1/usage/periods |
Closed monthly billing periods |
Limits
Section titled “Limits”- Requests can run concurrently up to your contract’s limits. Synchronous and asynchronous submissions have separate concurrency limits and share your contract’s request rate.
- A per-IP limit also applies, before authentication.
- Over any limit, the API returns
429 RATE_LIMITEDwith aRetry-Afterheader. - A synchronous call is bounded by your contract’s synchronous budget and a platform ceiling, because it holds a connection open. A queued job is bounded by your contract’s asynchronous budget, which is typically far larger.
timeout_mscan only lower the budget that applies.
Request body
Section titled “Request body”POST /v1/scrape and POST /v1/jobs take the same JSON body. The field table is in the guide.
{ "url": "https://example.com", "render_mode": "static", "geo": "US", "device": "mobile", "features": ["premium_proxy"], "timeout_ms": 15000, "metadata": { "customer_reference": "123" }}deviceisdesktopormobile.featuresholds up to 8 lowercase identifiers matching^[a-z0-9_]{1,32}$, agreed during onboarding.- Every requested feature must be enabled on your contract. Anything else, including a name Struqter does not offer, is refused with
400 FEATURE_NOT_ALLOWEDbefore anything runs or is charged. - Only backends that support the requested device and every requested feature can serve the request. If none can, the request fails with
503 NO_ELIGIBLE_ROUTE.
Synchronous requests
Section titled “Synchronous requests”A success returns HTTP 200 with the raw validated target body. Failures are JSON (see Errors).
| Header | Meaning |
|---|---|
X-Request-Id |
Struqter identifier to include in support requests |
X-Execution-Mode |
sync |
X-Route-Mode |
normal or best_effort |
X-Target-Status |
HTTP status observed at the target |
X-Render-Mode |
Render mode that produced the result |
Asynchronous jobs
Section titled “Asynchronous jobs”POST /v1/jobsreturns202withrequest_id,statusandstatus_urlonce the job is durably queued. Resubmitting with the same idempotency key returns202with the original job’s current state, which is not alwaysqueued.GET /v1/jobs/{request_id}returns one ofqueued,processing,succeeded,failedorexpired. A success includes a short-lived signeddownload_url.expiredmeans the result’s retention window has passed.GET /v1/jobs/{request_id}/resultis the authenticated download, for when you can’t use the signed URL. If the signed URL expires while the result is still retained, poll the status again for a new one.
Jobs are dispatched in order. Each customer’s jobs are spaced at that customer’s contracted request rate, so your jobs never wait behind another customer’s batch.
Idempotency
Section titled “Idempotency”Idempotency-Key is an optional header on POST /v1/scrape and POST /v1/jobs.
- The same key and the same payload return the original operation. You are not charged twice.
- The same key with a different payload returns
409 IDEMPOTENCY_CONFLICT. - Retrying a synchronous request while the original is still running returns
409 REQUEST_IN_PROGRESS. Wait and retry with the same key. - A key stays bound to its operation once execution may have started, even if the operation failed. Replaying it returns the recorded outcome. To deliberately retry a failed operation, use a new key.
- The one exception is
NO_ELIGIBLE_ROUTE, which proves nothing was executed. Its key is released and can be reused. - A key outlives its result. Once the retention window has passed, replaying returns
410 RESULT_EXPIRED. - Requests without a key are always new operations, and different keys are different billable operations.
GET /v1/usage?from=<ISO-8601>&to=<ISO-8601> returns your request count, successes, failures and usage_amount. Without from, the window is the 30 days before to; without to, it ends now.
- Usage is counted in whole UTC hours. The window widens to every hour it overlaps, and the response’s
fromandtoare those hour boundaries. data_throughis how far the figures reach. It is normally a few minutes behind the current time.usage_amountis usage valued at your price rules, before minimums. Each rate is applied to its own success count and rounded once. Minimums settle when the month closes, so under a minimum this amount can be lower than what the month is charged.
Billing periods
Section titled “Billing periods”GET /v1/usage/periods returns the closed months among the 12 calendar months (UTC) before the current one, newest first, with one entry per contract and month.
| Field | Meaning |
|---|---|
billed_amount |
The month’s charge, minimums included |
adjustments |
Sum of corrections made after the close; negative for a credit |
amount_due |
billed_amount plus adjustments. Check invoices against this |
lines |
The lines that make up billed_amount, described in the next table |
Each line has line_no, kind, price_rule_id, price_per_thousand, quantity (successes), amount, minimum_id, active_days and period_days.
kind |
Meaning |
|---|---|
usage |
Successes billed at one price rule and rate |
minimum_included |
Requests covered by a minimum’s fee |
minimum_excess |
Successes beyond what the minimum covers, at their rate |
minimum_top_up |
The amount that brings the month up to the minimum |
minimum_id names the minimum a line belongs to and is null on usage that no minimum covers. active_days and period_days are set only when a minimum covered part of the month. When price_rule_id is null, see the guide.
A closed period never changes. A correction is recorded as an adjustment beside it.
Money values are objects such as { "currency": "USD", "micros": "5400000", "amount": "5.4" }. micros is the amount in millionths of the currency unit, as a string.
Errors
Section titled “Errors”Failures return JSON with a stable code, a safe message and a retryable flag:
{ "status": "failed", "error": { "code": "NO_ELIGIBLE_ROUTE", "message": "No execution backend is currently eligible.", "retryable": true }}| HTTP | Code | Meaning |
|---|---|---|
400 |
INVALID_REQUEST |
Malformed payload, URL or idempotency key |
400 |
FEATURE_NOT_ALLOWED |
A requested feature is not enabled on your contract. Don’t retry as-is |
401 |
AUTHENTICATION_FAILED |
Missing, invalid or revoked API key |
403 |
TARGET_NOT_ALLOWED |
This target is not permitted. Nothing was executed |
409 |
IDEMPOTENCY_CONFLICT |
Key reused with a different payload |
409 |
REQUEST_IN_PROGRESS |
The request with this key is still running |
410 |
RESULT_EXPIRED |
The result’s retention window has passed |
429 |
RATE_LIMITED |
Per-IP, request rate, concurrency or queue limit. Wait for Retry-After |
502 |
TARGET_ERROR |
The target’s status is not billable under your contract. Not charged |
502 |
UPSTREAM_FAILURE |
Every eligible backend failed. Retry with backoff and jitter |
502 |
RESPONSE_TOO_LARGE |
The response exceeded your contract’s size ceiling. Not charged |
503 |
NO_ELIGIBLE_ROUTE |
No backend is currently eligible. Retry later |
504 |
TIMEOUT |
The request deadline was exceeded |
TARGET_ERROR responses carry X-Target-Status, so you still learn what the target said. They are not retryable, because the target answers every backend the same way.
The API never exposes which provider served a request, or provider-native errors.