From a key to Markdown in two calls.
getctx is one HTTPS API at api.getctx.sh. Authenticate with a bearer key from the console. Every call is paid in prepaid credits, and every price is known before the call runs.
- Create an account and make an API key in the console.
- Top up: a pack, or any amount from $5. New accounts start at 0 credits.
- Call
status(free), thenread.
$ export GETCTX_API_KEY=<your key from the console>$ curl https://api.getctx.sh/v1/status \ -H "Authorization: Bearer $GETCTX_API_KEY"$ curl https://api.getctx.sh/v1/read \ -H "Authorization: Bearer $GETCTX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/handbook"}'§0.1Status
GET /v1/status is free. It tells an agent whether the key works, how many credits its organization can spend, and the price of every capability, before it spends any. Call it before a large job, such as a crawl.
{ "organization": "…", "key": { "id": "…", "scopes": ["parse"] }, "credits": { "spendable": 12000 }, "prices": { "read": {"fetch":1,"browser":2,"document":2}, "brand": {"profile":10,"styleguide":15,"cached":1}, "search": 7, … }}§0.2Agents
Coding agents already make HTTP calls. Put the key in their environment as GETCTX_API_KEY and a paragraph like this in their instructions file (CLAUDE.md, AGENTS.md, .cursor/rules).
## Reading the webUse getctx (https://api.getctx.sh) to read any URL, PDF or GitHub file.Send "Authorization: Bearer $GETCTX_API_KEY" on every call.Call GET /v1/status first: it is free and shows credits and prices.POST /v1/read {"url": "…"}. A long page answers with its table ofcontents; read one part with {"url": "<ref>", "section": "@2"}: free.Reference: https://api.getctx.sh/openapi.json and https://getctx.sh/llms.txt§1read
POST /v1/read turns a URL into Markdown an agent can read: web pages (rendered in a browser when they need JavaScript), GitHub files, directories, READMEs, issues and pull requests, and PDF and office documents up to 10 MiB.
| urlstring | An http(s) URL, github://owner/repo[@ref]/path, or a ctx://cache/<key> a read returned. |
| budget.maxTokensinteger · 2,000–16,000 | The window. A document that fits (8k estimated tokens by default) comes back whole; a longer one as its table of contents. |
| sectionstring | What to read, by the locators the contents print: @1, @1-@3, @1.2,@3.1-@5.1, parts @1.2#0. @0 is what precedes the first heading. |
| tocboolean | Answer with the table of contents (of section, when given). |
| freshboolean | Skip the cache. |
| noCleanboolean | Keep the rendered content as it is, without cleanup. |
| renderobject | Browser options from an allowlist: waits, viewport, user agent, what the page loads. Never a script. |
| credentialsobject | headers, cookies, authenticate. The cached copy is then your organization’s alone. |
The answer carries text (the document, or its table of contents with each entry's size), ref (a ctx://cache/ address of the whole document), view.kind (full, section or toc), source, diagnostics, cached, charged and tier.
$ curl https://api.getctx.sh/v1/read \ -H "Authorization: Bearer $GETCTX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/handbook", "budget": {"maxTokens": 8000}}'{ "text": "…table of contents, each entry with its size…", "ref": "ctx://cache/<key>", "view": { "kind": "toc" }, "charged": 1, "tier": "fetch", "cached": false, …}$ curl … -d '{"url": "ctx://cache/<key>", "section": "@2.1"}'{ "text": "…", "view": { "kind": "section" }, "charged": 0, "cached": true, … }A fresh read costs 1–2 credits by how the page was obtained (fetch 1, browser 2, document 2). Reading a section of ref, a cache hit, empty output and every failure cost nothing.
§2parse
parse turns your files into Markdown in three steps: upload, start a Job at the price the upload answered, then follow the Job. A file is at most 30 MB and, for a PDF, 60 pages. Formats: pdf, docx, doc, docm, odt, rtf, pptx, ppt, pptm, odp, xlsx, xls, xlsm, xlsb, ods, epub, csv, html, htm, xhtml.
| POST /v1/files | Upload (multipart file); answers the price of parsing it.Free |
| POST /v1/parse | { fileId, maxCredits } starts a Job at that price.1 credit per PDF page, 1 credit per other file; 0 when cached |
| GET /v1/parse/{jobId} | queued, running, succeeded (artifact, charged) or failed (error, charged 0).Free |
$ curl https://api.getctx.sh/v1/files \ -H "Authorization: Bearer $GETCTX_API_KEY" \ -F file=@report.pdf{ "fileId": "…", "name": "report.pdf", "format": "pdf", "pages": 12, "credits": 12, "cached": false, … }$ curl https://api.getctx.sh/v1/parse \ -H "Authorization: Bearer $GETCTX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"fileId": "…", "maxCredits": 12}'{ "jobId": "…", "credits": 12 }$ curl https://api.getctx.sh/v1/parse/<jobId> \ -H "Authorization: Bearer $GETCTX_API_KEY"{ "status": "succeeded", "artifact": { "markdown": "…", "pages": […] }, "charged": 12 }A Job is charged only when it succeeds; a failed one says why in error.code (unreadable_document, provider_unavailable with retryAfterSeconds, or cache_expired). An upload is kept 7 days, a Job and its artifact 30.
§3web
Search, links, cleaned scrapes, screenshots, schema extraction and crawls. The page calls take url, and optionally render, credentials and fresh; every paid call takes maxCredits. Answers carry text, diagnostics, ref (read on with read), cached and charged; a cache hit costs nothing.
| POST /v1/web/search | Web search with excerpts. query, type (code, news, company, people, paper), num, domain, exclude, since, text.7 credits up to 10 results, +1 each after |
| POST /v1/web/links | A page’s link map; visibleOnly, internalOnly.1 credit |
| POST /v1/web/scrape | Elements chosen by CSS selector, cleaned; textOnly or raw.1 credit |
| POST /v1/web/screenshot | Up to three viewport slices, or fullPage; selector, scroll. Image URLs need your key.1 credit |
| POST /v1/web/json | prompt and a JSON Schema; a field the page lacks is null.3 credits |
| POST /v1/web/crawl | A bounded crawl: limit (default 10, up to 100 pages), depth (default 1, up to 10), include, exclude.Reserves the limit; 1 credit per fetched page |
| GET /v1/web/crawl/{jobId} | Status, counts by page state, fetched pages with a cursor. /export: every page as Markdown or JSON.Free |
| DELETE /v1/web/crawl/{jobId} | Cancel; pages already fetched stay charged.Free |
$ curl https://api.getctx.sh/v1/web/search \ -H "Authorization: Bearer $GETCTX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "Durable Objects alarms", "type": "code", "maxCredits": 7}'{ "text": "…", "diagnostics": [], "ref": "ctx://cache/<key>", "charged": 7, "cached": false }§4brand
POST /v1/brand with domain or url, and depth: profile (identity, logos, colors, a semantic profile; 10 credits) or styleguide (those, plus the visual system and font files; 15 credits). A brand read in the last 30 days costs 1. It is a Job, charged only when it succeeds.
GET /v1/brand/{jobId}/events streams it as Server-Sent Events, one per stage as it finishes: render, identity, logos, colors, at depth styleguide styleguide and fonts, then profile; then done (charged) or failed. Send Last-Event-ID to resume. GET /v1/brand/{jobId} returns the brand so far instead.
$ curl https://api.getctx.sh/v1/brand \ -H "Authorization: Bearer $GETCTX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"domain": "example.com", "depth": "profile"}'{ "jobId": "…", "domain": "example.com", "credits": 10, "cached": false }$ curl -N https://api.getctx.sh/v1/brand/<jobId>/events \ -H "Authorization: Bearer $GETCTX_API_KEY"id: 1event: renderdata: {"url": "…", "finalUrl": "…", "status": 200, "screenshot": {…}}…event: donedata: {"charged": 10}§5Page Watch and Search Feed
A Page Watch asks whether a known page changed; a Search Feed what is new for a query. getctx checks each on its schedule (intervalMinutes: a watch 15 to 43,200, a feed 60 to 43,200; default 1,440), charges each check, and POSTs each event to your webhook. Creating one runs its first check at once. An organization keeps up to 200 of each.
| POST /v1/web/watches | url, selector, render, credentials, intervalMinutes, tag, webhook. Runs the first check (the baseline).1 credit per check |
| POST /v1/web/watches/{id}/check | Check now: new, same, changed (with a unified diff) or removed.1 credit |
| POST /v1/web/feeds | query, num, domain, exclude, text, intervalMinutes, tag, webhook. Runs the first check.2 credits per check, +1 per result past 10 |
| POST /v1/web/feeds/{id}/check | Check now: the new results, with a summary.As a scheduled check |
| GET /v1/web/{watches|feeds} | List, by tag. /{id} reads one; /{id}/events?after=<seq> its events.Free |
| PATCH /v1/web/{watches|feeds}/{id} | Change intervalMinutes, webhook or tag; paused: true pauses, false resumes.Free |
| DELETE /v1/web/{watches|feeds}/{id} | Delete it with its state and events.Free |
$ curl https://api.getctx.sh/v1/web/watches \ -H "Authorization: Bearer $GETCTX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/pricing", "selector": ["main"], "intervalMinutes": 1440, "webhook": {"url": "https://you.example/hooks"}, "maxCredits": 1}'A failed check is free. A scheduled check refused for credits pauses the resource, and so do ten failures in a row; PATCH { "paused": false } resumes it.
§6Browser Rendering (/cf/)
Cloudflare's Browser Rendering REST API, path for path, at api.getctx.sh/cf. Point Cloudflare's SDK at it with a getctx key as the token; the account id in the path is ignored. Allowed actions: content, markdown, screenshot, pdf, snapshot, scrape, links, accessibilityTree, each 1 credit.
import Cloudflare from 'cloudflare' const cf = new Cloudflare({ baseURL: 'https://api.getctx.sh/cf', apiToken: process.env.GETCTX_API_KEY,})await cf.browserRendering.markdown.create({ account_id: '-', url })Refusals use Cloudflare's error envelope: unauthorized, action_not_allowed, insufficient_credits, rate_limited and provider_unavailable cost nothing; browser_timeout (past 60 s) is charged, since the browser ran.
§7Credits and billing
Credits are prepaid at $0.005 each and never expire. Buy them in the console; see pricing for packs.
maxCredits: the most a paid call may cost, required on parse, web, watch and feed calls. A price above it is refused asprice_changed(409) with the price indata.credits.charged: on every answer, what the call cost. Failures, cache hits and navigation cost 0.insufficient_credits(402): the call costs more than the organization can spend. Nothing was charged.
read
| Plain fetch (fetch) | 1 |
| Browser render (browser) | 2 |
| PDF or Office document (document) | 2 |
| Cache hit, section read, failure | 0 |
parse
| PDF, per page | 1 |
| Any other format, per file | 1 |
web
| search (up to 10 results, +1 per result after) | 7 |
| scrape · links · screenshot | 1 |
| json (extract to a schema) | 3 |
| crawl, per page | 1 |
brand
| profile, fresh | 10 |
| styleguide, fresh | 15 |
| Cache hit (within 30 days) | 1 |
Tracking
| Page Watch, per check | 1 |
| Search Feed, per check (up to 10 results, +1 per result after) | 2 |
§8Errors
A refusal is JSON with a code, a message and, for most codes, data. Refusals charge nothing; a read error's data.charged says so.
| 400 | invalid_request · invalid_target · invalid_section · invalid_domainChange the input; data.reason (and for web, data.field) says what is wrong. |
| 401 | unauthorizedMissing, unknown or revoked key. |
| 402 | insufficient_creditsFewer spendable credits than the call costs (data.price, data.spendable). Top up in the console. |
| 403 | forbiddenThe key lacks the scope the call needs (data.scope). |
| 404 | not_found · job_not_found · file_not_found · cache_missNothing under that id for your organization (or it expired), or the page does not exist. |
| 409 | price_changedmaxCredits is below the price (data.credits); call again with it. |
| 413 | file_too_large · too_largePast the limit (data.maxBytes, or a PDF’s data.maxPages). A large document goes to parse. |
| 410 | result_expiredA parse Job succeeded and was charged (data.charged), but its artifact is gone; parse again. |
| 415 | unsupported_format · unsupported_documentA format this call does not take; data.accepted, or data.alternative: parse. |
| 422 | target_failed · unreadable_document · section_not_foundThe target or file could not be read; data.reason says why. |
| 429 | rate_limitedToo many calls for this key; retry after data.retryAfterSeconds. |
| 503 | provider_unavailableAn upstream of getctx failed (502 or 503); retry, after retryAfterSeconds when given. |
§9Webhooks
Watch and feed events are POSTed to webhook.url (https) with Standard Webhooks headers (webhook-id, webhook-timestamp, webhook-signature), signed with your organization's secret: read it with GET /v1/webhooks/secret, rotate it with POST /v1/webhooks/secret. Verify with any Standard Webhooks library.
A refused delivery is retried 1, 5, 30, 120, 360, 720 minutes later, then dropped; the event stays readable at /events?after=<seq> (the last 100, within 30 days).
§10OpenAPI and llms.txt
Every route above, with its exact fields and errors, is in the OpenAPI 3.1 document the API serves: https://api.getctx.sh/openapi.json. For an agent, /llms.txt is this site in brief.