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
{
"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": "..."
}| Field | Type | Meaning |
|---|---|---|
number | integer | 1-based; the source_number of the annotations that cite it |
kind | provision, passage or web | What was cited |
unit_id | string or null | A 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 |
title | string | A display label: the act's short title, then the section |
act | object or null | title, short_title, identifier |
section | string or null | The provision's label |
country | string or null | A lower-case country code |
version | object or null | id, 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 |
url | string | An absolute https URL, always present |
url_kind | official, viewer or web | Where url points |
excerpt | string or null | At 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:
- The cited version's official URL (
url_kind: "official"). - The provision viewer, when the source has a
unit_id(url_kind: "viewer"). - 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. - 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:
{
"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.
// 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.