Wallet & billing
Scrapewise is prepaid and pay-as-you-go. Each customer has a wallet balance in euros. Billable calls are charged per page delivered; a page that fails to deliver is not charged, and the same URL is not charged twice within one run. Every new wallet starts with 5 free requests.
When a customer has neither balance nor free requests left, billable endpoints return 402 Payment Required with the error code WALLET_INSUFFICIENT_BALANCE. Clients should treat that code as “prompt the user to top up” rather than as a transient failure.
Price per page delivered: plain €0.00015 · render €0.00075 · super €0.0015 · render + super €0.00375 · Amazon plugins €0.00015 · Google search €0.0015. (Per 1,000 pages: €0.15 · €0.75 · €1.50 · €3.75 · €0.15 · €1.50.)
There are no plan limits — no project, task or seat limits, and no monthly quota. Scraped data is kept for 90 days. Balances do not expire and unused balance is not refunded.
Money unit
Every *Micros field on this surface is an integer count of micro-cents (1 cent = 10,000 µ¢). One plain page at €0.00015 is 150 micro-cents. The single exception is amountCents on the top-up request — that is the Stripe boundary and is expressed in whole cents.
Price list
| What you scrape | Per page delivered | Per 1,000 pages |
|---|---|---|
| Plain page | €0.00015 | €0.15 |
| Render (pages that need a browser) | €0.00075 | €0.75 |
| Super (residential proxy) | €0.0015 | €1.50 |
| Render + Super (the hardest sites) | €0.00375 | €3.75 |
| Amazon plugins | €0.00015 | €0.15 |
| Google search | €0.0015 | €1.50 |
Scrapewise always tries the cheapest method that works for a site. The live machine-readable catalogue is GET /api/wallet/prices.
Endpoint summary
| Method | Path | Operation ID | Auth scope |
|---|---|---|---|
| GET | /api/wallet | — | bearer |
| GET | /api/wallet/transactions | — | bearer |
| GET | /api/wallet/prices | — | bearer |
| POST | /api/wallet/topup | — | bearer (Firebase sign-in only) |
| GET | /api/wallet/topup/{sessionId} | — | bearer |
| GET | /api/billing-history | — | bearer |
Get the wallet — GET /api/wallet
curl -H "Authorization: Bearer $KEY" \
https://portal.scrapewise.ai/api/scraper-api/api/walletThe wallet is created on first read, with the signup free grant applied.
Response (200)
{
"balanceMicros": 1250000,
"currency": "EUR",
"freeRequestsRemaining": 5,
"lowBalance": false,
"lowBalanceThresholdMicros": 500000,
"walletBillingEnabled": true
}| Field | Type | Description |
|---|---|---|
balanceMicros | long | Signed balance in micro-cents. May be negative after a bounded overshoot on a run already in flight. |
currency | string | EUR |
freeRequestsRemaining | int | Signup free requests still unused |
lowBalance | boolean | balanceMicros < lowBalanceThresholdMicros (exactly at the threshold is not low) |
lowBalanceThresholdMicros | long | Low-balance threshold, micro-cents |
walletBillingEnabled | boolean | Master switch. When false the wallet is informational only and nothing is charged yet. |
Errors — 400 (N/A) / 401 / 403 (N/A) / 404 (N/A) / 429 / 500. (The customer is read from the auth principal, so 400/403/404 don’t apply.)
List transactions — GET /api/wallet/transactions
curl -H "Authorization: Bearer $KEY" \
"https://portal.scrapewise.ai/api/scraper-api/api/wallet/transactions?page=0&size=20"The customer’s own ledger, newest first. Public shape only — no admin audit fields and no margin data.
| Param | Type | Description |
|---|---|---|
page | int | Zero-indexed page (default 0) |
size | int | Page size, 1–100 (default 20) |
Response (200) — PageDTO<WalletTransactionDTO>. Note this is Scrapewise’s own page envelope: the index field is page, not Spring’s number.
{
"content": [
{
"id": "66f0c2...",
"type": "CHARGE",
"status": "SETTLED",
"amountMicros": -30000,
"freeRequestsDelta": 0,
"balanceAfterMicros": 1220000,
"source": "RUN",
"jobId": "66f0be...",
"scraperId": "66e11a...",
"groupId": "66e119...",
"lines": [
{ "label": "Plain", "pages": 200, "freePages": 0, "unitPriceMicros": 150, "amountMicros": 30000 }
],
"createdAt": "2026-09-15T08:12:04Z",
"settledAt": "2026-09-15T08:14:41Z"
}
],
"page": 0,
"size": 20,
"totalElements": 37,
"totalPages": 2
}| Field | Type | Description |
|---|---|---|
type | enum | TOPUP, CHARGE, ADJUSTMENT, FREE_GRANT |
status | enum | OPEN, SETTLED, APPLIED, EXPIRED, FAILED, UNMATCHED |
amountMicros | long | Signed micro-cents: TOPUP / positive ADJUSTMENT > 0, CHARGE ≤ 0, FREE_GRANT = 0 |
balanceAfterMicros | long | null | Balance after this row was applied; null while the row is OPEN |
source | enum | null | What produced the charge: RUN, SAMPLE, PREVIEW_URL, PREVIEW_CURL |
lines | array | Per-service breakdown — pages, freePages (covered by free requests), unitPriceMicros, amountMicros |
Errors — 400 (size outside 1–100, or negative page) / 401 / 403 (N/A) / 404 (N/A) / 429 / 500.
List scrape prices — GET /api/wallet/prices
curl -H "Authorization: Bearer $KEY" \
https://portal.scrapewise.ai/api/scraper-api/api/wallet/pricesEvery enabled sellable service with its customer price per delivered unit. Margin data is never included. Requires a bearer token.
Response (200)
[
{ "rowKey": "tier:plain", "kind": "TIER", "label": "Plain", "sellingPriceMicros": 150 },
{ "rowKey": "tier:render", "kind": "TIER", "label": "Render", "sellingPriceMicros": 750 }
]| Field | Type | Description |
|---|---|---|
rowKey | string | Stable catalogue key, e.g. tier:plain, tier:render, plugin:/plugin/amazon/pdp |
kind | enum | TIER (fetch tier), PLUGIN (dedicated endpoint), DOMAIN (per-domain price) |
label | string | Human-readable name |
sellingPriceMicros | long | Customer price per delivered page / plugin call, micro-cents (150 = €0.00015) |
Disabled catalogue rows are omitted, so the response is exactly what a customer can be charged for today.
Errors — 400 (N/A) / 401 / 403 (N/A) / 404 (N/A) / 429 / 500.
Start a top-up — POST /api/wallet/topup
POST /api/wallet/topup
Authorization: Bearer <firebase-jwt>
Idempotency-Key: 8f1c3f6e-1a2b-4c3d-9e8f-0a1b2c3d4e5f
Content-Type: application/json
{ "amountCents": 2000 }Creates a Stripe Checkout session (mode=payment, EUR) for amountCents (500–100000, i.e. €5–€1,000) and returns its URL and id. Redirect the customer to url.
- The
Idempotency-Keyheader is mandatory and must be non-blank. A retry with the same key returns the same session (200 plus anIdempotency-Replayed: trueresponse header) instead of opening a second one. Send a fresh UUID per genuine submit. - Signed-in (Firebase) principals only. API keys get
403, and so does an admin impersonating another customer via customer-switch — money is never moved on someone else’s behalf. - The balance is credited by the Stripe webhook once payment is confirmed, not by this call. Poll
GET /api/wallet/topup/{sessionId}after the redirect back.
Response (200)
{
"url": "https://checkout.stripe.com/c/pay/cs_...",
"sessionId": "cs_..."
}Errors — 400 (missing/blank Idempotency-Key, or amountCents outside 500–100000) / 401 / 403 (API-key principal, or an admin impersonating another customer) / 404 (N/A) / 429 / 500.
Get top-up status — GET /api/wallet/topup/{sessionId}
curl -H "Authorization: Bearer $KEY" \
https://portal.scrapewise.ai/api/scraper-api/api/wallet/topup/cs_test_a1b2c3Response (200)
{ "status": "APPLIED" }| Status | Meaning |
|---|---|
OPEN | Not paid yet, or payment still pending |
APPLIED | Paid and credited to the balance |
EXPIRED / FAILED | No money moved |
UNMATCHED | Paid but not attributable — contact support |
Errors — 400 (N/A) / 401 / 403 (N/A) / 404 (unknown session id, or another customer’s session — never treat 404 as “still pending”) / 429 / 500.
Insufficient balance — 402 Payment Required
Every billable launch (scraper run, group run, sample, URL / curl preview and the ad-hoc fetch endpoints) answers with 402 and the code WALLET_INSUFFICIENT_BALANCE when the wallet has neither balance nor free requests left:
{
"errors": [
{
"code": "WALLET_INSUFFICIENT_BALANCE",
"message": "Insufficient wallet balance — top up to continue"
}
]
}Switch on errors[0].code, not on the message. A run stopped mid-flight for the same reason reports stopReason: "WALLET_INSUFFICIENT_BALANCE" on its job-status row. Top up, then retry the call — nothing is lost.
Invoice history — GET /api/billing-history
curl -H "Authorization: Bearer $KEY" \
"https://portal.scrapewise.ai/api/scraper-api/api/billing-history?page=0&size=20&from=2026-01-01&to=2026-05-31"Paginated Stripe invoice history. Wallet top-ups and per-page charges live in the wallet ledger (GET /api/wallet/transactions), not here. Filters:
| Param | Type | Description |
|---|---|---|
page | int | Zero-indexed page (default 0) |
size | int | Page size (default 20) |
status | enum | PAID, OPEN, UNCOLLECTIBLE, VOID, DRAFT |
from | yyyy-MM-dd | Filter to invoices on or after this date (UTC) |
to | yyyy-MM-dd | Filter to invoices on or before this date (UTC) |
Response (200) — Spring Page<InvoiceDTO>:
{
"content": [
{
"id": "in_...",
"amount": 79,
"currency": "eur",
"status": "PAID",
"issuedAt": "2026-04-19T12:00:00Z",
"hostedInvoiceUrl": "https://invoice.stripe.com/i/..."
}
],
"totalElements": 14,
"totalPages": 1,
"size": 20,
"number": 0
}Errors — 400 (malformed from/to) / 401 / 403 (N/A) / 404 (N/A) / 429 / 500.
Legacy subscription endpoints
The /api/subscription/* endpoints still exist and still respond — they are superseded by the wallet, not removed. New integrations should use the wallet endpoints above; these are documented for clients still calling them.
POST /api/subscription/webhook is Stripe-internal (HMAC-signed) and intentionally excluded from these docs — clients should never call it. It is also the endpoint that credits wallet top-ups, so it stays in place.
Get current subscription — GET /api/subscription
curl -H "Authorization: Bearer $KEY" \
https://portal.scrapewise.ai/api/scraper-api/api/subscriptionResponse (200)
{
"plan": "PRO",
"status": "ACTIVE",
"currentPeriodEnd": "2026-06-19T23:59:59Z",
"scheduledPlan": null,
"payment": null
}scheduledPlan is non-null when a downgrade has been scheduled (effective at currentPeriodEnd). payment is non-null on create/change responses when the customer needs to be redirected to Stripe Checkout.
Errors — 400 (N/A) / 401 / 403 (N/A) / 404 (N/A) / 429 / 500. (The customer is read from the auth principal, so 400/403/404 don’t apply.)
Create subscription — POST /api/subscription/create
POST /api/subscription/create
Authorization: Bearer <key>
Content-Type: application/json
{ "plan": "PRO" }If the customer already has an ACTIVE subscription, the existing subscription is returned with no Checkout URL. Otherwise a new Stripe Checkout Session is created and the URL is returned in payment.url. Redirect the customer to that URL.
Response (200)
{
"plan": "PRO",
"status": "INACTIVE",
"payment": { "url": "https://checkout.stripe.com/c/pay/cs_..." }
}Errors — 400 (invalid plan) / 401 / 403 (N/A) / 404 (N/A) / 429 / 500.
Change plan — POST /api/subscription/change
POST /api/subscription/change
Authorization: Bearer <key>
{ "plan": "BUSINESS" }Switch the customer to a different plan. Behaviour depends on direction:
- Upgrade (higher
level): Stripe Checkout URL returned inpayment.url; redirect to collect new card / take immediate proration charge. - Downgrade:
scheduledPlanis set; the actual switch happens atcurrentPeriodEnd. No immediate charge. - No-op (same plan): existing subscription returned, no payment URL.
STARTERfromINACTIVE: free plan set inline, no Stripe round-trip.STARTERfromACTIVE: schedules downgrade toSTARTERat period end.
Errors — 400 (invalid plan / no active subscription) / 401 / 403 (N/A) / 404 (N/A) / 429 / 500.
Cancel subscription — POST /api/subscription/cancel
POST /api/subscription/cancel
Authorization: Bearer <key>Cancels the active subscription at the end of the current billing period. The customer retains access until currentPeriodEnd, then drops to STARTER.
Errors — 400 (no active subscription / no Stripe subscription id) / 401 / 403 (N/A) / 404 (N/A) / 429 / 500.
Cancel scheduled downgrade — POST /api/subscription/cancel-downgrade
POST /api/subscription/cancel-downgrade
Authorization: Bearer <key>Reverts a previously-scheduled downgrade — clears scheduledPlan and restores the Stripe subscription to the current plan’s price.
Errors — 400 (no downgrade scheduled) / 401 / 403 (N/A) / 404 (N/A) / 429 / 500.
Get product prices — GET /api/subscription/prices
curl https://portal.scrapewise.ai/api/scraper-api/api/subscription/pricesNo auth — public. Returns the Stripe subscription-product catalogue. These are legacy monthly plan prices, not what a pay-as-you-go customer is charged — see the price list above, or GET /api/wallet/prices.
Response (200)
[
{ "plan": "BASIC", "monthly": { "amount": 29, "currency": "eur" } },
{ "plan": "PRO", "monthly": { "amount": 79, "currency": "eur" } },
{ "plan": "BUSINESS", "monthly": { "amount": 199, "currency": "eur" } }
]Errors — 400 (N/A) / 401 (N/A — public) / 403 (N/A — public) / 404 (N/A) / 429 / 500. No auth-related codes (this endpoint is public).
See also
- Scrape prices — the per-page price catalogue
- Customer profile — profile + preferences