Issue with Brave AI Grounding API – Sources Not Returned

Hi Brave Devs,

I’m trying to use the Brave AI Grounding API via curl to get answers with all the source links included. While the API returns the answer text correctly, there are no sources attached—neither in a separate grounding.sources array nor inside the text via <citation> tags.

I tested using enable_research and enable_citations, in all combinations. When I specifically ask for sources, I sometimes get them at the end of the text, but not in a consistent format.

Here’s an example request I tried:

curl -X POST "https://api.search.brave.com/res/v1/chat/completions" \
  -H "accept: application/json" \
  -H "Content-Type: application/json" \
  -H "x-subscription-token: $BRAVE_API_KEY" \
  -d '{
        "stream": false,
        "messages": [
          {"role": "user", "content": "What is Trade Republic? Please provide your sources."}
        ],
        "extra_body": {"enable_citations": true}
      }'

Response snippet:

{
  "model": "brave-pro",
  "system_fingerprint": "",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Trade Republic Bank GmbH is a German online broker and neobank headquartered in Berlin. 
                    Founded in 2015 in Munich as Neon Trading within the startup incubator of Comdirect Bank,
                    the company was established by Christian Hecker (a philosopher), Thomas Pischke (a physicist),
                    and Marco Cancellieri (a computer scientist). It rebranded to Trade Republic and launched its mobile trading app in Germany in 2019,
                    initially for a closed user group before expanding to the general public.\n\nThe platform allows users to trade stocks, bonds, derivatives,
                    and cryptocurrencies commission-free via a mobile application. As of January 2025, Trade Republic reported eight million customers and
                    €100 billion in assets under management (AUM). The company serves customers across multiple European countries, including Germany, Austria,
                    France, Spain, and others.\n\nTrade Republic received a full banking license from Germany’s Federal Financial Supervisory Authority (BaFin)
                    at the end of 2023, enabling it to offer broader financial services. It began paying interest on cash deposits in January 2023 and launched
                    its own payment card in 2024. The broker introduced fractional share trading in October 2022, allowing customers to invest in stocks and ETFs
                    starting at €1 per order.\n\nIn May 2021, Trade Republic raised $900 million in funding at a $5 billion valuation, followed by an additional
                    €250 million in 2022. The company employed approximately 1,100 people as of 2025, though it underwent layoffs in 2022 due to workforce overexpansion
                    and challenging market conditions.\n\nClient funds are held in omnibus trust accounts with major banks such as Deutsche Bank AG, J.P. Morgan SE, HSBC
                    Continental Europe S.A., and Citibank Europe plc.\n\nSources:\n- Wikipedia: [Trade Republic](https://en.wikipedia.org/wiki/Trade_Republic)\n- 
                    PitchBook: [Trade Republic Company Profile](https://pitchbook.com/profiles/company/277451-74)\n- BrokerChooser: [Trade Republic Review 2025](https://brokerchooser.com/broker-reviews/trade-republic-review)"
      },
      "finish_reason": "stop"
    }
  ],
  "created": 1756128797,
  "id": "someid",
  "object": "chat.completion",
  "usage": {
    "completion_tokens": 466,
    "prompt_tokens": 6265,
    "total_tokens": 6731,
    "completion_tokens_details": {
      "reasoning_tokens": 0
    }
  }
}

As you can see, sources appear sometimes at the end of the text, but there is no consistent structure or separate array returned.

Could you clarify:

  1. Is this expected behavior?

  2. Am I missing any parameters to reliably get a structured sources array for every request?

Btw also tested using the provided python script from Documentation. Same results.

Thanks in advance for your help!

Please try with ”stream”: true,

I’ve run into the same issue before. Even with enable_citations set to true, Brave doesn’t always return sources in a structured grounding.sources array—it often just appends them as plain text. For now, it looks inconsistent and might be a bug rather than implementation error. Have you tried opening a support ticket on their GitHub or checking if there’s an update in their API docs?

stream “true” only works from time to time. Sometimes I recieve sources sometimes not.

did not found anything there the last time I looked. Switched to a different provider now and will give it another chance sometime in the future when it has gotten some updates.

In what format you expect to get these? The citations we offer do not come as part of the content as is shown in the example response snippet you posted.

When streaming and with enable_citations: true I get messages like:

{
  "model": "brave-pro",
  "system_fingerprint": "",
  "choices": [
    {
      "delta": {
        "role": "assistant",
        "content": "<citation>{\"start_index\": 451, \"end_index\": 454, \"number\": 2, \"url\": \"https://en.wikipedia.org/wiki/K2\", \"favicon\": \"https://imgs.search.brave.com/m6XxME4ek8DGIUcEPCqjRoDjf2e54EwL9pQzyzogLYk/rs:fit:32:32:1:0/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvNjQwNGZhZWY0/ZTQ1YWUzYzQ3MDUw/MmMzMGY3NTQ0ZjNj/NDUwMDk5ZTI3MWRk/NWYyNTM4N2UwOTE0/NTI3ZDQzNy9lbi53/aWtpcGVkaWEub3Jn/Lw\", \"snippet\": \"K2, at 8,611 metres (28,251 ft) above sea level, is the second-highest mountain on Earth, after Mount Everest at 8,849 metres (29,032 ft). It lies in the Karakoram range, partially in the Gilgit-Baltistan region of Pakistan-administered Kashmir and partially in the China-administered Trans-Karakoram Tract in the Taxkorgan Tajik Autonomous County of Xinjiang. K2 became known as the Savage Mountain \\u2026\"}</citation>"
      },
      "finish_reason": null
    }
  ],
  "created": 1756806450,
  "id": "98f69e95-d342-4f39-bace-0e03c0ab16e0",
  "object": "chat.completion.chunk",
  "usage": null
}

This citation tells which part of the text is from which source. If you check the Answers with AI on Brave Search, this is how the answer citations work.

If you just want simple sources, you can collect the URL:s from these messages.

thanks for the explanations so far. I tried to follow the suggestions and used stream: true with enable_citations: true. Below is the exact Python code I’m running:

import requests
import os
import json

url = "https://api.search.brave.com/res/v1/chat/completions"

payload = {
    "stream": True,
    "messages": [{"role": "user", "content": "What is Trade Republic?"}],
    "extra_body": {
        "enable_citations": True,
        "country": "us",
        "language": "en",
        "enable_entities": True,
    },
}
headers = {
    "x-subscription-token": os.getenv("BRAVE_API_KEY"),
    "accept": "application/json",
    "content-type": "application/json",
}

with requests.post(url, json=payload, headers=headers, stream=True) as response:
    if response.status_code != 200:
        print("Error:", response.status_code, response.text)
    else:
        for line in response.iter_lines():
            if not line:
                continue

            raw = line.decode("utf-8").strip()
            if raw.startswith("data:"):
                raw = raw[len("data:"):].strip()

            try:
                data = json.loads(raw)
                delta = data.get("choices", [{}])[0].get("delta", {}).get("content", "")
                print(delta, end="", flush=True)
            except json.JSONDecodeError:
                print("Could not decode:", raw)

When I run this, I don’t receive any content containing <citation>…</citation> blocks.
Instead, I only get content broken into single characters or symbols like:

data: {"model":"brave-pro","system_fingerprint":"","choices":[{"delta":{"role":"assistant","content":"M"},"finish_reason":null}],"created":1756978270,"id":"1152e944-b3e0-4617-bd7e-c2ad8102e7ae","object":"chat.completion.chunk","usage":null}

So my stream looks like a sequence of single letters rather than meaningful chunks with citations inside.

I’m not sure if I’m doing something wrong in my implementation, or if I’m misunderstanding how citations are supposed to be returned, but at the moment I never see any <citation> tags in the output.

Could you clarify if this is expected or if there’s something missing in my request setup?

Thanks!

You are using the “raw” API but using the payload which should be used with OpenAI SDK.

Change the payload to:

payload = {
    "stream": True,
    "messages": [{"role": "user", "content": "What is Trade Republic?"}],
    "enable_citations": True,
    "country": "us",
    "language": "en",
    "enable_entities": True,
}
1 Like