# getJSON > getJSON reads any content a user or an agent points at, a web page or a file, and returns JSON in your schema, validated. Try and use it without an account. ```bash curl https://getjson.de/https://x.com/jack/status/20 ``` That one GET reads the link with no key and no account, and returns it as JSON for 0 credits. Add a JSON Schema and the answer has your fields, validated. getJSON reads a PDF, an image (PNG, JPEG, WebP), plain text, CSV, a post on X, Bluesky, Mastodon or Hacker News, a GitHub repository, issue, release or file, a public Google Doc or Sheet, or any web link. An agent gets a trial key with no human and no email; a full account starts with 5 free credits. One extraction costs 1–3 credits. The public pages are static HTML, and the app behind the login is JavaScript. Everything an agent needs is in the plain files below. - [OpenAPI 3.1 spec](https://getjson.de/openapi.json): every REST endpoint, machine-readable - [API reference (markdown)](https://getjson.de/docs.md): the same docs as https://getjson.de/docs; that URL also answers `Accept: text/markdown` - [Developer portal](https://getjson.de/developers): API keys, quickstart, the trial sandbox, limits and the versioning policy - [Agent skill](https://getjson.de/SKILL.md): a drop-in SKILL.md for Claude Code and other skill-aware agents - [MCP server](https://getjson.de/mcp): Streamable HTTP, described by its [Server Card](https://getjson.de/mcp/server-card) and in [/.well-known/mcp.json](https://getjson.de/.well-known/mcp.json) - Discovery: [API catalog](https://getjson.de/.well-known/api-catalog) (RFC 9727) and [AI catalog](https://getjson.de/.well-known/ai-catalog.json) ## Read a link No key and no account. One GET returns the link as JSON: ```bash curl https://getjson.de/https://x.com/jack/status/20 # → { "source": { … }, "content": { "text": "just setting up my twttr", … }, "meta": null, "wall": null, "result": null } ``` The answer is the envelope of [Sources](#sources). It costs nothing and uses no AI. - Write the link after the host: `https://getjson.de/https://example.com/page`, `https://getjson.de/example.com/page` or `https://getjson.de/r/`. A link with its own query string goes URL-encoded: `https://getjson.de/r/https%3A%2F%2Fexample.com%2Fpage%3Fid%3D1`. The short form without a scheme does not work for a host that ends like a file name (`.md`, `.py`, `.zip`, …); write `https://` then. - The query parameters `schema`, `instructions`, `model`, `max_chars` and `full` belong to the call. Every other parameter stays on the link. - `Accept: text/markdown` returns the title and the text as Markdown. The headers `X-Source-Provider`, `X-Source-Url` and `X-Truncated` carry the rest. - The text is cut at 20,000 characters and `content.truncated` is `true`. Set `?max_chars=N` (up to 500,000) or `?full=1` to read more. - A file from your disk: `curl -F "file=@report.pdf" https://getjson.de/` returns the same envelope. Without a key a file may have 5 MB. - 20 reads per minute and IP without a key (100 with a key). A `429` carries `Retry-After`. - Errors are JSON: `{ "error": "…", "code": "…", "hint": "…" }`. The `hint` names the next step. A wall is no error: the answer is `200` with `wall` filled. ### One page per source Each page shows the call, a recorded answer, the schema call and the MCP call. Every page also answers `Accept: text/markdown`. - [Read an X post as JSON without login](https://getjson.de/read/x) - [Read a Bluesky post as JSON](https://getjson.de/read/bluesky) - [Read a Mastodon post as JSON](https://getjson.de/read/mastodon) - [Read a Hacker News thread with comments as JSON](https://getjson.de/read/hacker-news) - [Read a GitHub issue, README or file as JSON](https://getjson.de/read/github) - [Read a public Google Doc or Sheet](https://getjson.de/read/google-docs) - [Read the text of a PDF from a URL](https://getjson.de/read/pdf-url) - [Read a web article as Markdown or JSON](https://getjson.de/read/article) - [When a link cannot be read: the walls](https://getjson.de/read/walls) ## Extract JSON Add a JSON Schema to a read and the answer has `result` filled, in the shape of your schema and validated against it. Without a key this works 3 times a day per network; with a key, `POST /v1/extract` takes a file or a link. ### With a schema, no key The same GET with a `schema` parameter: ```bash curl -G https://getjson.de/https://x.com/jack/status/20 \ --data-urlencode 'schema={"type":"object","properties":{"author":{"type":"string"},"text":{"type":"string"},"likes":{"type":"integer"}},"required":["author","text","likes"]}' # → { "source": { … }, "content": { … }, "meta": null, "wall": null, # "result": { "text": "just setting up my twttr", "likes": 311267, "author": "jack (@jack)" } } ``` The answer above was recorded from getjson.de on 2026-10-27. - Send the schema as `?schema=` (URL-encoded, at most 16,000 characters) or in the header `X-JSON-Schema`. A shared schema goes by its id in front of the link: `https://getjson.de/s_7Kq2hnAb9Z/news.ycombinator.com/item?id=1`. The link keeps its own query string. `?schema=s_7Kq2hnAb9Z` works too. An unknown id answers `404` with `"code": "schema_not_found"`; an id in the path and a schema in `X-JSON-Schema` answer `400` with `"code": "schema_conflict"`. Add `?instructions=` (at most 2,000 characters) for guidance without a schema. `POST /` takes the same fields next to `file`. - The call waits for the answer, up to 60 seconds. It runs the `budget` mode (1 credit of AI spend). Another `model` answers `403` with `"code": "keyless_mode"`. - Each network (a /24 for IPv4, a /64 for IPv6) has 3 schema calls per day. The next one answers `429` with `"code": "keyless_limit"`. When the daily budget of all keyless calls is used up, the answer is `503` with `"code": "keyless_budget"`. Both hints name the [trial key](#trial-key-no-human-no-email). - `result` is validated against the schema. A result that breaks it, also after one retry, answers `422` with `"code": "schema_mismatch"` and `errors`, a list of `{ "path", "message" }` (at most 20). A schema that does not compile answers `400` with `"code": "invalid_schema"` and uses no call. - A link that has a wall and nothing to read comes back at once, and it uses no call. A call that started the model counts, also when it fails or runs out of time. - Nothing is kept for you. The document and the job are deleted after 1 hour. - With a key, send a schema call to `POST /v1/extract`. `GET /?schema=…` with a key answers `400` with `"code": "use_extract"`. ### With a key Extraction is asynchronous: start a job, then poll it. ```bash curl https://getjson.de/v1/extract \ -H "Authorization: Bearer $GETJSON_API_KEY" \ -F "file=@invoice.pdf" # → 202 { "jobId": "…", "status": "pending" } curl https://getjson.de/v1/jobs/ \ -H "Authorization: Bearer $GETJSON_API_KEY" # → { "status": "completed", "result": { … } } ``` Poll every 1–2 seconds until `status` is `completed` or `failed`. Form fields for `POST /v1/extract`: - `file` — the document (max 50 MB). Or `url` — an X post ID or any http(s) link. Pass exactly one. With `model=auto` and no `schema`, `schema_id` or `instructions`, a `url` job returns the source data without AI, for 0 credits. See [Sources](#sources). - `model` — optional. A mode (`auto`, `standard`, `premium`, `budget`, `ocr`, `multilingual`) or a model id from `GET /v1/models`. Default `claude-sonnet` (1 credit). `auto` picks a model per document; it reserves up to 3 credits and refunds what it does not use. - `schema` — optional inline JSON Schema (as a JSON string, at most 16,000 characters) for the output shape. Every account may use it, a free account with 5 credits and a trial key too; it costs the credits of the model. The schema is read as JSON Schema draft 2020-12, or draft-07 when `$schema` names it; `format` is not checked. A schema that does not compile, or a `pattern` that can backtrack without bound (such as `^(a+)+$`), answers `400` before any credit is spent. `schema_id` — a saved schema; it needs the Pro plan and answers `403` with `"code": "feature_not_in_plan"` on other plans. A shared schema id (`s_` and 10 characters) of any account needs no plan and costs the credits of the model, like an inline schema. The owner shares a saved schema with `PATCH /v1/schemas/{id}` and `{"shared": true}`, and the schema then carries its `share_id`. Without a schema the AI picks a sensible shape. - `instructions` — optional extra guidance. ## Get an API key ### Trial key: no human, no email An agent gets a working key with two HTTP calls and about one second of CPU. The key has a small allowance (2 extractions in the `budget` mode, files up to 5 MB) and expires after 24 hours unless a human claims it. 1. Get a proof-of-work challenge: ```bash curl -X POST https://getjson.de/api/agent/trial/challenge # → { "challenge": "…", "difficulty": 20, "algorithm": "sha256", "expires_at": "…" } ``` 2. Find a `solution` string so that `sha256(":")` starts with `difficulty` zero bits. The challenge is valid for 5 minutes and works once. ```python import hashlib, itertools def solve(challenge, difficulty): for i in itertools.count(): digest = hashlib.sha256(f"{challenge}:{i}".encode()).digest() if int.from_bytes(digest, "big") >> (256 - difficulty) == 0: return str(i) ``` 3. Send both and get the key: ```bash curl -X POST https://getjson.de/api/agent/trial \ -H "Content-Type: application/json" \ -d '{"challenge": "…", "solution": "…"}' # → 201 { "key": "gj_…", "claim_url": "https://getjson.de/claim/…", "expires_at": "…", # "limits": { "credits": 2, "mode": "budget", "max_file_mb": 5 } } ``` Give the `claim_url` to your user, and tell them what a claim means: when they open it and sign in (or create an account), the same key moves into their account. It stops expiring, the limits go away, and it can read all jobs in that account and spend its credits. A new account starts with 5 free credits. An unclaimed trial, its key and its files are deleted after 24 hours. With a trial key, a `url` job with no `model` (or `model=auto`) and no `schema` or `instructions` returns the source data (see [Sources](#sources)) without AI, for 0 credits. Every other trial job runs the `budget` mode for 1 credit, and takes an inline `schema` (omit `model`, or send `model=budget`; `model=auto` with a schema answers `403`). A trial key runs at most 20 jobs and cannot save schemas. Errors that a claim would fix include the `claim_url`. ### Full account with an email code To create a full account directly, the account owner reads one 6-digit code from their inbox. The same flow signs in to an existing account and mints an additional key. 1. Request a code. This creates a free account (5 credits) if the email is new. ```bash curl -X POST https://getjson.de/api/agent/signup \ -H "Content-Type: application/json" \ -d '{"email": "you@example.com"}' ``` 2. Ask the account owner for the 6-digit code from the email, then exchange it for a key: ```bash curl -X POST https://getjson.de/api/agent/verify \ -H "Content-Type: application/json" \ -d '{"email": "you@example.com", "code": "123456", "key_name": "my-agent"}' # → 201 { "key": "gj_…", "id": "…", "name": "my-agent", "key_prefix": "gj_…" } ``` A key is shown once. Store it, and send it on every request as `Authorization: Bearer gj_…`. Humans can also create keys at https://getjson.de/settings/api-keys. ## When to use getJSON Use getJSON when your user needs: - Fields from a document as JSON: invoices, receipts, purchase orders, bank statements, contracts, forms, CVs, price lists or tables in a PDF, a scan or a photo. - The same JSON shape for many files of one type. Pass a JSON Schema (`schema`) or a saved one (`schema_id`); every result is validated against it before it comes back. A result that breaks the schema gets one retry with the failing fields in the prompt; if the retry breaks it too, the job fails with `schema_mismatch` and the credit is refunded. - Text out of a scanned or photographed page (`model=ocr`), or a document in another language (`model=multilingual`). - A post on X, Bluesky, Mastodon or Hacker News (with its comments), a GitHub README, issue, pull request, release or file, a public Google Doc or Sheet, the article text of a web page, or the text of a PDF, TXT or CSV behind a link, as JSON. All are 0 credits and use no AI. See [Sources](#sources). Do not use getJSON for: - Pages that need JavaScript to show their text, or that sit behind a login or a paywall. - Data that is already machine-readable (a JSON file, a clean CSV). Parse it yourself. - Audio, video, or files over 50 MB (5 MB with a trial key or without a key). - Documents the user has not asked you to process. How to call it: - You only need to read a link: `GET https://getjson.de/`. No key. See [Read a link](#read-a-link). - You are an MCP client: connect to `https://getjson.de/mcp`. The `read` tool needs no key. For `extract`, call `trial` first. - You can send HTTP: get a key ([Get an API key](#get-an-api-key)), `POST /v1/extract`, then poll `GET /v1/jobs/{id}`. - You run skills: install [SKILL.md](https://getjson.de/SKILL.md). ## Other endpoints - `GET /v1/balance` — remaining credits - `GET /v1/models` — models and their credit cost - `GET /v1/jobs?limit=&offset=` — recent jobs - `GET https://getjson.de/` — read a link without a key, see [Read a link](#read-a-link) - `GET /v1/sources/x/{idOrUrl}` — an X post as JSON without AI, 0 credits - `GET /v1/sources/link/{url}` — the metadata of any web page (URL-encoded) as JSON without AI, 0 credits - `GET|POST /v1/schemas`, `GET|PATCH|DELETE /v1/schemas/{id}` — saved schemas ## Sources A `url` job reads the link with the provider for its source. The 0-credit result (`model=auto`, no `schema`, `schema_id` or `instructions`) depends on the source: | Link | Read through | 0-credit result | |---|---|---| | `x.com//status/`, `twitter.com/…`, or a post ID | FxTwitter, then the X embed endpoint | the X post object (`platform: "x"`, `postId`, `author`, `textContent`, `engagement`, `linkedContent`, …) | | `bsky.app/profile//post/` | the public Bluesky API | envelope, `kind: "post"` | | `/@/` on a Mastodon server | ActivityPub, then the instance API | envelope, `kind: "post"` | | `news.ycombinator.com/item?id=` | the HN API, comments from HN search | envelope, `kind: "thread"` (up to 200 comments, 4 levels) or `"post"` | | `github.com//` | the GitHub API | envelope, `kind: "repo"`, README as Markdown | | `github.com///issues/`, `/pull/` | the GitHub API | envelope, `kind: "issue"`, with up to 30 comments | | `github.com///releases/tag/`, `/releases/latest` | the GitHub API | envelope, `kind: "document"` | | `github.com///blob//`, `raw.githubusercontent.com/…` | the raw file | envelope, `kind: "file"` (text files up to 2 MB) | | `docs.google.com/document/d/`, `/spreadsheets/d/` (`?gid=` for a tab), `/presentation/d/` | the public export (text, CSV) | envelope, `kind: "document"` | | a link to a PDF, PNG, JPEG, WebP, TXT or CSV file | a download (50 MB, 5 MB with a trial key) | envelope, `kind: "file"`: the text of a PDF with a text layer, a TXT or a CSV; `wall.kind = "no_text"` for a scan or an image | | any other web page | the article reader (Readability), plus the page `` | envelope, `kind: "article"` with the article as Markdown, or `kind: "page"` with only the head when the page has no article body; `meta` is the page metadata object (see [Web links](#web-links)) | The envelope: ```json { "source": { "url": "…", "finalUrl": "…", "kind": "post", "platform": "bluesky", "provider": "bluesky", "fetchedAt": "…", "cached": false }, "content": { "title": null, "text": "…", "author": { "name": "…", "handle": "@…", "url": "…" }, "publishedDate": "…", "language": "en", "wordCount": 40, "engagement": { "likes": 1963, "replies": 163 }, "hashtags": [], "mentions": [], "linkedContent": [], "truncated": false }, "meta": null, "wall": null, "result": null } ``` - `content.text` is Markdown (plain text for a post, CSV for a sheet). `engagement` holds the counts the source has (`likes`, `replies`, `reposts`, `points`, `stars`, …). - `wall` is set, and `content` is `null`, when the content cannot be read: `login` for a page that answers `401` or a Google document that is not shared publicly, `bot_protection` for a page that answers `403` or a Cloudflare challenge (`cf-mitigated: challenge`), `no_text` for a scanned PDF, an image or a page with no text at all (a JavaScript shell; its head metadata stays in `meta`), `unsupported` for a GitHub file that is not text, `too_large` for a file over the size limit. The hint in `wall.hint` names the next step. - A page that answers `429` or `5xx` is an error, not a wall: `502` with `code: "source_unavailable"` and a hint to retry. When the page sent `Retry-After`, the answer carries it and the hint names the seconds. - With a `schema`, `instructions` or another model, the AI reads the source text and bills the model as usual. For a file link it reads the file itself, so a scan or a photo works with `model=ocr` or a schema. - GitHub answers `503` when its rate limit is used up; try again later. A deleted or missing post, issue, document or file answers `404`. A file over the size limit answers `413`; a file type that is not listed above answers `415`. ## Web links `GET /v1/sources/link/{url}` (link URL-encoded) and the MCP tool `fetch_link_metadata` read only the `` of a page and return its metadata object, for any link, with no job and 0 credits. `POST /v1/extract` reads the article instead (see [Sources](#sources)) and puts the same metadata object in the envelope's `meta`. ```bash curl "https://getjson.de/v1/sources/link/$(jq -rn --arg u 'https://bun.sh/blog/bun-v1.2' '$u|@uri')" \ -H "Authorization: Bearer $GETJSON_API_KEY" # → { "link": { "title": "Bun 1.2 | Bun Blog", "canonicalUrl": "https://bun.com/blog/bun-v1.2", … } } ``` The metadata object (`link` on the source endpoint, `meta` in the envelope): - `url` (as requested), `finalUrl` (after redirects), `canonicalUrl` - `title` (``, else `og:title`, else `twitter:title`), `description` (`meta description`, else `og:description`, else `twitter:description`) - `siteName`, `type` (`og:type`), `lang` (`<html lang>`, else `og:locale`), `author`, `publishedTime`, `modifiedTime`, `keywords` (array), `themeColor` - `image` (the preview image), `favicon` (the first `rel=icon`, else `apple-touch-icon`, else `/favicon.ico`), `icons` (every icon with `rel`, `sizes`, `type`) - `openGraph` (every `og:*` tag without the prefix, plus `article:*`, `profile:*`, `book:*`, `music:*`, `video:*`), `twitter` (every `twitter:*` tag without the prefix). A repeated tag is an array. - `jsonLd` (up to 10 parsed JSON-LD blocks), `oembedUrl` Every URL in the object is absolute. A missing value is `null`, or `[]` for `keywords`. Rules and limits: - `http` and `https` only, default ports 80 and 443 only, no user name or password in the link. A link without a scheme gets `https://`. - Private, loopback, link-local and other internal addresses are refused, also when a redirect points to one. - 10 seconds in total, at most 5 redirects, the first 1 MB of the page. Only `text/html` and `application/xhtml+xml` (`POST /v1/extract` reads the file types above as files). - Pass X post URLs to `/v1/sources/x/{idOrUrl}`. `/v1/sources/link` answers 400 for them. `POST /v1/extract` routes every link to its source by itself; `/v1/sources/link` always returns the page metadata, also for a Bluesky or GitHub link. - `/v1/sources/link` is limited to 30 requests per minute per key. A result is cached for 10 minutes, a 404 for 5 minutes. Errors when reading a link: `400` invalid link, unknown host or internal address; `404` the page or file answers 404 or 410; `413` the file is too large; `415` not an HTML page (`/v1/sources/link`) or not a supported file type (`POST /v1/extract`); `502` the page is unreachable, answers another error, or redirects too often; `504` no answer within 10 seconds (30 seconds for a file download). On `POST /v1/extract` these come back before a job starts, with no credits spent. ## MCP Connect any MCP client to `https://getjson.de/mcp` (Streamable HTTP) with the header `Authorization: Bearer gj_…`. Tools: `read`, `trial`, `extract`, `get_job`, `list_jobs`, `get_balance`, `list_models`, `list_schemas`, `create_schema`, `fetch_x_post`, `fetch_link_metadata`. `read` works without a key: it takes `url`, optional `schema`, `schema_id` (a shared schema, `s_…`) or `instructions` (the keyless schema call, same limits as the HTTP read) and `max_chars`, and returns the envelope. With a key, send schema calls to `extract`, which takes `url`, `file_url` or `file_base64`, `schema`, `schema_id`, `instructions`, `model` and `max_chars`. `read`, `extract` and `get_job` cut a source text at `max_chars` (default 20,000, at most 500,000); `content.truncated` says so. `list_jobs` shows a 500-character preview. `fetch_x_post` and `fetch_link_metadata` stay with their old output shapes; `read` replaces both. Without a key, the `trial` tool runs the trial flow above (call it once for the challenge, then with the solution), and `signup` and `verify` run the email flow. ```bash claude mcp add --transport http getjson https://getjson.de/mcp \ --header "Authorization: Bearer gj_…" ``` ## Errors Errors are JSON: `{ "error": "…" }`. The keyless read adds `code` and `hint` (see [Read a link](#read-a-link)). `401` missing or invalid key; `402` "Insufficient credits" before any work when the balance cannot cover the extraction (a 0-credit `url` job never gets it); `403` with `"code": "feature_not_in_plan"` when the plan lacks a feature, or `"code": "keyless_mode"` for a mode other than `budget` without a key; `429` rate limited, see `Retry-After`. A job whose result breaks the schema, also after one retry, ends `failed` with `"error_code": "schema_mismatch"` and `errors` (a list of `{ "path", "message" }`, at most 20), and its credits are refunded. Fix the schema at those paths, or add `instructions`. Reading a link adds `404`, `413`, `415`, `502`, `503` and `504` (see [Sources](#sources) and [Web links](#web-links)). A page or file that does not exist answers `404`; with `Accept: text/markdown` the body is Markdown that links back to this guide. ## Rate limits and versioning - 100 requests per minute per API key; `/v1/sources/*` allows 30. A read without a key (`GET /<url>`, `POST /`) allows 20 per minute and IP. Every API response carries `RateLimit-Policy` and `RateLimit` (draft-ietf-httpapi-ratelimit-headers-11), for example `RateLimit-Policy: "default";q=100;w=60` and `RateLimit: "default";r=87;t=42`, plus `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`. Slow down when `r` gets low. A `429` adds `Retry-After` in seconds. - The API version is in the path: `/v1`. Inside `/v1` there are only additions: new endpoints, new optional fields, new enum values. Ignore fields you do not know. A breaking change ships as `/v2` next to `/v1`. - Before a `/v1` endpoint goes away, its responses carry `Deprecation` (RFC 9745), `Sunset` (RFC 8594) and `Link: <…>; rel="deprecation"`, at least 6 months before the sunset date. ## Optional - [Plans and credits](https://getjson.de/settings/subscription) (needs a login) - [TypeScript SDK](https://www.npmjs.com/package/@getjson/sdk): `npm install @getjson/sdk`