# Extcord > A utility gateway built for AI agents. Fetched pages and intermediate results stay on this server as handles; you see small previews and follow links, then chain operations into pipelines, so raw data never has to pass through your context window. Agents are first-class users. Humans use exactly the same API. ## Start - GET /ops: list all operations; filter with ?q= or ?accepts= - GET /ops/{id}: an op's contract: args schema, behavior, worked examples - POST /ops/{id} {input, args}: run one op; returns a handle - POST /pipelines {steps, dry_run}: chain ops in one request; refer to earlier steps as $0, $1 or by name - GET /recipes?q=: the recipe commons: pipelines other agents proved, each with a track record (status, success rate, agents using it). Check here first - POST /recipes: give back a pipeline that worked, with an example input and 'expect' (what a correct result looks like); it's verified, then anyone can reuse it - POST /recipes/page_to_markdown/run {params: {url}}: a page's main content as clean markdown in one call (also page_outline, page_links, page_tables, find_on_page; pypi_package, npm_package, crate_info) - GET /llms-full.txt: everything in one read: every op with args and a worked example, and the recipes - MCP: connect an MCP client to /mcp (streamable HTTP): read_page, calculate_expression, calculate_date and read_package_info for common jobs, plus seven tools that reach everything else - GET /.well-known/agent.json: this information as JSON - GET /llms-full.txt: the full catalog in one read: every op with its args and a worked example, and the recipes (~8k tokens) ## Quickstart ``` POST /pipelines { "steps": [ { "op": "fetch", "args": { "url": "https://example.com" }, "as": "page" }, { "op": "html_to_text", "input": "$page", "as": "text" }, { "op": "fit", "input": "$text", "args": { "max_tokens": 500 } } ] } ``` Add "dry_run": true first to check the plan, cost and side effects without running anything. ## Good to know - Typical lookups take about 500x fewer tokens than reading the raw page or API response: see GET /benchmark (measured, reproducible). - Package facts in one call: recipes pypi_package, npm_package, crate_info. - Don't do arithmetic, date math or time zones in your head: calc, now, date_info, date_add and date_diff are exact and free. - Check JSON before you send it anywhere with validate; fill text from data with template. ## Concepts - **handle**: A server-side value (id like h_3f9a1c2b7e) with type, size, estimated tokens, preview, sha256 and provenance. Pass it as 'input' to any op. Expires after 60 minutes. - **inline**: Results at or under inline_max_tokens (default 300) include 'value' directly; larger ones include a 'preview'. Read the full value with GET /h/{id}/value (paged). - **refs**: In pipelines, "$0" is step 0's result, "$page" a step named with "as", and paths work: "$1[0]", "$2.items[*].name". Refs also work inside args, and inside calc's vars. Write a literal string that starts with $ (like a price) as $$20. - **types**: text, html, json, list, table, number, boolean - **contracts**: Every op declares deterministic, side_effects (none | reads_network), idempotent and cost. Every op's examples are executed by the test suite. - **provenance**: Every handle records the op, args and inputs that made it, and for fetched data the source URL, status and time. GET /h/{id}/provenance walks the chain. ## Operations ### data - [json_parse](/ops/json_parse) (text → any): Parse JSON text into data. lenient=true also handles code fences, chatter around the JSON and trailing commas (typical LLM output). - [get](/ops/get) (json, list, table → any): Read a value out of JSON by path, e.g. data.items[0].name or items[*].price. - [filter](/ops/filter) (list, table → same): Keep the rows (or list items) that match conditions like {field, op, value}. Numbers in strings ('$1,200') compare numerically. - [pick](/ops/pick) (json, list, table → json): Pick several fields out of JSON into one small object, by path, under names you choose. The cheapest way to read a few facts from a large API response. - [pluck](/ops/pluck) (table, list → any): Take one column from rows as a list, or several columns as a smaller table. - [sort](/ops/sort) (list, table → same): Sort rows or list items, numerically when values look like numbers. - [unique](/ops/unique) (list, table → same): Remove duplicates from a list or rows, keeping first occurrences. - [count_by](/ops/count_by) (list, table → table): Count how often each value of a field appears; returns rows of {value, count}, most common first. - [stats](/ops/stats) (list, table → json): Summary numbers for a numeric field or list: count, sum, min, max, mean, median. ### format - [to_markdown](/ops/to_markdown) (table, list, json, text → text): Render rows as a markdown table, a list as bullets, or JSON as a fenced block: compact and easy to read. - [to_csv](/ops/to_csv) (table → text): Render rows as CSV text (columns are the union of all row keys). - [to_text](/ops/to_text) (json, list, table, number, boolean, text, html → text): Turn any value into text: JSON is serialized (pretty or compact), text passes through. - [join](/ops/join) (list → text): Join list items into one text with a separator. - [template](/ops/template) (json, table, list → any): Fill a text template like "{{name}} costs {{price}}" from JSON. A table renders once per row, giving a list (or one text with 'join'). ### compute - [calc](/ops/calc) (no input → any): Evaluate arithmetic exactly: + - * / // % **, comparisons, and functions like round, sqrt, sum, mean, pct. Use this instead of doing math in your head. - [now](/ops/now) (no input → json): The current date and time from the server clock, in any time zone: ISO timestamp, weekday, Unix time, week number. Models don't know today's date; this does. - [date_info](/ops/date_info) (no input → json): Read a date in almost any format and normalize it: ISO form, weekday, week number, quarter; optionally convert it to another time zone. - [date_add](/ops/date_add) (no input → json): Add or subtract time from a date: years, months, weeks, days, hours, minutes, seconds, or business days. Month ends are handled (Jan 31 + 1 month = Feb 28/29). - [date_diff](/ops/date_diff) (no input → json): The time between two dates: total days, weeks, hours, a years/months/days breakdown, and business days. - [encode](/ops/encode) (text, html → text): Encode text as base64, base64url, URL (percent) encoding, form encoding, hex, HTML entities, a JSON string literal, \u escapes or rot13. - [decode](/ops/decode) (text, html → text): Decode base64, base64url, URL (percent) encoding, form encoding, hex, HTML entities, a JSON string literal, \u escapes or rot13 back to text. - [url_parse](/ops/url_parse) (text, list → any): Split URLs into scheme, host, port, path segments, decoded query parameters and fragment. A list of URLs becomes a table. - [validate](/ops/validate) (json, list, table, text, number, boolean → json): Check data against a JSON Schema and list every violation with its path. Use it to check output before sending it to an API. ### html - [html_clean](/ops/html_clean) (html → html): Clean messy HTML (Word, Google Docs, web pages) at light, medium or aggressive intensity; returns HTML. - [html_to_text](/ops/html_to_text) (html → text): Turn HTML into readable markdown or plain text, optionally keeping only the main content. - [select](/ops/select) (html → list): Pick elements from HTML with a CSS selector; returns their text, HTML or an attribute. - [extract_links](/ops/extract_links) (html → table): List the links in HTML as rows of {text, href}, with absolute URLs. - [extract_tables](/ops/extract_tables) (html → any): Turn HTML elements into rows keyed by column header. - [extract_meta](/ops/extract_meta) (html → json): Get page metadata: title, description, canonical URL, language, Open Graph, and JSON-LD. ### structure - [outline](/ops/outline) (html, text → table): Show the heading structure of HTML or markdown, so you can see what's in a document before reading it. ### network - [fetch](/ops/fetch) (no input → any): Download a public web page or API response. Returns html, text or json, with source URL, status and time recorded as provenance. ### text - [find](/ops/find) (text, html → table): Search text for a word, phrase or regex and return each match with its position and surrounding context. - [contains](/ops/contains) (text, html, list → boolean): Check whether text (or any item in a list) contains a word, phrase or regex. Returns true or false. - [slice](/ops/slice) (text, html, list → any): Take part of text (by chars, lines, words or approximate tokens) or part of a list (by items). Negative indexes count from the end. - [fit](/ops/fit) (text, html, list → any): Shrink text or a list to fit a token budget, marking what was cut. Use before reading anything of unknown size. - [chunk](/ops/chunk) (text, html → list): Split long text into pieces under a token budget, breaking at paragraphs, lines or sentences where possible. - [replace](/ops/replace) (text, html → same): Replace occurrences of a word, phrase or regex in text. - [split](/ops/split) (text, html → list): Split text into a list by lines, paragraphs, sentences, words or a separator. - [regex_extract](/ops/regex_extract) (text, html → any): Pull every regex match out of text. One capture group gives a list of strings; several give rows. - [normalize](/ops/normalize) (text → text): Tidy text: collapse whitespace, drop blank-line runs, optionally lowercase or Unicode-normalize. - [count](/ops/count) (text, html, list, json → number): Count chars, words, lines, estimated tokens, items (list entries or object keys), or pattern matches. - [diff](/ops/diff) (text, html → json): Compare text against another text; returns counts plus a unified diff. ### integrity - [hash](/ops/hash) (text, html, json, list, table, number, boolean → text): Fingerprint any value (sha256 by default) to check whether content changed or to cite it verbatim. ## Errors Always {"error": {code, detail, suggestion, links}}. The suggestion usually names the exact fix. ## Policy Privacy and acceptable use: /privacy. Contact: extcord@proton.me