{"meta":{"title":"Citations","intro":"Citations link spans of an assistant response back to the sources that support them. Turn on enableCitations when you create or resume a session, then read the citations payload on assistant.message events to render footnotes, source lists, or inline links.","product":"GitHub Copilot","breadcrumbs":[{"href":"/en/enterprise-cloud@latest/copilot","title":"GitHub Copilot"},{"href":"/en/enterprise-cloud@latest/copilot/how-tos","title":"How-tos"},{"href":"/en/enterprise-cloud@latest/copilot/how-tos/copilot-sdk","title":"Copilot SDK"},{"href":"/en/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/features","title":"Features"},{"href":"/en/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/features/citations","title":"Citations"}],"documentType":"article"},"body":"# Citations\n\nCitations link spans of an assistant response back to the sources that support them. Turn on enableCitations when you create or resume a session, then read the citations payload on assistant.message events to render footnotes, source lists, or inline links.\n\n<!-- markdownlint-disable GHD046 GHD005 -->\n\n<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->\n\n> \\[!WARNING]\n> Citations are experimental. The option name, event payload, and provider coverage can change in a future release.\n\n## How citations work\n\nCitations are produced by the model provider, not by the SDK. The flow has three parts:\n\n1. Your application supplies citable material, such as a document attachment or a tool result that carries source content.\n2. The runtime marks that material as citable on the wire when `enableCitations` is on. For Anthropic models, file attachments are sent as `document` blocks with citations enabled.\n3. The model returns citation metadata, and the runtime normalizes it into a provider-agnostic `citations` object on the final `assistant.message` event.\n\nProvider support is limited. The `provider` field on each source records where the citation came from:\n\n| Provider value | Meaning                                                   |\n| -------------- | --------------------------------------------------------- |\n| `anthropic`    | Citation produced by an Anthropic (Claude) model response |\n| `openai`       | Citation produced by an OpenAI model response             |\n| `client`       | Citation synthesized by the runtime from tool output      |\n\n> \\[!NOTE]\n> Turning on `enableCitations` does not guarantee that a response contains citations. Models emit them only when the response is grounded in citable source material. Always treat the `citations` field as optional.\n\n## Enable citations on a session\n\nSet the option on session create, and set it again on resume if you want citations after a restart.\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n<!-- docs-validate: skip -->\n\n```typescript\nconst session = await client.createSession({\n    onPermissionRequest: approveAll,\n    enableCitations: true,\n});\n\nconst resumed = await client.resumeSession(session.sessionId, {\n    onPermissionRequest: approveAll,\n    enableCitations: true,\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n<!-- docs-validate: skip -->\n\n```python\nsession = await client.create_session(\n    on_permission_request=PermissionHandler.approve_all,\n    enable_citations=True,\n)\n\nresumed = await client.resume_session(\n    session.session_id,\n    on_permission_request=PermissionHandler.approve_all,\n    enable_citations=True,\n)\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n<!-- docs-validate: skip -->\n\n```golang\nsession, err := client.CreateSession(ctx, &copilot.SessionConfig{\n\tOnPermissionRequest: copilot.PermissionHandler.ApproveAll,\n\tEnableCitations:     copilot.Bool(true),\n})\n\nresumed, err := client.ResumeSession(ctx, session.SessionID, &copilot.ResumeSessionConfig{\n\tOnPermissionRequest: copilot.PermissionHandler.ApproveAll,\n\tEnableCitations:     copilot.Bool(true),\n})\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n<!-- docs-validate: skip -->\n\n```csharp\nvar session = await client.CreateSessionAsync(new SessionConfig\n{\n    OnPermissionRequest = PermissionHandler.ApproveAll,\n    EnableCitations = true,\n});\n\nvar resumed = await client.ResumeSessionAsync(session.SessionId, new ResumeSessionConfig\n{\n    OnPermissionRequest = PermissionHandler.ApproveAll,\n    EnableCitations = true,\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n<!-- docs-validate: skip -->\n\n```java\nCopilotSession session = client\n        .createSession(new SessionConfig()\n                .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)\n                .setEnableCitations(true))\n        .get();\n\nCopilotSession resumed = client\n        .resumeSession(session.getSessionId(), new ResumeSessionConfig()\n                .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)\n                .setEnableCitations(true))\n        .get();\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"rust\" data-label=\"Rust\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Rust</div>\n\n<!-- docs-validate: skip -->\n\n```rust\nlet session = client\n    .create_session(\n        SessionConfig::new()\n            .approve_all_permissions()\n            .with_enable_citations(true),\n    )\n    .await?;\n\nlet resumed = client\n    .resume_session(\n        ResumeSessionConfig::new(session.id().clone())\n            .approve_all_permissions()\n            .with_enable_citations(true),\n    )\n    .await?;\n```\n\n</div>\n\n</div>\n\n## Read citations from assistant messages\n\nCitations arrive on the final `assistant.message` event, not on `assistant.message_delta` events. Wait for the final message before you render source markers.\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n<!-- docs-validate: skip -->\n\n```typescript\nsession.on((event) => {\n    if (event.type !== \"assistant.message\" || !event.data.citations) {\n        return;\n    }\n\n    const { sources, spans } = event.data.citations;\n    const sourceById = new Map(sources.map((source) => [source.id, source]));\n\n    for (const span of spans) {\n        const quoted = event.data.content.slice(span.startIndex, span.endIndex);\n        for (const reference of span.references) {\n            const source = sourceById.get(reference.sourceId);\n            const label = source?.title ?? source?.url ?? source?.path ?? source?.id;\n            console.log(`\"${quoted}\" — ${label}`);\n        }\n    }\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n<!-- docs-validate: skip -->\n\n```python\nfrom copilot.session_events import SessionEventType\n\ndef utf16_slice(text: str, start: int, end: int) -> str:\n    \"\"\"Slice by UTF-16 code units, which is how span offsets are measured.\"\"\"\n    units = text.encode(\"utf-16-le\")\n    return units[start * 2 : end * 2].decode(\"utf-16-le\")\n\ndef handle(event):\n    if event.type != SessionEventType.ASSISTANT_MESSAGE or not event.data.citations:\n        return\n\n    sources = {source.id: source for source in event.data.citations.sources}\n\n    for span in event.data.citations.spans:\n        quoted = utf16_slice(event.data.content, span.start_index, span.end_index)\n        for reference in span.references:\n            source = sources[reference.source_id]\n            label = source.title or source.url or source.path or source.id\n            print(f'\"{quoted}\" — {label}')\n\nsession.on(handle)\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n<!-- docs-validate: skip -->\n\n```golang\n// import \"unicode/utf16\"\n\nsession.On(func(event copilot.SessionEvent) {\n\td, ok := event.Data.(*copilot.AssistantMessageData)\n\tif !ok || d.Citations == nil {\n\t\treturn\n\t}\n\n\tsources := map[string]copilot.CitationSource{}\n\tfor _, source := range d.Citations.Sources {\n\t\tsources[source.ID] = source\n\t}\n\n\t// Span offsets are UTF-16 code units, so index the UTF-16 view of the content.\n\tunits := utf16.Encode([]rune(d.Content))\n\n\tfor _, span := range d.Citations.Spans {\n\t\tquoted := string(utf16.Decode(units[span.StartIndex:span.EndIndex]))\n\t\tfor _, reference := range span.References {\n\t\t\tsource := sources[reference.SourceID]\n\t\t\tlabel := source.ID\n\t\t\tswitch {\n\t\t\tcase source.Title != nil:\n\t\t\t\tlabel = *source.Title\n\t\t\tcase source.URL != nil:\n\t\t\t\tlabel = *source.URL\n\t\t\tcase source.Path != nil:\n\t\t\t\tlabel = *source.Path\n\t\t\t}\n\t\t\tfmt.Printf(\"%q — %s\\n\", quoted, label)\n\t\t}\n\t}\n})\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n<!-- docs-validate: skip -->\n\n```csharp\nsession.On<SessionEvent>(evt =>\n{\n    if (evt is not AssistantMessageEvent message || message.Data.Citations is null)\n    {\n        return;\n    }\n\n    var sources = message.Data.Citations.Sources.ToDictionary(source => source.Id);\n\n    foreach (var span in message.Data.Citations.Spans)\n    {\n        var quoted = message.Data.Content[(int)span.StartIndex..(int)span.EndIndex];\n        foreach (var reference in span.References)\n        {\n            var source = sources[reference.SourceId];\n            var label = source.Title ?? source.Url ?? source.Path ?? source.Id;\n            Console.WriteLine($\"\\\"{quoted}\\\" — {label}\");\n        }\n    }\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n<!-- docs-validate: skip -->\n\n```java\nsession.on(AssistantMessageEvent.class, event -> {\n    Citations citations = event.getData().citations();\n    if (citations == null) {\n        return;\n    }\n\n    Map<String, CitationSource> sources = citations.sources().stream()\n            .collect(Collectors.toMap(CitationSource::id, source -> source));\n\n    for (CitationSpan span : citations.spans()) {\n        String quoted = event.getData().content()\n                .substring(span.startIndex().intValue(), span.endIndex().intValue());\n        for (CitationReference reference : span.references()) {\n            CitationSource source = sources.get(reference.sourceId());\n            String label = source.title() != null ? source.title()\n                    : source.url() != null ? source.url()\n                    : source.path() != null ? source.path()\n                    : source.id();\n            System.out.printf(\"\\\"%s\\\" — %s%n\", quoted, label);\n        }\n    }\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"rust\" data-label=\"Rust\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Rust</div>\n\n<!-- docs-validate: skip -->\n\n```rust\nuse github_copilot_sdk::session_events::AssistantMessageData;\nuse std::collections::HashMap;\n\nlet mut events = session.subscribe();\n\nwhile let Ok(event) = events.recv().await {\n    if event.event_type != \"assistant.message\" {\n        continue;\n    }\n\n    let Some(data) = event.typed_data::<AssistantMessageData>() else {\n        continue;\n    };\n    let Some(citations) = data.citations.as_ref() else {\n        continue;\n    };\n\n    let sources: HashMap<&str, _> = citations\n        .sources\n        .iter()\n        .map(|source| (source.id.as_str(), source))\n        .collect();\n\n    // Span offsets are UTF-16 code units, so index the UTF-16 view of the content.\n    let units: Vec<u16> = data.content.encode_utf16().collect();\n\n    for span in &citations.spans {\n        let quoted = String::from_utf16_lossy(\n            &units[span.start_index as usize..span.end_index as usize],\n        );\n        for reference in &span.references {\n            let Some(source) = sources.get(reference.source_id.as_str()) else {\n                continue;\n            };\n            let label = source\n                .title\n                .as_deref()\n                .or(source.url.as_deref())\n                .or(source.path.as_deref())\n                .unwrap_or(source.id.as_str());\n            println!(\"\\\"{quoted}\\\" — {label}\");\n        }\n    }\n}\n```\n\n</div>\n\n</div>\n\n## Citation payload reference\n\nThe `citations` object separates deduplicated sources from the spans that reference them, so a source cited five times appears once in `sources`.\n\n| Type                | Field               | Description                                                                          |\n| ------------------- | ------------------- | ------------------------------------------------------------------------------------ |\n| `Citations`         | `sources`           | Deduplicated set of sources referenced by the citation spans                         |\n| `Citations`         | `spans`             | Spans of generated text annotated with their supporting sources                      |\n| `CitationSource`    | `id`                | Stable, turn-scoped identifier referenced by `CitationReference.sourceId`            |\n| `CitationSource`    | `provider`          | System that produced the citation: `anthropic`, `openai`, or `client`                |\n| `CitationSource`    | `title?`            | Human-readable title of the source                                                   |\n| `CitationSource`    | `url?`              | URL of the source, when it is a web resource                                         |\n| `CitationSource`    | `path?`             | File path relative to the agent workspace root, when the source is a file            |\n| `CitationSpan`      | `startIndex`        | Start offset in the final message content (UTF-16 code units, zero-based, inclusive) |\n| `CitationSpan`      | `endIndex`          | End offset in the final message content (UTF-16 code units, zero-based, exclusive)   |\n| `CitationSpan`      | `references`        | The sources that support this span                                                   |\n| `CitationReference` | `sourceId`          | Identifier of the `CitationSource` this reference points to                          |\n| `CitationReference` | `citedText?`        | Exact text from the source that supports the span, when the model provides it        |\n| `CitationReference` | `location?`         | Location within the source that supports the span                                    |\n| `CitationReference` | `providerMetadata?` | Provider-native correlation data, passed through opaquely                            |\n\n> \\[!TIP]\n> Span offsets are measured in UTF-16 code units against the final `content` string. TypeScript, Java, and .NET strings are already UTF-16, so you can slice them directly. Python strings are indexed by Unicode code point and Go and Rust strings are UTF-8, so convert the content to UTF-16 code units before slicing, as the examples above do.\n\n### Citation locations\n\n`CitationReference.location` is a discriminated union keyed on `type`:\n\n| Location type | Fields                   | Use                                              |\n| ------------- | ------------------------ | ------------------------------------------------ |\n| `char`        | `startIndex`, `endIndex` | Character range within the source text           |\n| `page`        | `startPage`, `endPage`   | Page range within a paginated document           |\n| `block`       | `startBlock`, `endBlock` | Content-block range within a structured document |\n\n## Provide citable sources\n\nCitations need source material the model can attribute. There are two ways to supply it.\n\n### Attach documents to a message\n\nWhen citations are enabled and the session uses an Anthropic provider, file attachments are sent as `document` blocks with citations turned on, so the model can cite passages from them.\n\n<!-- docs-validate: skip -->\n\n```typescript\nawait session.sendAndWait({\n    prompt: \"Summarize the attached PDF and cite the passages you used.\",\n    attachments: [\n        {\n            type: \"blob\",\n            data: pdfBase64,\n            displayName: \"quarterly-report.pdf\",\n            mimeType: \"application/pdf\",\n        },\n    ],\n});\n```\n\nSee [Image input](/en/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/features/image-input) for the attachment API and the `file` and `blob` attachment shapes.\n\n### Return citable sources from a tool\n\nTool results carry an experimental `citableSources` array. Each entry supplies `content` that the model can cite, along with an `id` and optional `title`, `url`, and `path`. These sources are persisted with the tool result, so they survive session resume, and citations built from them are tagged with the `client` provider.\n\n## Limitations\n\n* Citations are experimental in every SDK and are not covered by compatibility guarantees.\n* Coverage depends on the model provider. A session configured for a provider without citation support emits no `citations` payload.\n* Citations are only present on the final `assistant.message` event, so streaming consumers cannot render them mid-response.\n* Public code and IP-duplication citations are not part of this surface.\n\n## Further reading\n\n* [Streaming session events](/en/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/features/streaming-events): subscribe to session events and narrow event types\n* [Image input](/en/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/features/image-input): attach files and in-memory blobs to a message\n* [Session resume and persistence](/en/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/features/session-persistence): resume sessions and re-apply session options\n* [SDK and CLI compatibility](/en/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/troubleshooting/compatibility): SDK and CLI feature matrix"}