> ## Documentation Index
> Fetch the complete documentation index at: https://docs.athenahq.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Power BI & Microsoft Fabric

> Pull AthenaHQ data into Power BI Desktop, Power BI Service, or Microsoft Fabric via the REST API

AthenaHQ does not ship a native connector for Power BI or Microsoft Fabric, but the public REST API works directly with Power Query M, the same language used in Power BI Desktop, Power BI Service, and Fabric Dataflow Gen2.

## Prerequisites

* An AthenaHQ API key (Organization → API tab). See the [Authentication](/api-reference/authentication) page.
* The `website_id` you want to report on. Fetch it with `GET /api/v1/websites`.
* Power BI Desktop, or a Microsoft Fabric workspace with Dataflow Gen2 enabled.

## Setup

<Steps>
  <Step title="Create an API key">
    Go to the [API Keys page](https://app.athenahq.ai/organization?tab=api) in your AthenaHQ dashboard. Select the websites the key should access, create the key, and save it securely.

    <Card title="Manage API keys" icon="key" href="https://app.athenahq.ai/organization?tab=api">
      Create and manage API keys in your organization settings
    </Card>
  </Step>

  <Step title="Look up your website_id">
    Run the following from your terminal:

    ```bash theme={null}
    curl https://api.athenahq.ai/api/v1/websites \
      -H "x-api-key: YOUR_API_KEY"
    ```

    Copy the `id` of the website you want to pull into Power BI or Fabric.
  </Step>

  <Step title="Add the query in Power BI Desktop or Fabric">
    * **Power BI Desktop:** Home → Get data → Blank query → Advanced editor.
    * **Microsoft Fabric:** Workspace → New → Dataflow Gen2 → Get data → Blank query → Advanced editor.

    Paste the M snippet below, replacing `YOUR_API_KEY`, `YOUR_WEBSITE_ID`, and the date range. `start_date` / `end_date` accept ISO-8601 date-time strings (UTC).

    ```m theme={null}
    let
      ApiKey   = "YOUR_API_KEY",
      Website  = "YOUR_WEBSITE_ID",
      // Adjust to your reporting window. Format: ISO-8601 UTC.
      Body     = Json.FromValue([
                   website_id = Website,
                   filters    = [
                     start_date = "2024-01-01T00:00:00.000Z",
                     end_date   = "2024-01-31T23:59:59.999Z"
                   ]
                 ]),
      Source   = Web.Contents(
                   "https://api.athenahq.ai",
                   [
                     RelativePath = "/api/v1/metrics/share-of-voice/time-series",
                     Headers      = [
                       #"x-api-key"    = ApiKey,
                       #"Content-Type" = "application/json"
                     ],
                     Content      = Body
                   ]
                 ),
      Json     = Json.Document(Source),
      Data     = Json[data]
    in
      Data
    ```

    For `GET` endpoints such as `/api/v1/prompts` or `/api/v1/competitors`, omit `Content` and pass parameters through `Query` instead:

    ```m theme={null}
    let
      ApiKey   = "YOUR_API_KEY",
      Website  = "YOUR_WEBSITE_ID",
      Source   = Web.Contents(
                   "https://api.athenahq.ai",
                   [
                     RelativePath = "/api/v1/prompts",
                     Query        = [ website_id = Website ],
                     Headers      = [ #"x-api-key" = ApiKey ]
                   ]
                 ),
      Json     = Json.Document(Source),
      Data     = Json[data]
    in
      Data
    ```
  </Step>

  <Step title="Shape the response into a table">
    Each metrics endpoint returns `{ "data": [...] }`. The share-of-voice time-series response is an array of rows keyed by date, with one record per company. Expand it like so:

    ```m theme={null}
    let
      AsTable  = Table.FromList(Data, Splitter.SplitByNothing(), {"row"}),
      Expanded = if List.IsEmpty(Data) then
                   AsTable
                 else
                   Table.ExpandRecordColumn(
                     AsTable,
                     "row",
                     Record.FieldNames(AsTable{0}[row])
                   )
    in
      Expanded
    ```

    The `List.IsEmpty` guard keeps scheduled refreshes from erroring when the date range returns no rows. Adjust the expansion step to match the response shape of the endpoint you call.
  </Step>

  <Step title="Configure credentials in Power BI Service or Fabric">
    When the dataset or dataflow is published, set the data source credential for `https://api.athenahq.ai` to **Anonymous**. The API key is passed through the request header rather than via Power BI's credential store, since Power BI's built-in **Web API** credential type only supports an `Authorization` header and cannot store a custom `x-api-key` header.

    If your organization requires on-premises data gateways for external traffic, route the connection through the configured gateway.
  </Step>
</Steps>

## Security considerations

<Warning>
  When the API key is embedded inside an M query, **any user with edit, build, or contributor access to the dataset, dataflow, or `.pbix` file can open Power Query and read the key**. Storing the key as a Power BI Parameter has the same exposure: parameters are visible to editors. Treat the key as shared credential material and apply the safeguards below.
</Warning>

Recommended practices:

* **Use a dedicated, website-scoped API key for this integration.** Create a separate key from your other automation, and scope it to only the website(s) you need to report on. See [API key scoping](/api-reference/authentication#api-key-scoping). Do not reuse a broadly-scoped organization key.
* **Restrict workspace access.** Limit edit / build / contributor permissions on the Power BI workspace, dataset, dataflow, and source `.pbix` to the smallest set of trusted users. Viewers in Power BI Service cannot read M source, but anyone with access to the `.pbix` file can open it in Desktop and read it.
* **Rotate on suspected exposure.** If the workspace membership changes, a `.pbix` is shared externally, or you suspect leakage, rotate the key from the [API Keys page](https://app.athenahq.ai/organization?tab=api).
* **Avoid committing `.pbix` files containing the key to source control.** Strip the key (or use a parameter overridden in the service) before committing.

Higher-assurance alternatives (for teams with strict secret-handling requirements):

* **Run a thin proxy you control.** Host a small endpoint (Azure Function, Cloud Function, internal service) that injects the AthenaHQ API key from a secure store before forwarding the request. The proxy itself **must** require its own authentication (e.g. an Entra ID / Microsoft account, a per-caller token, mTLS, or be reachable only from an internal network or via the Power BI on-premises data gateway). If the proxy is left anonymous on the public internet, anyone who learns its URL can call AthenaHQ on your behalf, exfiltrate your data, and burn through your quota. The key never lives in the M query, but it inherits whatever access controls you put in front of the proxy.
* **Build a Power BI custom connector (`.mez`).** Custom connectors can use Power BI's credential store for arbitrary headers, keeping the key out of report source. This requires connector development and admin distribution.

## Endpoints

Any endpoint from the [API reference](/api-reference/introduction) can be called from Power Query. Common starting points:

| Endpoint                                     | Method | Purpose                                           |
| -------------------------------------------- | ------ | ------------------------------------------------- |
| `/api/v1/metrics/share-of-voice/time-series` | POST   | Daily share of voice across brand and competitors |
| `/api/v1/metrics/mention-rate/time-series`   | POST   | Mention rate across LLM responses over time       |
| `/api/v1/metrics/citation-rate/time-series`  | POST   | Citation rate over time                           |
| `/api/v1/metrics/position/time-series`       | POST   | Average response position over time               |
| `/api/v1/prompts`                            | GET    | List tracked prompts                              |
| `/api/v1/competitors`                        | GET    | List configured competitors                       |

Each metrics endpoint also has a `/cumulative` variant that returns a single rolled-up value for the date range. Use the POST snippet for metrics endpoints and the GET snippet for `prompts` / `competitors`.

## Authentication

All requests use the `x-api-key` header. See [Authentication](/api-reference/authentication) for full details, including API key scoping.

## Rate limits

API requests are subject to the limits documented on the [Rate limits](/api-reference/rate-limits) page. Power BI scheduled refreshes count against the same quota, so stagger refreshes if you connect many reports.

## Troubleshooting

* **`401 Unauthorized`**: The API key is missing, revoked, or not scoped to the requested `website_id`.
* **Credentials prompt loops in Power BI Service**: Confirm the data source credential is set to **Anonymous** for `https://api.athenahq.ai`. Power BI cannot store the `x-api-key` header itself, so it must live inside the M query.
* **`Web.Contents` dynamic URL warning**: Keep the base URL hard-coded and pass dynamic parts through `RelativePath` and `Query` options. The snippet above already follows this pattern.
* **`405 Method Not Allowed`**: Calling a `GET` endpoint (e.g. `/api/v1/prompts`) with `Content` set forces a POST and is rejected. Use the `GET` variant of the snippet.
