Jittator
Screenshots and PDFs of web pages or your HTML/Markdown, paid per call in sats (Lightning L402 or Cashu). No account.
Barındırılan MCP Sunucusu
npx add-mcp 'https://api.jittator.com/mcp'Claude Code, Codex, Cursor ve daha fazlasına kurulur
Dokümantasyon
API reference
Contents: Basics | Routes and prices | Parameters | How pages are rendered | Limits | Rate limits | Responses | Errors | Payment | L402 | Cashu | No Lightning invoice | Failures after payment | MCP server | Rules
Basics
- Base URL:
https://api.jittator.com - No account, no API key, no prepaid balance, no subscription. Every paid request carries its own payment.
- URL routes (
/v1/screenshot,/v1/screenshot/full,/v1/pdf) acceptGETwith query parameters orPOSTwith a JSON object body (Content-Type: application/json). Not both at once. - Render routes (
/v1/render/...) acceptPOSTwith a JSON object body only. HEADon a URL route returns a 402 challenge only: the same free check asGET, then402withWWW-AuthenticateandX-Cashuand an empty body. It never renders and never takes payment; credentials sent with it are ignored. The challenge may be shared and up to about 50 minutes old, so to pay, send aGETorPOSTand use the challenge it returns.HEADon a render route gets405.- Unknown parameters are rejected with
400 unknown_parameter, so a typo never silently falls back to a default. - Every request is checked for free before payment is asked for (parameters, URL, DNS, public address, robots.txt, queue). An invalid request gets its error straight away and no invoice. POST bodies are checked too.
- In a
GETquery, percent-encode the target URL if it has its own query string, for examplecurl -G --data-urlencode "url=https://example.com/a?b=c" https://api.jittator.com/v1/screenshot. - Machine-readable prices and accepted mints:
GET https://api.jittator.com/.well-known/l402-services(free JSON).
Routes and prices
| Route | Methods | Input | Output | Sats |
|---|---|---|---|---|
/v1/screenshot | GET, POST (HEAD: 402 only) | url + screenshot options | image, viewport only | 20 |
/v1/screenshot/full | GET, POST (HEAD: 402 only) | url + screenshot options | image, whole page | 30 |
/v1/pdf | GET, POST (HEAD: 402 only) | url + PDF options | 30 | |
/v1/render/screenshot | POST | html or markdown + screenshot options | image, viewport only | 20 |
/v1/render/screenshot/full | POST | html or markdown + screenshot options | image, whole page | 30 |
/v1/render/pdf | POST | html or markdown + PDF options | 30 | |
/healthz | GET | none | JSON status | free |
/.well-known/l402-services | GET | none | JSON: routes, prices, mints and their fees | free |
Prices are per call, fixed in sats. Cashu tokens from coinos or macadamia also cover that mint's swap fee (Cashu). Lightning routing fees are paid by the payer's wallet.
/healthz returns {"status": "ok", "queue": {"running": 0, "waiting": 0}}: the number of renders running and waiting right now.
Parameters
Target (URL routes)
| Name | Type | Default | Notes |
|---|---|---|---|
url | string | required | Absolute http or https URL of a public page, at most 2048 characters. Ports 80 and 443 only. No user:password@ part. The host must be a public domain name or a plain dotted-decimal IP address that is public. The fragment (#...) is dropped. |
Content (render routes)
Send exactly one of these.
| Name | Type | Notes |
|---|---|---|
html | string | An HTML document. At most 1,048,576 bytes (1 MiB) as UTF-8. Its JavaScript runs. |
markdown | string | Markdown, sanitized and placed in a fixed, plain page template first. At most 1,048,576 bytes (1 MiB) as UTF-8. |
Screenshot options
For /v1/screenshot, /v1/screenshot/full, /v1/render/screenshot and /v1/render/screenshot/full.
| Name | Type | Default | Range | Notes |
|---|---|---|---|---|
width | integer | 1280 | 320 to 1920 | Viewport width in CSS pixels. |
height | integer | 800 | 240 to 1920 | Viewport height in CSS pixels. On the /full routes the image is as tall as the page, up to 8000 pixels. |
format | string | png | png, jpeg, webp | Image format. |
quality | integer | 80 | 1 to 100 | Compression quality for jpeg and webp. |
wait_ms | integer | 0 | 0 to 10000 | Extra wait after the page loads, in milliseconds, for pages that draw late. |
PDF options
For /v1/pdf and /v1/render/pdf.
| Name | Type | Default | Range | Notes |
|---|---|---|---|---|
paper | string | letter | letter, legal, tabloid, a3, a4, a5 | Paper size. |
landscape | boolean | false | Landscape orientation. | |
print_background | boolean | true | Print background colours and images. | |
margin_in | number | 0.4 | 0 to 2 | Margin on all four sides, in inches. |
media | string | print | print, screen | CSS media type to emulate. Use screen to get what a browser window shows. |
wait_ms | integer | 0 | 0 to 10000 | Extra wait after the page loads, in milliseconds. |
In a GET query, booleans are true / false (also 1 / 0, yes / no). In a JSON body, use JSON booleans and numbers. Choice values (format, paper, media) are not case-sensitive.
How pages are rendered
- All screenshots (URL, HTML and Markdown) are taken with headless Chromium driven by Playwright. All PDFs are made with Chromium's print path (Gotenberg).
- A page counts as loaded at its
loadevent;wait_msadds time after that. - A browser error page is never returned as a result. If the target cannot be reached, answers with an HTTP error, or redirects to a non-public address, you get a JSON error instead (Errors).
- Full-page screenshots grow the viewport to the page's height (at most 8000 pixels) and capture that. Elements sized in
vhunits grow with it, so a100vhbanner can fill the image. - WebGL is turned off for screenshots, so WebGL canvases (some maps, 3D views) come out blank.
- Every request the browser makes, including images, styles, fonts, frames, scripts and redirects, is checked against the same public-address rules. Private, loopback and internal addresses are refused, and screenshots also load nothing from ports other than 80 and 443, so such resources are simply missing from the result.
- HTML you send may load public
httpandhttpsresources.file:URLs are blocked. In screenshots,data:andblob:URLs also work. In screenshots and PDFs alike, a script or meta refresh that navigates the page itself away fails the call with422 content_navigation_blocked. - Markdown screenshots and Markdown PDFs use different converters. They look the same, except that
~~strikethrough~~and bare URLs (not written as links) are not converted in screenshots.
Limits
| Limit | Value |
|---|---|
| Target URL length | 2048 characters |
| Target ports | 80 and 443 |
html or markdown content | 1,048,576 bytes (1 MiB) as UTF-8 |
| Whole JSON request body | 2,097,152 bytes (2 MiB) |
| Viewport | 320 to 1920 wide, 240 to 1920 high |
| Full-page screenshot height | 8000 pixels; taller pages are cut off and the response has X-Jittator-Truncated: true |
| PDF length | the first 50 pages |
| Output size | 26,214,400 bytes (25 MiB) |
Extra wait (wait_ms) | 10 seconds |
| Render time, screenshots | 20 seconds per job, including wait_ms |
| Render time, PDFs | 40 seconds per job (45 seconds hard limit) |
| Concurrency | One render at a time; at most 4 requests wait in the queue. When the queue is full, or the expected wait is over 20 seconds, the answer is 503 busy with Retry-After. This is checked before payment too. |
Rate limits
Applied by the gateway, per client IP address unless noted. These are the current values and may change.
| What | Limit |
|---|---|
Requests on /v1/ routes without a payment proof (validation and 402 challenges) | 20 per minute, bursts of 10 |
| The same, all clients together | 60 per minute, bursts of 10 |
| Requests carrying an L402 or Cashu proof | 120 per minute, bursts of 30 (a separate bucket, so unpaid traffic never uses it up) |
| The same, all clients together | 180 per minute, bursts of 40 |
| 402 challenges (new invoices) per route | 10 per minute |
| Retries and refunds after failed paid calls (below) | 10 per hour |
Blank-page retries and refunds (below), per IPv4 address or IPv6 /48 | 3 per 24 hours |
/healthz, /.well-known/l402-services, unknown paths | 60 per minute, bursts of 20 |
| Open connections | 8 |
Over a limit, the answer is 429 with a Retry-After header in seconds, usually with the body {"error":{"code":"rate_limited","message":"...","retry_after_s":10}}. Wait that long before trying again.
Responses
A successful call returns 200 with the file as the body:
Content-Type:image/png,image/jpeg,image/webporapplication/pdfContent-Disposition: inline; filename="screenshot.png"(with the format's extension) orfilename="document.pdf"Cache-Control: no-storeX-Jittator-Render-Ms: time spent on the request, in millisecondsX-Jittator-Truncated: true: only on full-page screenshots cut at 8000 pixels
Results are sent only to the caller and are not stored. Save the body; it cannot be fetched again.
Errors
Errors from the renderer are JSON:
{"error": {"code": "robots_disallowed", "message": "robots.txt on 'example.com' disallows JittatorBot for this path", "status": 403}}
busy errors also carry "retry_after_s" and a Retry-After header. The same codes are returned by the free check before payment and, for anything that only shows up while rendering, after payment. A renderer error after payment also carries its code in the X-Jittator-Error response header.
| Status | Code | Meaning |
|---|---|---|
| 400 | missing_parameter | url is missing, or not exactly one of html / markdown was sent |
| 400 | unknown_parameter | A parameter this route does not take |
| 400 | invalid_parameter | Wrong type or value not in the allowed list |
| 400 | parameter_out_of_range | Number outside its range |
| 400 | invalid_request | Parameters sent both in the query and in a POST body |
| 400 | invalid_json | POST body is not a JSON object |
| 400 | invalid_url | URL cannot be parsed, has no host, or contains whitespace, control characters or backslashes |
| 400 | url_too_long | URL longer than 2048 characters |
| 400 | scheme_not_allowed | Not http or https |
| 400 | userinfo_not_allowed | URL contains a user name or password |
| 400 | port_not_allowed | Port other than 80 or 443 |
| 400 | ambiguous_ip_encoding | Host written as a numeric, octal or hex IP; use dotted decimal |
| 400 or 403 | host_not_allowed | Host is not a fully qualified domain name (400), or is under a private or reserved suffix such as .local or .internal (403), or, for /v1/pdf, is one of Google's browser-service hosts (such as accounts.google.com) that the PDF renderer never contacts (403; screenshots of it work), or is one of Jittator's own servers (jittator.com and its subdomains, our name servers, or a name that resolves to their addresses; 403) |
| 403 | target_not_public | Host resolves to a private, internal or reserved address, or (screenshots) the page redirected to one while rendering |
| 403 | robots_disallowed | The site's robots.txt disallows JittatorBot for this path, or (while rendering) for a page it redirects or navigates to. Also when the site's robots.txt cannot be read because of a server error (5xx) or a network failure: RFC 9309 treats that as disallowing everything (rechecked after 1 minute) |
| 404 | not_found | No such route |
| 405 | method_not_allowed | For example GET or HEAD on a render route |
| 413 | content_too_large | html or markdown over 1 MiB |
| 413 | body_too_large | Request body over 2 MiB |
| 415 | unsupported_media_type | POST body not sent as application/json |
| 422 | dns_resolution_failed | The host name does not resolve |
| 422 | render_rejected | The PDF renderer refused the input |
| 422 | content_navigation_blocked | HTML or Markdown you sent tried to navigate the page away (screenshots and PDFs) |
| 422 | content_too_complex | Markdown you sent could not be converted within the time and memory limits |
| 500 | internal_error | Unexpected server error |
| 502 | target_http_error | The target page answered with an HTTP error (4xx or 5xx) |
| 502 | target_unreachable | The target could not be loaded: DNS failure, refused or timed-out connection, or (PDFs) a redirect to a non-public address |
| 502 | output_too_large | The result is over 25 MiB |
| 502 | blank_render | A URL screenshot or URL PDF came out blank (often a bot challenge, or a page that draws late); see blank pages |
| 502 | page_crashed | The page or content crashed the browser, for example by using up its memory |
| 502 | engine_error | The renderer itself failed |
| 502 | engine_unavailable | The renderer is unreachable |
| 503 | busy | Queue full, or too many robots.txt checks running at once; wait Retry-After seconds and retry |
| 504 | render_timeout | The render did not finish in time (20 seconds for screenshots, 40 for PDFs) |
Errors from the payment gateway
These come from the gateway in front of the renderer. Their JSON bodies have code and message but no status field. 401 and the Cashu 400 are not JSON: their body is a short HTML page (Content-Type: text/html, for example "401 Authorization Required") and the reason is only in the headers, so always read the status code and headers.
| Status | Code or header | Meaning |
|---|---|---|
| 400 | X-Cashu-Error header | Cashu token not acceptable: mint not on the list, wrong unit, worth too little after the mint's fee, the mint is at this gateway's custody limit, the mint is paused (Mint ... is not accepted right now (payouts from it are failing)), or the mint was dropped because it refused to pay out (Mint ... is no longer accepted (it refused or failed to pay out); it is also left out of the X-Cashu mint list). Pay with another mint or Lightning. The header gives the reason; the body is HTML. The token is not spent. |
| 401 | WWW-Authenticate: L402; X-Cashu-Error for Cashu | Proof not accepted: L402 credential invalid or unpaid, or still unused but for another route or method (a used or expired one gets a fresh 402 instead); Cashu token malformed or already spent. The body is HTML. Send the request again without a proof to get a fresh 402. A spent token gets X-Cashu-Error: Cashu token already spent; nothing was received, so there is no refund. |
| 402 | payment_required | No payment; see Payment. Without WWW-Authenticate it offers Cashu only (below) |
| 402 | payment_not_confirmed | Only for an invoice issued by a mint (the 402 had X-L402-Invoice-Issuer): that mint has not seen the payment yet. Wait Retry-After seconds and send the same request with the same Authorization header again. Do not pay again. There is no new challenge in this answer |
| 404 | not_found | Any path not listed above |
| 429 | rate_limited | Too many requests; wait Retry-After seconds (Rate limits) |
| 502 | validation_failed | The free check failed on the server side; no payment was requested |
| 503 | validation_unavailable | The free check is unavailable; retry shortly. No payment was requested |
| 503 | payment_backend_error | The payment step failed, for example a Cashu token could not be checked at its mint in time. Wait Retry-After seconds. A Cashu token sent with this request may already be spent: check it in your wallet before reusing it. For an invoice issued by a mint (X-L402-Invoice-Issuer) it means the payment could not be checked with that mint: the credential was not used, so send the same Authorization header again later and do not pay again. |
| 503 | mint_unavailable | The token's mint is not answering properly and is skipped for a short while. The token was not used. Retry after Retry-After seconds, or pay with another mint or Lightning |
| 503 | mint_busy | Too many payments are being checked with the token's mint at once. The token was not used. Retry after Retry-After seconds, or pay with another mint or Lightning |
Payment
Payment is handled by a gateway in front of the renderer. It first runs the free check: an invalid request gets its error and no invoice, and a proof sent with an invalid request is not used. A valid request with no payment gets 402 Payment Required offering both rails at once (Cashu only while no Lightning invoice can be made, see below). Pick one, pay, and send the same request again with the proof of payment. Every call needs its own payment.
HTTP/2 402
www-authenticate: L402 macaroon="AGIAJEem...", invoice="lnbc200n1..."
x-cashu: creqA...
content-type: application/json
{"error":{"code":"payment_required","message":"Pay the Lightning invoice in WWW-Authenticate (L402) and retry with Authorization: L402 <macaroon>:<preimage>, or retry with a Cashu token in X-Cashu (NUT-24)","price_sat":20}}
| Rail | Challenge (in the 402) | Proof (in the retry) | Status |
|---|---|---|---|
| L402 (Lightning) | WWW-Authenticate: L402 macaroon="...", invoice="..." | Authorization: L402 : | live |
| Cashu NUT-24 | X-Cashu: creqA... (NUT-18 payment request) | X-Cashu: cashuB... | live |
| MPP charge | not offered yet | planned | |
| x402 (Lightning) | not offered yet | planned | |
L402
- Send the request. Read the
WWW-Authenticateheader of the 402:L402 macaroon="<base64 macaroon>", invoice="<BOLT11 invoice>". - Check the invoice amount matches the route's price (20 or 30 sats), then pay it with any Lightning wallet. The wallet returns the payment preimage (64 hex characters).
- Send the same request again with
Authorization: L402 <macaroon>:<preimage>.
- The invoice and the macaroon expire 1 hour (3600 seconds) after the 402.
- The invoice usually comes from this service's own node. When that node cannot make one, it comes from one of the accepted Cashu mints and the 402 also has
X-L402-Invoice-Issuer: <mint URL>. Pay it and send the proof exactly the same way. The call is served once that mint confirms the payment. Until then the answer is402 payment_not_confirmed(the mint has not seen the payment yet) or503 payment_backend_error(the mint could not be asked); in both cases waitRetry-Afterseconds and send the sameAuthorizationheader again. Never pay a second time. - The macaroon is bound to the route and method it was issued for: a credential bought for
GET /v1/screenshotdoes not open/v1/pdforPOST /v1/screenshot. - A credential pays for one call. Sending it again after that call, or after it expired, gets a fresh
402challenge with a new invoice (counted against the same rate limits as a request without proof), except for the one retry after a failed call described under Failures after payment. The call it paid for was already served: pay the new invoice only if you want another call.
$ curl -si "https://api.jittator.com/v1/screenshot?url=https://example.com" | grep -i www-authenticate
www-authenticate: L402 macaroon="AGIAJEem...", invoice="lnbc200n1..."
# pay lnbc200n1... with your wallet; it returns the preimage
$ curl -o shot.png \
-H "Authorization: L402 AGIAJEem...:6f1c...preimage-hex" \
"https://api.jittator.com/v1/screenshot?url=https://example.com"
With Lightning Labs' lnget, which pays from your own node and retries automatically:
lnget --max-cost 30 -o shot.png "https://api.jittator.com/v1/screenshot/full?url=https://example.com"
lnget --max-cost 30 -X POST -H "Content-Type: application/json" \
-d '{"html": "<h1>Invoice 42</h1>", "paper": "a4"}' \
-o invoice.pdf https://api.jittator.com/v1/render/pdf
lnget keeps one token per host and sends it again on the next call there. A token pays for one call, so that call gets a fresh 402, which lnget pays before retrying: each call pays once, with no lnget tokens remove between calls. --max-cost still caps what lnget pays per call.
Cashu (NUT-24)
- Send the request. The 402 carries an
X-Cashuheader with a NUT-18 payment request (creqA...). Decoded, it gives the amounta(the route's list price), the unitu(sat) and the accepted mint URLsm. That list is the authoritative one. - Make a
cashuBtoken in sats from one of the listed mints, worth the amount due (see fees below). - Send the same request again with the token in the
X-Cashurequest header.
$ curl -si "https://api.jittator.com/v1/pdf?url=https://example.com" | grep -i x-cashu
x-cashu: creqA...
$ curl -o page.pdf -H "X-Cashu: cashuB..." "https://api.jittator.com/v1/pdf?url=https://example.com"
The gateway swaps the token at its mint before rendering, so an accepted token is spent and cannot be used again (401). Overpayment is kept; no change is returned.
Accepted mints and what a token must be worth:
| Mint | URL | Input fee (ppk) | Token for a 20 / 30 sat call |
|---|---|---|---|
| Minibits | https://mint.minibits.cash/Bitcoin (the path is case-sensitive) | 0 | 20 / 30 sats (list price) |
| 21mint | https://21mint.me | 0 | 20 / 30 sats (list price) |
| Routstr | https://mint.routstr.com | 0 | 20 / 30 sats (list price) |
| Flash | https://forge.flashapp.me | 0 | 20 / 30 sats (list price) |
| cashu.chat | https://mint.cashu.chat | 0 | 20 / 30 sats (list price) |
| coinos | https://mint.coinos.io | 100 | list price plus the swap fee, which depends on the number of proofs (usually 1 or 2 sats): make the token with "include fees" (cashu send -f) |
| macadamia | https://mint.macadamia.cash | 150 | list price plus the swap fee, which depends on the number of proofs (usually 1 or 2 sats): make the token with "include fees" (cashu send -f) |
The swap fee is set by the mint (NUT-02): the sum of the token's per-proof input fees in ppk, divided by 1000 and rounded up, in sats. It depends on the number of proofs: 21 and 31 sat tokens made of the fewest proofs carry a 1 sat fee, while a 20 sat call paid with many small proofs can need 22 sats. A token is accepted when its value minus the fee covers the price. The payment request's a is the list price, because NUT-18 has no field for a fee, so at coinos or macadamia always create the token with your wallet's "include fees" option (for example cashu send -f 20 in Nutshell, or includeFees in cashu-ts): the wallet then adds the fee for the proofs it actually picks. For example, a 31 sat macadamia token made of 7 proofs carries a 2 sat fee and falls 1 sat short of a 30 sat call.
A token that falls short is refused with 400 and an X-Cashu-Error header that says by how much, for example:
X-Cashu-Error: Cashu token amount insufficient after the mint's 1000 msat input fee: 19000 msat (required: 20000 msat)
The token is not spent; send a larger one. The m list in the 402 and /.well-known/l402-services (which also lists each mint's input_fee_ppk) are always current.
When no Lightning invoice can be made
If neither this service's node nor a fallback mint can issue an invoice, the 402 offers Cashu only: it has X-Cashu but no WWW-Authenticate, and a Retry-After header (seconds, at most 60). Pay with a Cashu token as usual, or wait Retry-After seconds and send the request again for a Lightning invoice.
HTTP/2 402
x-cashu: creqA...
retry-after: 30
content-type: application/json
{"error":{"code":"payment_required","message":"Lightning invoices are unavailable right now. Pay with a Cashu token in X-Cashu (NUT-24), or retry after Retry-After seconds for a Lightning invoice.","price_sat":20,"rails":["cashu"],"lightning_retry_after_s":30}}
Failures after payment
There are no balances, so nothing is credited to an account. What happens when a call whose payment was accepted then fails with 422 or any 5xx depends on what caused it. The response headers below say which case applied.
Failures on our side: made good once
When the failure is this service's own (engine_error, engine_unavailable, busy, internal_error, a render_timeout because the renderer itself hung, or a bare 502 or 504 from the gateway when the renderer is down or too slow), the gateway gives back what you paid, once per payment: an L402 retry or a Cashu refund token (below).
Failures caused by the requested page: no retry or refund
A page or content that fails would fail the same way on every attempt, so these failures are final and the proof is spent: target_http_error, target_unreachable, target_not_public, dns_resolution_failed, robots_disallowed, render_rejected, content_navigation_blocked, content_too_complex, output_too_large, page_crashed, and render_timeout when the page or your content kept the browser busy past the time limit. Most of them are caught by the free check before you pay; after payment they show up only when the page fails while it is being rendered. A 422 or 5xx of this kind carries X-L402-Retry: target_error or X-Cashu-Refund-Error: target_error. A 403 after payment (for example a page that redirects to a private address while being captured) carries neither header and is not retried or refunded either. Blank pages have a small allowance of their own (below).
L402: one retry with the same proof
The failed response carries X-L402-Retry:
| Value | Meaning |
|---|---|
allowed | Send the same Authorization: L402 <macaroon>:<preimage> again, on the same route and method, before the macaroon expires (1 hour after the 402). It is accepted once more. You may change the parameters, for example a larger wait_ms. |
target_error | The requested page caused the failure (above); no retry, the proof is spent |
blank_allowance_exhausted | The page rendered blank and your blank-page allowance is used up (below) |
exhausted | This proof already had its retry; a further attempt gets a fresh 402 (pay again for another call) |
rate_limited | Your IP address has used its 10 retries and refunds for the hour |
unavailable | No retry could be granted (internal store unavailable) |
Cashu: a refund token
The failed response carries X-Cashu-Refund: cashuB..., a token from the same mint worth the route's price (not the swap fee you added; for a blank page, 75% of the price, below). Treat it as money: redeem it into your own wallet straight away (for example cashu receive <token>), because until you do, anyone who sees it can take it. Do not log it. You can also pay the retry with it directly in X-Cashu; at coinos or macadamia, add the swap fee again. If no refund is made, the response has X-Cashu-Refund-Error instead:
| Value | Meaning |
|---|---|
target_error | The requested page caused the failure (above); no refund |
refund_failed | The refund token could not be created (for example the mint did not answer). It is recorded; ask for a manual refund through the contact form. |
budget_exhausted | The service-wide budget for automatic refunds this hour (for all clients, or for this mint) is used up. It is recorded; ask through the contact form. |
blank_allowance_exhausted | The page rendered blank and your blank-page allowance is used up (below) |
blank_budget_exhausted | The page rendered blank and the service-wide daily or hourly cap for blank-page refunds is reached |
rate_limited | Your IP address has used its 10 retries and refunds for the hour |
already_refunded | A refund for this token was already issued |
unavailable | No refund could be issued (internal store unavailable) |
Blank pages: a small courtesy allowance
A URL screenshot or URL PDF that comes out blank fails with 502 blank_render. That is caused by the page too (often a bot challenge, sometimes a page that draws late), but it gets a courtesy remedy up to 3 times per client per 24 hours (a sliding window, both rails together):
- Cashu: a refund token for 75% of the price, rounded down to whole sats: 15 of 20 sats or 22 of 30 sats come back, and the service keeps 5 or 8. The response also has
X-Cashu-Refund-Amount(sats returned) andX-Cashu-Refund-Kept(sats kept). - L402: the usual one retry with the same proof (
X-L402-Retry: allowed).
A client is an IPv4 address or an IPv6 /48, so clients behind a shared address (or in one /48) share the allowance. X-Jittator-Blank-Refunds-Left shows how many are left. When the allowance is used up, the answer is blank_allowance_exhausted (in X-L402-Retry or X-Cashu-Refund-Error) with X-Jittator-Blank-Refunds-Left: 0 and X-Jittator-Blank-Refunds-Reset (unix time in seconds when the next one becomes available). If the service-wide cap on blank-page refunds is reached (Cashu only), the answer is X-Cashu-Refund-Error: blank_budget_exhausted. A page that draws late is worth retrying with a larger wait_ms; a bot challenge usually stays blank.
Other cases
- Errors from the free check (any
4xxor503 busybefore a 402) cost nothing and use up no proof. Fix the request; do not pay first. 401on a retry means the proof was not accepted; do not pay again without finding out why. A Cashu token that was already spent (X-Cashu-Error: Cashu token already spent) gets no refund.402 payment_not_confirmedafter paying an invoice issued by a mint: that mint has not seen the payment yet. Send the sameAuthorizationheader again afterRetry-Afterseconds; never pay again.503 payment_backend_errormeans the payment step itself failed; waitRetry-Afterseconds and try again. Check a Cashu token in your wallet before reusing it. For an invoice issued by a mint, retry with the sameAuthorizationheader.503 mint_unavailableand503 mint_busy: the Cashu token was not used. Send it again afterRetry-Afterseconds, or pay with another mint or Lightning.
MCP server
The same service is an MCP server for agents: https://api.jittator.com/mcp (Streamable HTTP, no account, no API key). Add it as a remote server, for example claude mcp add --transport http jittator https://api.jittator.com/mcp. It speaks MCP 2026-07-28 and the earlier 2025-11-25, 2025-06-18 and 2025-03-26 revisions, keeps no session and answers each message with one JSON object.
A 2026-07-28 client calls server/discover (no initialize) and marks every request as 2026-07-28: params._meta carries "io.modelcontextprotocol/protocolVersion": "2026-07-28" and "io.modelcontextprotocol/clientCapabilities" (an object), and the headers MCP-Protocol-Version: 2026-07-28 and Mcp-Method (the method name) match the body. A request without that version in params._meta or the header is treated as an earlier revision, where server/discover answers -32601 (method not found).
| Tool | Arguments | Returns | Sats |
|---|---|---|---|
screenshot | exactly one of url, html, markdown; full_page, width, height, format, quality, wait_ms; payment | an image (MCP image content) | 20, or 30 with full_page |
pdf | exactly one of url, html, markdown; paper, landscape, print_background, margin_in, media, wait_ms; payment | the PDF as an embedded resource (application/pdf) | 30 |
quote | tool (screenshot or pdf) and its arguments | whether the call would be accepted, its price and the accepted mints | free |
Options and limits are those of the HTTP routes above (Parameters, Limits), with one difference: full-page screenshots default to jpeg here, to keep long pages small; pass format to choose.
Paying inside MCP:
- Call
screenshotorpdfwithout payment. The request is checked for free first; an invalid one returns its error code and no invoice. A valid one returns a tool errorpayment_requiredwith the price, a Lightning invoice, a macaroon and a Cashu payment request (also instructuredContent). - Pay the invoice with any Lightning wallet; it gives you the 64-hex preimage. Call the same tool again with the same arguments plus
l402set to<macaroon>:<preimage>. The proof is bound to the route: keep the same tool,full_pagesetting and kind of source (url, or html/markdown). Or call withcashuset to a token worth the price from an accepted mint (see Cashu; no first call needed). - A failure after payment follows Failures after payment: a Cashu refund token or an L402 retry comes back in the tool result.
Each tool call is an ordinary call to the HTTP route behind it, from your address: the same free check, prices, rate limits and refunds apply. The MCP endpoint itself allows 60 messages a minute per client address.
Rules
- Public
httpandhttpspages only. Private, internal and reserved addresses are refused, including via DNS names or redirects that lead to them. - No logins, cookies or credentials, and no way to pass them.
- robots.txt is checked under the token
JittatorBotfor the requested URL before the page is loaded, and again for each page the renderer is sent on to (redirects, script and meta-refresh navigations; for screenshots also frames and popups). It follows RFC 9309: a missing robots.txt (4xx) allows everything, one that cannot be read (5xx, network failure) disallows everything on that site; see the bot page. The free check sees only the requested URL, so a redirect to a disallowed page is found while rendering, after payment, and ends as403 robots_disallowedwith no retry or refund. Pages are fetched with the user agentJittatorBot/0.1 (+https://jittator.com/bot). See the bot page. - No CAPTCHA solving or anti-bot bypass. A page that blocks the bot comes back as
502 target_http_errorif the site answers with an error status, as502 blank_renderif it draws nothing, or as whatever else the site shows the bot. - Results go back only to the caller and are not stored.
- Payment records (Lightning payment hash or a fingerprint of the Cashu proofs, amount, rail, mint, route and time) are kept as long as needed for accounting and tax obligations. Server logs and contact-form messages are kept for 90 days. See privacy.
See also: terms, acceptable use, contact and abuse reports.