Getting Started
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. Reading a link needs no key: curl https://getjson.de/https://x.com/jack/status/20. Every /v1 request needs an API key in the Authorization header.
curl https://getjson.de/v1/balance \
-H "Authorization: Bearer gj_your_api_key"Authentication
Create an API key in Settings. Include it in every request:
Authorization: Bearer gj_your_api_keyFor Agents
Agents read /llms.txt, /openapi.json and /SKILL.md. An agent can get a trial key with no email and no human: it solves a short proof-of-work and receives a key with a small allowance and a claim link. Open the claim link and sign in to move the key into your account.
curl -X POST https://getjson.de/api/agent/trial/challenge
# { "challenge": "...", "difficulty": 20 } → find a solution with
# sha256("<challenge>:<solution>") starting with 20 zero bits
curl -X POST https://getjson.de/api/agent/trial \
-H "Content-Type: application/json" \
-d '{"challenge": "...", "solution": "..."}'
# Response (201): { "key": "gj_...", "claim_url": "https://getjson.de/claim/...", ... }Or connect an MCP client to https://getjson.de/mcp:
claude mcp add --transport http getjson https://getjson.de/mcp \
--header "Authorization: Bearer gj_your_api_key"The read tool needs no key: it reads a link without AI, for 0 credits, and returns the text and the metadata. The older tools fetch_link_metadata and fetch_x_post keep their output shapes. read, extract and get_job cut a source text at max_chars (default 20,000).
Endpoints
/:urlRead any link as JSON without a key. Free: 0 credits, no AI. Add a schema and the answer has the extraction.
curl https://getjson.de/https://x.com/jack/status/20
# As Markdown
curl -H "Accept: text/markdown" https://getjson.de/https://example.com/article
# A file from disk (5 MB without a key)
curl -F "file=@report.pdf" https://getjson.de/
# With a schema, still without a key (waits up to 60 s)
curl -G https://getjson.de/https://example.com/pricing \
--data-urlencode 'schema={"type":"object","properties":{"plans":{"type":"array","items":{"type":"string"}}}}'
# Response: { "source": {...}, "content": {...}, "meta": null, "wall": null, "result": null }The path holds the link: /https://example.com/page, /example.com/page or /r/<url>. A link with its own query string goes URL-encoded. The query parameters schema, instructions, model, max_chars and full belong to the call.
content.text is cut at 20,000 characters (max_chars up to 500,000, or full=1). A wall is no error: the answer is 200 with wall filled and content null. login: the page answers 401, or a Google document is not public. bot_protection: the page answers 403 or a Cloudflare challenge. no_text: a scan, an image or a page with no text, such as a JavaScript shell. unsupported: a GitHub file that is not text. too_large: a file over the limit. A page that answers 429 or 5xx is a 502 error with a hint to retry, and the page's Retry-After when it sent one.
Without a key: 20 reads per minute and IP. A schema call runs the budget mode, and each network has 3 per day. After that the answer is 429 with a hint to get a trial key (keyless_limit). A model other than budget answers 403 (keyless_mode). The schema may have 16,000 characters, the instructions 2,000. Nothing is kept: the document and the job are deleted after 1 hour. With a key, send a schema call to POST /v1/extract. Errors are { error, code, hint }.
/v1/extractUpload a file or pass an X post URL or any web link and start JSON extraction.
curl https://getjson.de/v1/extract \
-H "Authorization: Bearer gj_your_api_key" \
-F "file=@invoice.pdf" \
-F "model=claude-sonnet" \
-F 'schema={"type":"object","properties":{"total":{"type":"number"}}}'
# Or extract from an X (Twitter) post
curl https://getjson.de/v1/extract \
-H "Authorization: Bearer gj_your_api_key" \
-F "url=https://x.com/jack/status/20"
# Or read any web page as an article (0 credits with model=auto)
curl https://getjson.de/v1/extract \
-H "Authorization: Bearer gj_your_api_key" \
-F "url=https://example.com/blog/post" \
-F "model=auto"
# Response: { "jobId": "uuid", "status": "pending" }file (required unless url is set) - PDF, PNG, JPEG, WebP, TXT, or CSV
url (required unless file is set) - An X post ID or any http(s) link. X, Bluesky, Mastodon and Hacker News posts, GitHub repositories, issues, pull requests, releases and files, and public Google Docs, Sheets and Slides are read from their source. Public content only. For a post with an X article, the full article text is included; pass the post URL, not the /i/article/ URL. A link to a PDF, PNG, JPEG, WebP, TXT or CSV file is downloaded (50 MB, 5 MB with a trial key); any other web page is read as an article, with its metadata in meta. With model=auto and no schema, schema_id or instructions, the result comes from the source without AI and costs 0 credits, with any balance: the X post, or an envelope (source, content, meta, wall) for every other source. A scan or an image has no text and returns wall.kind = "no_text". With a schema or instructions, the AI reads the source text, or the file itself for a file link. Read errors (400, 404, 413, 415, 502, 503, 504) return before a job starts and cost nothing.
model (optional) - claude-sonnet (default), claude-opus, claude-haiku, gpt-5-mini, gemini-3-pro, gemini-3-flash, qwen3-vl-235b. Call /v1/models for capabilities and pricing.
schema (optional) - Inline JSON Schema object for structured output, at most 16,000 characters. Every account may use it
schema_id (optional) - ID of a saved schema (Pro plan) to use instead of inline schema
instructions (optional) - Additional extraction instructions
/v1/jobs/:idGet job status and result.
curl https://getjson.de/v1/jobs/job_uuid \
-H "Authorization: Bearer gj_your_api_key"
# Response when completed:
# {
# "id": "uuid",
# "status": "completed",
# "result": { "total": 150.00, "vendor": "Acme Corp" },
# "tokens_used": 1234,
# "processing_time_ms": 3200
# }/v1/jobsList your recent jobs.
curl "https://getjson.de/v1/jobs?limit=10&offset=0" \
-H "Authorization: Bearer gj_your_api_key"limit (optional, max 100) - Number of jobs to return
offset (optional) - Pagination offset
/v1/balanceGet your current credit balance.
curl https://getjson.de/v1/balance \
-H "Authorization: Bearer gj_your_api_key"
# Response: { "balance": 42 }/v1/modelsList available AI models.
curl https://getjson.de/v1/models \
-H "Authorization: Bearer gj_your_api_key"
# Response: { "models": [
# { "id": "claude-sonnet", "name": "Claude Sonnet 5.5", "creditsPerRequest": 1 },
# ...
# ] }/v1/sources/x/:idOrUrlFetch an X (Twitter) post as normalised JSON, without AI extraction. Free: costs 0 credits.
curl https://getjson.de/v1/sources/x/20 \
-H "Authorization: Bearer gj_your_api_key"
# Or pass a URL-encoded post URL
curl https://getjson.de/v1/sources/x/https%3A%2F%2Fx.com%2Fjack%2Fstatus%2F20 \
-H "Authorization: Bearer gj_your_api_key"
# Response:
# { "post": {
# "id": "20",
# "url": "https://x.com/jack/status/20",
# "author": { "name": "jack", "handle": "jack" },
# "createdAt": "2006-03-21T20:50:14.000Z",
# "lang": "en",
# "text": "just setting up my twttr",
# "likes": 300000,
# "replies": 12000,
# "inReplyTo": null,
# "media": [],
# "article": null,
# "quoted": null
# } }idOrUrl (required) - Post ID or URL-encoded X post URL. Public posts only.
For a post with an X article, article is { title, previewText, content }. content is the full article as Markdown, or null if only the preview is available. Article URLs (/i/article/:id) return 400; pass the URL of the post that contains the article.
Limited to 30 requests per minute per API key. Results are cached: 60 seconds for posts younger than 1 hour, 1 hour for older posts, 5 minutes for posts that are not found (404).
/v1/sources/link/:urlFetch the metadata of any web page as JSON, without AI extraction. Free: costs 0 credits.
curl https://getjson.de/v1/sources/link/https%3A%2F%2Fexample.com%2Fblog%2Fpost \
-H "Authorization: Bearer gj_your_api_key"
# Response:
# { "link": {
# "platform": "web",
# "url": "https://example.com/blog/post",
# "finalUrl": "https://example.com/blog/post",
# "canonicalUrl": "https://example.com/blog/post",
# "title": "Hello world",
# "description": "Our first post.",
# "siteName": "Example",
# "type": "article",
# "lang": "en",
# "author": "Ada",
# "publishedTime": "2026-09-01T10:00:00Z",
# "modifiedTime": null,
# "keywords": ["news"],
# "themeColor": "#112233",
# "image": "https://example.com/og.png",
# "favicon": "https://example.com/favicon-32.png",
# "icons": [{ "url": "https://example.com/favicon-32.png", "rel": "icon", "sizes": "32x32", "type": "image/png" }],
# "openGraph": { "title": "Hello world", "image": "https://example.com/og.png", ... },
# "twitter": { "card": "summary_large_image", ... },
# "jsonLd": [{ "@type": "Article", ... }],
# "oembedUrl": null
# } }url (required) - URL-encoded http or https link. All URLs in the response are absolute. openGraph and twitter hold every og:* and twitter:* tag without the prefix; repeated tags are arrays.
The fetcher reads the page's <head>. It does not run JavaScript and does not return the article text. title falls back to og:title, description to og:description, favicon to apple-touch-icon and then /favicon.ico. jsonLd holds up to 10 parsed blocks.
Only http and https on ports 80 and 443, no credentials in the link. 10 seconds in total, at most 5 redirects, the first 1 MB of the page. Private and internal addresses are refused on every redirect hop. X post links go to /v1/sources/x.
Errors: 400 invalid link, unknown host or internal address; 404 page not found; 415 not an HTML page; 502 unreachable, another error status or too many redirects; 504 no answer within 10 seconds.
Limited to 30 requests per minute per API key. Results are cached for 10 minutes, 404s for 5 minutes.
/v1/schemasList your saved schemas.
curl https://getjson.de/v1/schemas \
-H "Authorization: Bearer gj_your_api_key"
# Response: { "schemas": [
# { "id": "uuid", "name": "Invoice", "schema": {...}, "is_default": true, ... }
# ] }/v1/schemasCreate a new saved schema for reuse across extractions.
curl https://getjson.de/v1/schemas \
-H "Authorization: Bearer gj_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Invoice",
"description": "Extract invoice fields",
"schema": {"type":"object","properties":{"total":{"type":"number"},"vendor":{"type":"string"}}},
"is_default": false
}'
# Response (201): { "schema": { "id": "uuid", "name": "Invoice", ... } }name (required) - Schema name
schema (required) - JSON Schema object
description (optional) - Schema description
is_default (optional) - Set as default schema
/v1/schemas/:idGet a specific saved schema.
curl https://getjson.de/v1/schemas/schema_uuid \
-H "Authorization: Bearer gj_your_api_key"
# Response: { "schema": { "id": "uuid", "name": "Invoice", "schema": {...}, ... } }/v1/schemas/:idUpdate a saved schema.
curl -X PATCH https://getjson.de/v1/schemas/schema_uuid \
-H "Authorization: Bearer gj_your_api_key" \
-H "Content-Type: application/json" \
-d '{"name": "Updated Invoice", "is_default": true}'
# Response: { "schema": { "id": "uuid", "name": "Updated Invoice", ... } }/v1/schemas/:idDelete a saved schema.
curl -X DELETE https://getjson.de/v1/schemas/schema_uuid \
-H "Authorization: Bearer gj_your_api_key"
# Response: { "status": "deleted" }TypeScript SDK
Install the SDK for easy integration:
npm install @getjson/sdkimport { GetJsonClient } from "@getjson/sdk"
const client = new GetJsonClient({
apiKey: "gj_your_api_key",
})
// Extract with inline schema
const file = new File([pdfBuffer], "invoice.pdf", { type: "application/pdf" })
const job = await client.extractAndWait(file, {
model: "claude-sonnet",
schema: { type: "object", properties: { total: { type: "number" } } },
})
console.log(job.result) // { total: 150.00, vendor: "Acme Corp" }
// Create a reusable schema
const { schema } = await client.createSchema({
name: "Invoice",
schema: { type: "object", properties: { total: { type: "number" } } },
})
// Use saved schema by ID
const job2 = await client.extractAndWait(file, {
schemaId: schema.id,
})Webhooks
Configure webhooks in Settings to receive notifications when jobs complete. Payloads are signed with HMAC-SHA256.
// Webhook payload
{
"event": "job.completed",
"job_id": "uuid",
"timestamp": "2026-03-09T12:00:00.000Z",
"data": {
"status": "completed",
"result": { ... },
"tokens_used": 1234,
"processing_time_ms": 3200
}
}
// Verify signature (Node.js example)
const crypto = require("crypto")
const signature = crypto
.createHmac("sha256", webhookSecret)
.update(requestBody)
.digest("hex")
if (signature === req.headers["x-webhook-signature"]) {
// Valid webhook
}Error Handling
All errors return a JSON body with an error field:
// 401 Unauthorized
{ "error": "Invalid or missing API key" }
// 400 Bad Request
{ "error": "file or url field is required" }
// 500 Internal Server Error
{ "error": "Internal server error" }Rate limits: 100 requests per minute per API key, 30 for /v1/sources, 20 per minute and IP for a read without a key. Every API response carries RateLimit-Policy and RateLimit (IETF draft 11) plus RateLimit-Limit, -Remaining and -Reset. A 429 adds Retry-After in seconds.
Versioning: the version is in the path (/v1), and /v1 only gets additions. A breaking change ships as /v2. Before a /v1 endpoint goes away, it sends Deprecation and Sunset headers at least 6 months ahead.