Skip to content

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.

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.

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
  • 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_LIMITED with a Retry-After header.
  • 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_ms can only lower the budget that applies.

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" }
}
  • device is desktop or mobile.
  • features holds 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_ALLOWED before 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.

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
  • POST /v1/jobs returns 202 with request_id, status and status_url once the job is durably queued. Resubmitting with the same idempotency key returns 202 with the original job’s current state, which is not always queued.
  • GET /v1/jobs/{request_id} returns one of queued, processing, succeeded, failed or expired. A success includes a short-lived signed download_url. expired means the result’s retention window has passed.
  • GET /v1/jobs/{request_id}/result is 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-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 from and to are those hour boundaries.
  • data_through is how far the figures reach. It is normally a few minutes behind the current time.
  • usage_amount is 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.

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.

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.