Sources

The source record every answer carries, how its url is chosen, and how to render citations from annotation offsets.

An answer cites the provisions it rests on. sources[] on the response holds one record for each source the answer cites, numbered by first citation, and nothing else. The research the run read along the way is never listed. A citation that does not resolve to a link shows as its label with no number.

The source record

JSON
{
  "number": 1,
  "kind": "provision",
  "unit_id": "hu:njt:2007-127-00-00:part-3.ch-viii.h1.s142.p1.ptb",
  "title": "Áfa tv. 142. § (1) b)",
  "act": { "title": "2007. évi CXXVII. törvény az általános forgalmi adóról", "short_title": "Áfa tv.", "identifier": "2007. évi CXXVII. törvény" },
  "section": "142. § (1) b)",
  "country": "hu",
  "version": { "id": "...", "effective_from": "2026-01-01", "effective_to": null, "is_current": true },
  "url": "https://njt.hu/jogszabaly/2007-127-00-00",
  "url_kind": "official",
  "excerpt": "..."
}
FieldTypeMeaning
numberinteger1-based; the source_number of the annotations that cite it
kindprovision, passage or webWhat was cited
unit_idstring or nullA provision's id, the same across versions and re-ingestion. Pass it to POST /v1/provisions/fetch. Null for a passage, a web page and a provision whose id is not stable
titlestringA display label: the act's short title, then the section
actobject or nulltitle, short_title, identifier
sectionstring or nullThe provision's label
countrystring or nullA lower-case country code
versionobject or nullid, effective_from, effective_to, is_current; dates are YYYY-MM-DD. The id is per version and not stable, so never store it as a key
urlstringAn absolute https URL, always present
url_kindofficial, viewer or webWhere url points
excerptstring or nullAt most 300 characters, whitespace collapsed

A Hungarian unit_id is hu:njt:<njt id>:<path>. Treat it as an opaque string and pass it back as is.

Which url you get

url is the first of these that applies:

  1. The cited version's official URL (url_kind: "official").
  2. The provision viewer, when the source has a unit_id (url_kind: "viewer").
  3. The document viewer, when the act has a published identifier that is unique in its country (url_kind: "viewer"). It is never used for countries whose provision ids are not yet stable.
  4. Otherwise the citation shows as its label with no number and no record.

A passage (kind: "passage") gets the passage viewer, on the API and on MCP alike. A web result keeps its own URL (url_kind: "web").

Viewer URLs open on platform.azta.ai and need an Azta sign-in. They never carry an internal id that changes when a law is re-ingested, so a stored link keeps working.

Annotations

The answer text is plain text. Each citation is an annotation over the label's span, in OpenAI's url_citation shape plus source_number:

JSON
{
  "type": "url_citation",
  "start_index": 120,
  "end_index": 141,
  "url": "https://...",
  "title": "Áfa tv. 142. § (1) b)",
  "source_number": 1
}

source_number matches the number of a record in sources[].

start_index and end_index count Unicode code points of the returned text, so text[start:end] is exactly the label. A UTF-16 count, which JavaScript's String.length and slice use, differs after astral characters such as an emoji or a mathematical letter, and a base letter followed by a combining mark counts as two code points. In JavaScript, split the text with Array.from(text) before you slice. In Python a str already indexes code points.

Render the citations

Insert [n] after each annotated span, then list the sources. Walk the annotations from the highest end_index down, so the offsets of earlier spans stay valid while you insert.

TypeScript
// Render an Azta answer with [n] after each cited span, then a Sources list.
// Annotation offsets are Unicode code points, so slice a code point array.
type SourceRecord = { number: number; title: string; url: string };
type Annotation = {
  start_index: number;
  end_index: number;
  source_number?: number;
};
type OutputText = { type?: string; text: string; annotations?: Annotation[] };
type Item = { content?: OutputText[] };

const renderText = (part: OutputText): string => {
  const points = Array.from(part.text);
  const annotations = [...(part.annotations ?? [])]
    .filter((a) => a.source_number !== undefined)
    .sort((a, b) => b.end_index - a.end_index);
  // Highest end_index first, so the offsets of earlier spans stay valid.
  for (const a of annotations) {
    points.splice(a.end_index, 0, ` [${a.source_number}]`);
  }
  return points.join('');
};

export const renderWithCitations = (response: {
  output: unknown[];
  sources: SourceRecord[];
}): string => {
  const parts = (response.output as Item[])
    .flatMap((item) => item.content ?? [])
    .filter((part) => typeof part.text === 'string');
  const text = parts.map(renderText).join('\n');
  const lines = response.sources.map(
    (s) => `[${s.number}] ${s.title} ${s.url}`,
  );
  return `${text}\n\nSources:\n${lines.join('\n')}`;
};

if (process.argv[1]?.endsWith('render.ts')) {
  const chunks: Buffer[] = [];
  for await (const chunk of process.stdin) {
    chunks.push(chunk as Buffer);
  }
  console.log(
    renderWithCitations(JSON.parse(Buffer.concat(chunks).toString())),
  );
}

The output is the text with [n] after each cited label, a blank line, Sources:, then one [n] title url line per source, as in the quickstart.

On this page