API docs

Send a URL, get back cited business context. Poll it while it builds, so your customer can watch.

Quick start

  1. Key

    Sign in to the dashboard with your email and create an API key. It starts with utc_ and is shown once.

  2. Build
    curl https://urltocontext.com/v1/contexts \
      -H "Authorization: Bearer $UTC_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"url": "https://example.com"}'
  3. Watch
    curl https://urltocontext.com/v1/contexts/ctx_… \
      -H "Authorization: Bearer $UTC_API_KEY"
  4. Use

    When status is ready, the same response holds the context. Most contexts are ready in one to three minutes.

Keys

Send your key on every request: Authorization: Bearer utc_…. Keys belong on your server; never put one in browser or app code. Revoke a key in the dashboard and it stops working at once. An account can have 10 active keys.

The base URL is https://urltocontext.com. Requests and responses are JSON, except the Markdown files. Times are UTC in ISO 8601, like 2026-09-25T03:31:28Z.

Endpoints

POST /v1/contexts

Build a context. Body: {"url": "https://example.com"}; a bare domain works too. Returns 202 with the context (status queued) and a Location header.

Send an Idempotency-Key header (up to 200 characters) to retry safely: the same key returns the context it already created, with 200, and never builds or charges twice.

Only public websites on ports 80 and 443 are accepted.

GET /v1/contexts/<id>

The context's status, every status change with its time, the error if it failed, and the finished context once ready.

GET /v1/contexts

Your contexts, newest first, without their bodies. ?limit= 1 to 100, default 20.

GET /v1/contexts/<id>/<file>.md

One part of a ready context as Markdown: brief, product, icp, positioning, brand, notes or questions. Returns 409 not_ready until the context is ready.

GET /v1/me

Your account: free contexts left, credits, contexts built and how many can build at once.

Context

{
  "id": "ctx_3nx1iyWFJ57qu-lL",
  "url": "https://example.com/",
  "status": "ready",
  "created_at": "2026-09-25T03:31:28Z",
  "updated_at": "2026-09-25T03:32:40Z",
  "events": [{"status": "queued", "at": "2026-09-25T03:31:28Z"}, …, {"status": "ready", "at": "…"}],
  "error": null,
  "context": {
    "business_name": "Example",
    "brief": "Example makes invoicing tools for plumbers. [ch_…]",
    "pillars": {"product": "### Offering\n…", "icp": "…", "positioning": "…", "brand": "…"},
    "notes": "### Evidence limits\n…",
    "questions": [{"field": "icp.buyers", "purpose": "gap", "text": "Who approves the purchase?",
                   "reason": "The site does not say.", "source_ids": ["ch_…"]}],
    "claims": [{"pillar": "product", "kind": "website_claim", "text": "Example sells invoicing software.",
                "evidence": [{"source_id": "ch_…", "quote": "Invoicing for plumbers"}]}],
    "sources": [{"id": "ch_…", "url": "https://example.com/", "title": "Example"}]
  }
}

pillars are Markdown. Every statement cites its evidence as [ch_…], the id of an entry in sources. claims hold the exact quotes behind each pillar. questions are what the website could not answer, to ask the business owner. notes say what the evidence can and cannot support.

The context describes what the website says. It is AI-generated from public pages, so treat it as a well-sourced draft and check what matters.

Statuses

queued
Waiting for a worker.
exploring
Reading the website's public pages.
synthesizing
Ranking the evidence and writing the pillars from it.
building
Assembling and filing the context.
ready
Done. The response holds the context.
failed
error.code is unreachable (no readable public pages), timeout or failed. A failed context costs nothing.

Poll every 3 to 5 seconds. Each change is in events with its time, so you can show your customer each step as it happens.

Errors

Errors are {"error": {"code": "…", "message": "…"}}. The message is written for people and may change; match on the code.

StatusCodeMeaning
400invalid_requestThe body is not {"url": "…"}.
400invalid_urlNot a public http or https website.
401missing_api_keyNo Authorization: Bearer utc_… header.
401invalid_api_keyThe key is wrong or revoked.
402out_of_creditsNo free contexts or credits left.
404not_foundNo such context on your account.
409not_readyThe file exists once the context is ready.
429too_many_activeWait for a context to finish before starting another.
503busyWe are at capacity. Try again in a few minutes.

Limits and credits

Every account gets 3 free contexts. After those, one credit builds one context: packs of 100 credits for $13 and 500 credits for $25. A context that fails costs nothing.

A free account builds one context at a time; an account with credits builds three. GET /v1/me shows what is left.

Examples

Build a context and wait for it.

Python

import json, os, time, urllib.request

API, KEY = "https://urltocontext.com", os.environ["UTC_API_KEY"]

def call(method, path, body=None):
    request = urllib.request.Request(API + path, method=method,
        data=json.dumps(body).encode() if body else None,
        headers={"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"})
    with urllib.request.urlopen(request) as response:
        return json.load(response)

context = call("POST", "/v1/contexts", {"url": "https://example.com"})
while context["status"] not in ("ready", "failed"):
    time.sleep(4)
    context = call("GET", f"/v1/contexts/{context['id']}")
    print(context["status"])
print(context["context"]["pillars"]["product"] if context["status"] == "ready" else context["error"])

JavaScript (Node 18+)

const API = "https://urltocontext.com";
const headers = { Authorization: `Bearer ${process.env.UTC_API_KEY}`, "Content-Type": "application/json" };

let context = await (await fetch(`${API}/v1/contexts`, {
  method: "POST", headers, body: JSON.stringify({ url: "https://example.com" }),
})).json();
while (!["ready", "failed"].includes(context.status)) {
  await new Promise(r => setTimeout(r, 4000));
  context = await (await fetch(`${API}/v1/contexts/${context.id}`, { headers })).json();
  console.log(context.status);
}
console.log(context.status === "ready" ? context.context.brief : context.error);