---
title: "RAG with citations: a source on every answer | Calypso Context"
canonical_url: "https://www.calypso.so/context/citations"
last_updated: "2026-09-16T00:41:15.089Z"
meta:
  description: "Every grounded answer carries inline annotations anchored by character offsets and a structured source list with source number, label, page, modality, and a signed link. On Responses and Chat Completions, with a fully OpenAI-shaped native mode."
  keywords: "RAG with citations, grounded answers with sources, verifiable AI answers, OpenAI annotations file_citation, page-level citations RAG"
  "og:description": "Every grounded answer carries inline annotations anchored by character offsets and a structured source list with source number, label, page, modality, and a signed link. On Responses and Chat Completions, with a fully OpenAI-shaped native mode."
  "og:title": "RAG with citations: a source on every answer | Calypso Context"
  "twitter:description": "Every grounded answer carries inline annotations anchored by character offsets and a structured source list with source number, label, page, modality, and a signed link. On Responses and Chat Completions, with a fully OpenAI-shaped native mode."
  "twitter:title": "RAG with citations: a source on every answer | Calypso Context"
---

Calypso home

**Calypso Context · Citations**

# Every answer says where it came from.

A grounded answer carries inline citations anchored to the text by character offsets, and a structured list of the sources it drew on: source number, label, page, modality, and a signed link to open the original. Your app renders references without parsing the answer.

[**Read the citations docs**](https://docs.calypso.so/context/ask/citations-and-sources) [**Start 14-day free trial**](https://context.calypso.so/join)

**The example**

## Ask once. Render the sources from the response.

Native mode keeps the response fully OpenAI-shaped: annotations on the text, sources in file_search_call.results. Nothing to parse, nothing vendor-specific to learn.

**CALYPSO_API_KEY**

```
import os
from openai import OpenAI

client = OpenAI(api_key=os.environ["CALYPSO_API_KEY"],
                base_url="https://api.calypso.so/v1")

resp = client.responses.create(
    model="calypso-agent",
    input="What is our refund policy?",
    metadata={"citation_compat": "native", "sources_appendix": "omit"},
)

print(resp.output_text)
for item in resp.output:
    if item.type == "file_search_call":
        for r in item.results:
            a = r.attributes
            print(f"[{a['source_index']}] {a['label']}")   # refund_policy_2026.pdf (p. 4)
```

**Two surfaces**

## Responses for citation-aware UIs. Chat Completions for compatibility.

Both endpoints carry citations. Responses exposes the full structured surface; Chat Completions is limited to the url_citation shape that spec defines.

### **Responses**

<dl>

<dt>**Citations live in**</dt>
<dd>output[] → message → output_text.annotations[]</dd>

<dt>**Sources live in**</dt>
<dd>output[] → file_search_call.results[]</dd></dl>

Recommended. Streaming sends one annotation.added event per citation and the full results in response.completed.

### **Chat Completions**

<dl>

<dt>**Citations live in**</dt>
<dd>choices[0].message.annotations[] as url_citation</dd>

<dt>**Sources live in**</dt>
<dd>A plain-text "Sources:" appendix in the content</dd></dl>

File and media sources map to url_citation with a signed URL when one exists and the filename as title. When streaming, annotations arrive in a final chunk.

**The attributes**

## What every source carries.

The same attributes appear on Responses results and on Search results. Build your source list from them directly.

| **source_index** | Stable, 1-based source number that matches the [n] order in the text. Use it for badges and the list. |
| --- | --- |
| **label** | Display-ready, for example refund_policy_2026.pdf (p. 4). Falls back to the filename. |
| **page_number · page_label · locator_label** | Where in the document, when known. PDFs cite the page; other documents cite the file. |
| **modality · mime_type** | text, multimodal, or image, and the MIME type. An image citation points at the image itself. |
| **access_url · media_id** | A signed URL to open the original when one is available, and media identifiers for previews. |
| **text · excerpt · preview** | The quoted evidence the answer drew on, plus structured excerpt and preview descriptors. |

**How it works**

## Ask, map, open.

1. 01 ### **Ask an agent**

   Call Responses with calypso-agent or a named agent. Retrieval runs against its buckets and the evidence comes back with the answer.
2. 02 ### **Map offsets to the text**

   Each annotation carries start_index and end_index. Place your [n] markers from the offsets, numbered by source_index.
3. 03 ### **Let users open the source**

   Render the source list from the attributes, and link each entry to its access_url so a reader can check the page.

**Rendering**

## Four rules from the docs.

1. 01 ### **Anchor by offsets, not markers**

   Use start_index and end_index to place references. Do not parse [n] tokens out of the text.
2. 02 ### **Number by source_index**

   It is stable, 1-based, and consistent with citation order. Use it for badges and the list.
3. 03 ### **Sort before slicing**

   Apply annotations in descending start_index order so earlier insertions do not shift later offsets.
4. 04 ### **Respect the grounding state**

   Render a sources panel only when grounded_sources_state is available. hidden means policy suppresses sources; missing and none mean there is nothing to show.

**From the docs**

## The schema in one table.

| **Model** | OpenAI annotations: citation objects anchored to the text by character offsets; the answer text stays plain |
| --- | --- |
| **Responses** | output[] → message → output_text.annotations[] · output[] → file_search_call.results[] |
| **Chat Completions** | choices[0].message.annotations[] (url_citation) + "Sources:" appendix |
| **Compatibility modes** | metadata.citation_compat: legacy \| native · metadata.sources_appendix: include \| omit |
| **Streaming event** | response.output_text.annotation.added |
| **Page-level citations** | PDFs, which are read as page images; images cite the image; other documents cite the file |
| **Grounding states** | available · hidden · missing · none |

**Questions and answers**

## Before you render your first citation.

**Keep reading**

[<h3>**Sources **</h3>PDFs, images, pages, and data. What it reads and how it gets in.](https://www.calypso.so/context/sources) [<h3>**Buckets **</h3>Scoped, durable source memory. Provision by slug, bind to agents.](https://www.calypso.so/context/buckets) [<h3>**Agents **</h3>One default, any number of named agents with their own scope and policy.](https://www.calypso.so/context/agents) [<h3>**Search **</h3>Retrieval without generation: the passages an answer would cite, ranked.](https://www.calypso.so/context/search) [<h3>**Privacy **</h3>Yours, isolated: no training on your data, export or delete any time, keys with explicit scopes.](https://www.calypso.so/context/privacy) [<h3>**Citations in the docs **</h3>The reference this page is written from, with every endpoint and field.](https://docs.calypso.so/context/ask/citations-and-sources)

**Start today**

## **Ship answers your users can check.**

Load a bucket, ask calypso-agent, and render the source list from the first response.

[**Start 14-day free trial**](https://context.calypso.so/join) [**Read the developer docs**](https://docs.calypso.so/context/ask/citations-and-sources)