Jittator

Screenshots and PDFs of web pages or your HTML/Markdown, paid per call in sats (Lightning L402 or Cashu). No account.

Hosted MCP Server

npx add-mcp 'https://api.jittator.com/mcp'

Installs into Claude Code, Codex, Cursor and more

Documentation

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) accept GET with query parameters or POST with a JSON object body (Content-Type: application/json). Not both at once.
  • Render routes (/v1/render/...) accept POST with a JSON object body only.
  • HEAD on a URL route returns a 402 challenge only: the same free check as GET, then 402 with WWW-Authenticate and X-Cashu and 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 a GET or POST and use the challenge it returns. HEAD on a render route gets 405.
  • 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 GET query, percent-encode the target URL if it has its own query string, for example curl -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

RouteMethodsInputOutputSats
/v1/screenshotGET, POST (HEAD: 402 only)url + screenshot optionsimage, viewport only20
/v1/screenshot/fullGET, POST (HEAD: 402 only)url + screenshot optionsimage, whole page30
/v1/pdfGET, POST (HEAD: 402 only)url + PDF optionsPDF30
/v1/render/screenshotPOSThtml or markdown + screenshot optionsimage, viewport only20
/v1/render/screenshot/fullPOSThtml or markdown + screenshot optionsimage, whole page30
/v1/render/pdfPOSThtml or markdown + PDF optionsPDF30
/healthzGETnoneJSON statusfree
/.well-known/l402-servicesGETnoneJSON: routes, prices, mints and their feesfree

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)

NameTypeDefaultNotes
urlstringrequiredAbsolute 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.

NameTypeNotes
htmlstringAn HTML document. At most 1,048,576 bytes (1 MiB) as UTF-8. Its JavaScript runs.
markdownstringMarkdown, 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.

NameTypeDefaultRangeNotes
widthinteger1280320 to 1920Viewport width in CSS pixels.
heightinteger800240 to 1920Viewport height in CSS pixels. On the /full routes the image is as tall as the page, up to 8000 pixels.
formatstringpngpng, jpeg, webpImage format.
qualityinteger801 to 100Compression quality for jpeg and webp.
wait_msinteger00 to 10000Extra wait after the page loads, in milliseconds, for pages that draw late.

PDF options

For /v1/pdf and /v1/render/pdf.

NameTypeDefaultRangeNotes
paperstringletterletter, legal, tabloid, a3, a4, a5Paper size.
landscapebooleanfalseLandscape orientation.
print_backgroundbooleantruePrint background colours and images.
margin_innumber0.40 to 2Margin on all four sides, in inches.
mediastringprintprint, screenCSS media type to emulate. Use screen to get what a browser window shows.
wait_msinteger00 to 10000Extra 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 load event; wait_ms adds 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 vh units grow with it, so a 100vh banner 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 http and https resources. file: URLs are blocked. In screenshots, data: and blob: URLs also work. In screenshots and PDFs alike, a script or meta refresh that navigates the page itself away fails the call with 422 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

LimitValue
Target URL length2048 characters
Target ports80 and 443
html or markdown content1,048,576 bytes (1 MiB) as UTF-8
Whole JSON request body2,097,152 bytes (2 MiB)
Viewport320 to 1920 wide, 240 to 1920 high
Full-page screenshot height8000 pixels; taller pages are cut off and the response has X-Jittator-Truncated: true
PDF lengththe first 50 pages
Output size26,214,400 bytes (25 MiB)
Extra wait (wait_ms)10 seconds
Render time, screenshots20 seconds per job, including wait_ms
Render time, PDFs40 seconds per job (45 seconds hard limit)
ConcurrencyOne 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.

WhatLimit
Requests on /v1/ routes without a payment proof (validation and 402 challenges)20 per minute, bursts of 10
The same, all clients together60 per minute, bursts of 10
Requests carrying an L402 or Cashu proof120 per minute, bursts of 30 (a separate bucket, so unpaid traffic never uses it up)
The same, all clients together180 per minute, bursts of 40
402 challenges (new invoices) per route10 per minute
Retries and refunds after failed paid calls (below)10 per hour
Blank-page retries and refunds (below), per IPv4 address or IPv6 /483 per 24 hours
/healthz, /.well-known/l402-services, unknown paths60 per minute, bursts of 20
Open connections8

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/webp or application/pdf
  • Content-Disposition: inline; filename="screenshot.png" (with the format's extension) or filename="document.pdf"
  • Cache-Control: no-store
  • X-Jittator-Render-Ms: time spent on the request, in milliseconds
  • X-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.

StatusCodeMeaning
400missing_parameterurl is missing, or not exactly one of html / markdown was sent
400unknown_parameterA parameter this route does not take
400invalid_parameterWrong type or value not in the allowed list
400parameter_out_of_rangeNumber outside its range
400invalid_requestParameters sent both in the query and in a POST body
400invalid_jsonPOST body is not a JSON object
400invalid_urlURL cannot be parsed, has no host, or contains whitespace, control characters or backslashes
400url_too_longURL longer than 2048 characters
400scheme_not_allowedNot http or https
400userinfo_not_allowedURL contains a user name or password
400port_not_allowedPort other than 80 or 443
400ambiguous_ip_encodingHost written as a numeric, octal or hex IP; use dotted decimal
400 or 403host_not_allowedHost 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)
403target_not_publicHost resolves to a private, internal or reserved address, or (screenshots) the page redirected to one while rendering
403robots_disallowedThe 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)
404not_foundNo such route
405method_not_allowedFor example GET or HEAD on a render route
413content_too_largehtml or markdown over 1 MiB
413body_too_largeRequest body over 2 MiB
415unsupported_media_typePOST body not sent as application/json
422dns_resolution_failedThe host name does not resolve
422render_rejectedThe PDF renderer refused the input
422content_navigation_blockedHTML or Markdown you sent tried to navigate the page away (screenshots and PDFs)
422content_too_complexMarkdown you sent could not be converted within the time and memory limits
500internal_errorUnexpected server error
502target_http_errorThe target page answered with an HTTP error (4xx or 5xx)
502target_unreachableThe target could not be loaded: DNS failure, refused or timed-out connection, or (PDFs) a redirect to a non-public address
502output_too_largeThe result is over 25 MiB
502blank_renderA URL screenshot or URL PDF came out blank (often a bot challenge, or a page that draws late); see blank pages
502page_crashedThe page or content crashed the browser, for example by using up its memory
502engine_errorThe renderer itself failed
502engine_unavailableThe renderer is unreachable
503busyQueue full, or too many robots.txt checks running at once; wait Retry-After seconds and retry
504render_timeoutThe 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.

StatusCode or headerMeaning
400X-Cashu-Error headerCashu 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.
401WWW-Authenticate: L402; X-Cashu-Error for CashuProof 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.
402payment_requiredNo payment; see Payment. Without WWW-Authenticate it offers Cashu only (below)
402payment_not_confirmedOnly 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
404not_foundAny path not listed above
429rate_limitedToo many requests; wait Retry-After seconds (Rate limits)
502validation_failedThe free check failed on the server side; no payment was requested
503validation_unavailableThe free check is unavailable; retry shortly. No payment was requested
503payment_backend_errorThe 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.
503mint_unavailableThe 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
503mint_busyToo 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}}
RailChallenge (in the 402)Proof (in the retry)Status
L402 (Lightning)WWW-Authenticate: L402 macaroon="...", invoice="..."Authorization: L402 :live
Cashu NUT-24X-Cashu: creqA... (NUT-18 payment request)X-Cashu: cashuB...live
MPP chargenot offered yetplanned
x402 (Lightning)not offered yetplanned

L402

  1. Send the request. Read the WWW-Authenticate header of the 402: L402 macaroon="<base64 macaroon>", invoice="<BOLT11 invoice>".
  2. 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).
  3. 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 is 402 payment_not_confirmed (the mint has not seen the payment yet) or 503 payment_backend_error (the mint could not be asked); in both cases wait Retry-After seconds and send the same Authorization header 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/screenshot does not open /v1/pdf or POST /v1/screenshot.
  • A credential pays for one call. Sending it again after that call, or after it expired, gets a fresh 402 challenge 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)

  1. Send the request. The 402 carries an X-Cashu header with a NUT-18 payment request (creqA...). Decoded, it gives the amount a (the route's list price), the unit u (sat) and the accepted mint URLs m. That list is the authoritative one.
  2. Make a cashuB token in sats from one of the listed mints, worth the amount due (see fees below).
  3. Send the same request again with the token in the X-Cashu request 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:

MintURLInput fee (ppk)Token for a 20 / 30 sat call
Minibitshttps://mint.minibits.cash/Bitcoin (the path is case-sensitive)020 / 30 sats (list price)
21minthttps://21mint.me020 / 30 sats (list price)
Routstrhttps://mint.routstr.com020 / 30 sats (list price)
Flashhttps://forge.flashapp.me020 / 30 sats (list price)
cashu.chathttps://mint.cashu.chat020 / 30 sats (list price)
coinoshttps://mint.coinos.io100list 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)
macadamiahttps://mint.macadamia.cash150list 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:

ValueMeaning
allowedSend 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_errorThe requested page caused the failure (above); no retry, the proof is spent
blank_allowance_exhaustedThe page rendered blank and your blank-page allowance is used up (below)
exhaustedThis proof already had its retry; a further attempt gets a fresh 402 (pay again for another call)
rate_limitedYour IP address has used its 10 retries and refunds for the hour
unavailableNo 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:

ValueMeaning
target_errorThe requested page caused the failure (above); no refund
refund_failedThe 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_exhaustedThe 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_exhaustedThe page rendered blank and your blank-page allowance is used up (below)
blank_budget_exhaustedThe page rendered blank and the service-wide daily or hourly cap for blank-page refunds is reached
rate_limitedYour IP address has used its 10 retries and refunds for the hour
already_refundedA refund for this token was already issued
unavailableNo 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) and X-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 4xx or 503 busy before a 402) cost nothing and use up no proof. Fix the request; do not pay first.
  • 401 on 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_confirmed after paying an invoice issued by a mint: that mint has not seen the payment yet. Send the same Authorization header again after Retry-After seconds; never pay again.
  • 503 payment_backend_error means the payment step itself failed; wait Retry-After seconds and try again. Check a Cashu token in your wallet before reusing it. For an invoice issued by a mint, retry with the same Authorization header.
  • 503 mint_unavailable and 503 mint_busy: the Cashu token was not used. Send it again after Retry-After seconds, 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).

ToolArgumentsReturnsSats
screenshotexactly one of url, html, markdown; full_page, width, height, format, quality, wait_ms; paymentan image (MCP image content)20, or 30 with full_page
pdfexactly one of url, html, markdown; paper, landscape, print_background, margin_in, media, wait_ms; paymentthe PDF as an embedded resource (application/pdf)30
quotetool (screenshot or pdf) and its argumentswhether the call would be accepted, its price and the accepted mintsfree

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:

  1. Call screenshot or pdf without payment. The request is checked for free first; an invalid one returns its error code and no invoice. A valid one returns a tool error payment_required with the price, a Lightning invoice, a macaroon and a Cashu payment request (also in structuredContent).
  2. Pay the invoice with any Lightning wallet; it gives you the 64-hex preimage. Call the same tool again with the same arguments plus l402 set to <macaroon>:<preimage>. The proof is bound to the route: keep the same tool, full_page setting and kind of source (url, or html/markdown). Or call with cashu set to a token worth the price from an accepted mint (see Cashu; no first call needed).
  3. 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 http and https pages 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 JittatorBot for 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 as 403 robots_disallowed with no retry or refund. Pages are fetched with the user agent JittatorBot/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_error if the site answers with an error status, as 502 blank_render if 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.