Reference

AI context (llms.txt)

Feed these docs to ChatGPT, Claude, or any LLM using the machine-readable llms.txt endpoints.

These docs are designed to be ingested by large language models. Two endpoints expose the whole site in the llmstxt.org format, so you can point an AI assistant at ThirdFactor Obsidian and get accurate, source-grounded answers instead of guesses.

Why this matters

Generic model knowledge about "a KYC API" is a poor substitute for this product's actual contract — its exact field names, its stable-decision vs. raw-status split, its flat KYC-session webhook shape vs. the nested application envelope. An assistant that hasn't seen these docs will confidently invent plausible-looking endpoints and fields that don't exist. Grounding it in llms-full.txt turns "plausible" into "correct."

The two endpoints

/llms.txt

A structured index — the site name, description, and a linked list of every documentation page with its one-line summary. Small; ideal for giving a model a map of what exists.

/llms-full.txt

The full text of every page concatenated into one document. Larger; ideal for pasting into a model's context so it can answer detailed questions without fetching each page.

Both live at the site root:

https://<docs-domain>/llms.txt
https://<docs-domain>/llms-full.txt

Every documentation page is authored in MDX with a title and description in its frontmatter, which is exactly what the index endpoint emits — so the two stay in sync automatically as pages are added.

Using them with an assistant

For a focused question, fetch /llms-full.txt, paste it into the conversation, and ask. A prompt that works well:

Here are the ThirdFactor Obsidian developer docs. Using only this material,
show me how to create a KYC session and verify the webhook signature. Cite the
relevant section.

<paste contents of /llms-full.txt>

Grounding the model in the pasted docs keeps it from inventing endpoints or field names.

Worked example

Here's a full round trip: fetching the corpus, asking a realistic integration question, and what a well-grounded answer looks like.

Fetch the corpus

curl -s https://kyc.acme.com/llms-full.txt -o obsidian-docs.txt
wc -l obsidian-docs.txt   # sanity-check it actually downloaded content

Paste it with a specific question

Vague prompts ("how does this API work?") produce vague, padded answers. Ask for the exact thing you need to build:

Here are the ThirdFactor Obsidian docs (below). I'm building a Node/Express
backend that:
1. Creates a KYC session for a signed-up user and returns the hosted URL to
   our mobile app.
2. Receives the webhook when the session settles and updates our `users`
   table.

Show me the two route handlers, including idempotency handling on create and
signature verification on the webhook. Use only endpoints and fields that
appear in the docs below — if something isn't covered, say so instead of
guessing.

<paste contents of /llms-full.txt>

Expect a grounded answer

A well-grounded response should look like this — specific field names, the correct header casing, and the flat KYC-session payload shape (not the nested application envelope, which is a common hallucination when a model isn't grounded):

// POST /kyc/start
app.post("/kyc/start", async (req, res) => {
  const userId = req.user.id;
  const r = await fetch(`${process.env.TF_BASE_URL}/v3/session/`, {
    method: "POST",
    headers: {
      "x-api-key": process.env.TF_API_KEY!,
      "content-type": "application/json",
      "idempotency-key": `kyc-${userId}`,
    },
    body: JSON.stringify({
      vendor_data: userId,
      callback_url: "https://app.acme.com/kyc/done",
    }),
  });
  const session = await r.json();
  res.json({ url: session.url });
});

// POST /webhooks/obsidian  (raw body middleware, not express.json())
app.post("/webhooks/obsidian", express.raw({ type: "application/json" }), (req, res) => {
  const sig = req.header("X-Webhook-Signature") ?? "";
  if (!verifySignature(process.env.TF_WEBHOOK_SECRET!, sig, req.body)) {
    return res.status(400).send("bad signature");
  }
  const event = JSON.parse(req.body.toString("utf8"));
  res.status(200).end();
  // event.decision, event.vendor_data — flat, no nested `session` object
  void queue.enqueue(event);
});

Verify against the checklist

Cross-check the generated code against the go-live checklist — it's the authoritative list of what "correct" means for a production integration, independent of what any model produced.

Tip

If a model's answer cites a field or endpoint you don't recognize, search for it in the pasted llms-full.txt yourself (Ctrl/Cmd+F). If it's not there, the model likely drifted from the grounding text — regenerate with a stricter instruction ("only use endpoints that literally appear in the text below").

Guardrails for AI-generated integration code

An assistant grounded in these docs is a fast way to scaffold an integration, but the safety-critical rules still apply — check any generated code against them:

Warning

The client result is advisory. Generated code must gate access on a signed webhook or a server-side GET /v3/session/<id>/decision/, never on the client's return value.

  • API keys belong on the server only — never in browser or mobile code.
  • Webhook signatures must be verified over the raw request body with a constant-time compare, rejecting stale timestamps.
  • KYC session webhook payloads are flat (event.vendor_data, event.decision), not nested under event.session.
  • Branch on stable decision values, not raw statuses.

If an assistant produces code that violates any of these, treat it as a bug — the go-live checklist is the authoritative list.

FAQ