OpenAI-Compatible API

Citations and sources

The complete citation and source schema for Calypso RAG — annotation objects, file search results, grounding metadata, and the native citation-compat mode — on both the Responses and Chat Completions endpoints.

Overview

Every grounded answer from calypso-rag-agent carries structured citation data so your app can render inline references and a source list without parsing the answer text. This page is the canonical schema reference for that data on both endpoints.

Calypso follows the OpenAI annotation model: citations are annotation objects anchored to the generated text by character offsets. The answer text stays plain; your client maps start_index / end_index spans back onto the text and renders [1]-style markers, links, or hover cards.

SurfaceWhere citations liveWhere sources live
Responsesoutput[]messageoutput_text.annotations[]output[]file_search_call.results[] (plus metadata._aicore in legacy mode)
Chat Completionschoices[0].message.annotations[]Plain-text "Sources:" appendix in the message content

Prefer the Responses endpoint for citation-aware UIs: it exposes the full structured surface. Chat Completions is limited to OpenAI's nested url_citation annotation shape.

Responses: response anatomy

A grounded, non-streaming Responses reply contains two output items — the file search call that retrieved the evidence, and the assistant message with annotated text:

{
  "id": "resp_abc123",
  "object": "response",
  "model": "calypso-rag-agent",
  "output": [
    {
      "type": "file_search_call",
      "status": "completed",
      "results": [
        {
          "file_id": "file-1",
          "filename": "refund_policy_2026.pdf",
          "text": "Customers may request a full refund within 30 days of purchase.",
          "attributes": {
            "source_index": 1,
            "label": "refund_policy_2026.pdf (p. 4)",
            "page_number": 4,
            "page_label": "4",
            "locator_label": "p. 4",
            "modality": "text",
            "knowledge_id": "kb-1"
          }
        }
      ]
    },
    {
      "type": "message",
      "role": "assistant",
      "content": [
        {
          "type": "output_text",
          "text": "Refunds are available within 30 days of purchase.",
          "annotations": [
            {
              "type": "file_citation",
              "text": "Refunds are available within 30 days of purchase.",
              "start_index": 0,
              "end_index": 49,
              "file_id": "file-1",
              "filename": "refund_policy_2026.pdf",
              "index": 0,
              "file_citation": {
                "file_id": "file-1",
                "filename": "refund_policy_2026.pdf",
                "quote": "Customers may request a full refund within 30 days of purchase."
              }
            }
          ]
        }
      ]
    }
  ],
  "metadata": {
    "_aicore": {
      "retrieval_status": "grounded",
      "grounded_sources_state": "available",
      "grounded_sources_hint": "Retrieved context used to support this answer."
    }
  }
}

Annotation objects

Each annotation in output_text.annotations[] carries the OpenAI-native flat fields at the top level, plus a legacy nested object kept for backward compatibility. Three types are emitted:

FieldTypePresent onDescription
typestringallurl_citation, file_citation, or media_citation (Calypso extension for image/audio/video evidence)
textstringallThe exact answer segment the citation supports
start_index / end_indexintallCharacter span of text within the output_text it belongs to
url / titlestringurl_citationFlat OpenAI fields for web sources
file_id / filenamestringfile_citationFlat OpenAI fields for file sources
indexintfile_citationOpenAI-standard insertion point (equals start_index)
url_citation / file_citation / media_citationobjectmatching typeLegacy nested shape; same data as the flat fields plus quote for files
_aicoreobjectallCalypso extension: grounded_source (the full source descriptor), chunk_index, segment_text, segment_start, segment_end, support_indices

The annotation _aicore.segment_* fields let you rebuild evidence panels ("which sentence is backed by which source") from annotations alone — no response-level metadata required.

File search results

The file_search_call.results[] array lists every retrieved source the answer draws on. Each result carries file_id, filename, text (the quoted evidence), an optional relevance score, and an attributes object:

AttributeDescription
source_indexStable 1-based source number — matches the [n] numbering used in citations
labelDisplay-ready source label, e.g. refund_policy_2026.pdf (p. 4)
page_number / page_labelPage location within the document, when known
locator_labelShort human-readable locator, e.g. p. 4
modality / mime_typeSource modality (text, multimodal, ...) and MIME type
source_type / knowledge_idBucket-level provenance identifiers
media_id / media_statusPresent for media sources; media_status reports preview resolution
access_urlSigned URL for opening the source, when available
excerpt / previewStructured evidence excerpt and preview descriptors

Use source_index and label to render your source list directly from the native output — they make the response self-describing without any vendor metadata.

Response metadata (metadata._aicore)

Every Responses reply includes a compact status block:

KeyDescription
retrieval_statusWhether retrieval ran and grounded the answer (e.g. grounded)
grounded_sources_stateavailable, hidden, missing, or none — see grounding states
grounded_sources_hintDisplay-ready caption for the sources panel

In the default legacy mode, the block additionally duplicates the citation data as grounded_sources[] (indexed source descriptors), grounding_supports[] (per-segment evidence spans with support_index, chunks, text, start_index, end_index), display_text, and ui[]. This duplication exists for older consumers; new integrations should read the native surface and opt into native mode below.

Citation compatibility modes

Control how much duplicated metadata you receive with two request-level metadata keys (top-level or nested under metadata._aicore):

KeyValuesDefaultEffect
citation_compatlegacy, nativelegacynative prunes the duplicated _aicore citation payload; annotations and file_search_call.results[].attributes become the canonical carrier
sources_appendixinclude, omitincludeomit returns the answer body without the trailing plain-text "Sources:" block
resp = client.responses.create(
    model="calypso-rag-agent",
    input="What is our refund policy?",
    metadata={
        "citation_compat": "native",
        "sources_appendix": "omit"
    }
)

In native mode the response is leaner and fully OpenAI-shaped: sources come from file_search_call.results[] (source_index, label), inline citations from annotations[], and evidence spans from each annotation's _aicore.segment_* fields. The compact status keys (retrieval_status, grounded_sources_state, grounded_sources_hint) are always present in both modes.

Both modes render identically — native mode changes where the data travels, not what your users see. If you already consume the legacy grounded_sources / grounding_supports metadata, nothing changes until you opt in.

Responses: streaming events

When stream=True, citation data arrives as typed SSE events alongside the text deltas:

EventPayload
response.output_text.deltaIncremental answer text
response.output_text.annotation.addedOne event per citation annotation, with annotation_index and the full annotation object
response.content_part.done / response.output_item.doneFinal text with the complete annotations[] array
response.completedFull response object, including file_search_call results and metadata
for event in stream:
    if event.type == "response.output_text.delta":
        render_text(event.delta)
    elif event.type == "response.output_text.annotation.added":
        render_citation(event.annotation)

Chat Completions: citations

Chat Completions follows OpenAI's spec for that surface, which only defines the nested url_citation annotation shape. Citations appear on choices[0].message.annotations[]:

{
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "Refunds are available within 30 days of purchase. [1]\n\nSources:\n[1] refund_policy_2026.pdf (p. 4)",
        "annotations": [
          {
            "type": "url_citation",
            "url_citation": {
              "url": "https://files.calypso.so/signed/refund_policy_2026.pdf",
              "title": "refund_policy_2026.pdf",
              "start_index": 0,
              "end_index": 49
            }
          }
        ]
      }
    }
  ]
}

Behavior to note:

  • File and media sources are mapped into url_citation annotations: the URL is a signed access URL when one is available, and the title falls back to the filename so you can always label the source.
  • The answer content includes a plain-text "Sources:" appendix, since this surface has no structured source list.
  • When streaming, annotations arrive in a final delta.annotations chunk just before the finish_reason chunk.

For structured file search results, grounding supports, and the native compat mode, use the Responses endpoint.

Rendering guidance

  • Anchor by offsets, not by markers. Use each annotation's start_index / end_index to place inline references; don't parse [n] tokens out of the text.
  • Number sources with source_index. It is stable, 1-based, and consistent with the citation order — use it for your [n] badges and source list.
  • Sort before slicing. If you decorate text by span, apply annotations in descending start_index order so earlier insertions don't shift later offsets.
  • Respect the grounding state. Only render a sources panel when grounded_sources_state is available; hidden means the workspace policy suppresses sources, and missing / none mean there is nothing to show. See grounding states.

Next steps