> This is the Markdown of a Backed Help Centre page, https://admin.backed.giving/help/integrations/odata-feed.md. Every page is listed at https://admin.backed.giving/help/llms.txt.

> This page uses Backed's default terms, such as Funding Round and Application Template. An organisation may rename them, so read each as your organisation's own term if you've renamed it.

The OData feed is a read-only address that business intelligence tools understand without any
custom code. Power BI, Excel and most data warehouse loaders can read it: they ask the feed what it
holds, then read each table page by page. It lives on your organisation's own address, under
`/odata/v4/`.

The feed reads the same records your organisation's reports read, and only your own organisation's.
Backed sends nothing anywhere: your tool asks, and the feed answers with the records as they are at
that moment.

## Before you start

- An Administrator creates an API credential under `Settings > Profile and terms`, on the
  `API Access` tab, as [API access](api-access.md) explains, and ticks `Read reporting feed` under
  `Scopes`. Only an Administrator who also holds `Can generate reports` can give that scope. You can
  add it to an existing credential with `Edit scopes`; it takes effect on the credential's next
  request, with no new token needed.
- Tick `Include bank account numbers` only if the tool needs applicants' full bank account numbers.
  Without it, the feed leaves those columns out.
- Keep the credential's `Client ID` and `Client Secret` to hand. The secret is shown once.
- Know your organisation's address: the one your team signs in at, such as
  `https://your-subdomain.backed.giving`. The feed is at that address plus `/odata/v4/`.

## How the feed signs in

Every request carries a token in an `Authorization: Bearer <token>` header. The tool gets the token
by sending the credential's `Client ID` and `Client Secret` to `/oauth/token` on your
organisation's address, exactly as in [Reporting feed for analytics tools](power-bi.md). The feed
takes the token only from that header, never from the address or a cookie.

A tool that can only ask for a user name and password, such as Tableau's or Power BI's `OData`
connector, can sign in to the feed with the credential itself: the `Client ID` as the user name and
the `Client Secret` as the password. The feed checks the pair on every request, just as
`/oauth/token` checks it, so there is no token to renew. Only the feed takes a user name and
password; the rest of the API takes a token alone. A wrong pair, or a deleted credential, answers
`401 Unauthorized`.

A token lasts five days. After that, every request answers `401 Unauthorized` until the tool asks
for a new one. **A scheduled refresh needs a fresh token**: either the query asks for one each time
it runs (the Power BI sample below does), or someone renews the token before it expires.

A credential without `Read reporting feed` answers `403` with `insufficient_scope`. A credential may
make 120 calls a minute, the feed and the rest of the API together; past that the feed answers `429`
with a `Retry-After` header.

## What the feed holds

| Entity set             | What each row is                                                         |
| ---------------------- | ------------------------------------------------------------------------ |
| `Applications`         | an Application, with a column for each of your Field Tags                |
| `Applicants`           | an applicant                                                             |
| `RegistrationEntities` | an applicant organisation or person                                      |
| `FundingRounds`        | a Funding Round                                                          |
| `Assessments`          | an assessment                                                            |
| `Decisions`            | a decision line                                                          |
| `Contracts`            | a contract                                                               |
| `Milestones`           | a milestone, with a column for each of your Field Tags on its deliverables |
| `Payments`             | a payment                                                                |
| `Funds`                | a Fund                                                                   |

The columns are those of the matching data source in a Backed report, so a column in your tool and
a column in a [data table](/administrator/reports/building-a-data-table) match by name. Every set
also has an `updated_at` column. The feed never carries people's email addresses, phone numbers,
postal addresses or first and last names, whatever the credential.

`/odata/v4/` lists the ten entity sets, and `/odata/v4/$metadata` describes every column and its
type, which is how a BI tool builds its tables. The API's OpenAPI document points to the feed but
does not describe it: `$metadata` does. See [Developer documentation](developer-documentation.md).

### Field Tag columns

On `Applications` and `Milestones`, each of your organisation's Field Tags is a text column named
after the tag, such as `amount_requested`. A character a column name can't hold becomes `_`, and a
name that would repeat another column's gets the tag's number added, such as `title_42`. A table or
repeatable answer comes back as one value, its cells separated by commas. Tag the fields you report
on, and the columns keep their names across Funding Rounds while the tags keep theirs.

### Value formats

Amounts are numbers. Dates are `YYYY-MM-DD`, and dates with times are in UTC, such as
`2026-11-08T03:15:00.000Z`.

## Ask for less: query options

Add these to an entity set's address to narrow what comes back:

| Option     | Example                                                       | What it does                                       |
| ---------- | ------------------------------------------------------------- | -------------------------------------------------- |
| `$select`  | `$select=record_id,application_title,current_status`          | only these columns                                 |
| `$filter`  | `$filter=grant_id eq 12 and submission_date ge 2026-07-01T00:00:00Z` | only rows that match                        |
| `$orderby` | `$orderby=submission_date desc`                               | sort by up to eight columns, each `asc` or `desc`  |
| `$top`     | `$top=200`                                                    | at most this many rows in all                      |
| `$count`   | `$count=true`                                                 | adds `@odata.count`, the number of rows that match |

A filter compares one column with one value using `eq`, `ne`, `gt`, `ge`, `lt` or `le`, and joins
comparisons with `and`, `or`, `not` and brackets. Text goes in single quotes, and dates are written
as `2026-07-01` or `2026-07-01T00:00:00Z`. A list or JSON column can't be filtered or sorted.

The feed answers `501` with `not_implemented` for an option it doesn't support, such as `$skip`,
`$expand`, `$search` or `$apply`, and for a filter that uses a function (`contains`,
`startswith`), arithmetic, `in` or `has`. A mistake, such as a column that doesn't exist or the same
option twice, answers `400` with `bad_request`. Both say which option was wrong. An entity set name
is case-sensitive; one that doesn't exist answers `404`.

### Pages

A page holds up to 1,000 rows. When there are more, the page ends with `@odata.nextLink`: the
address of the next page, with your options carried in it. Read that address next, and stop when a
page has no `@odata.nextLink`. Power BI and Excel do this for you. A `$top` larger than 1,000 is not
refused: you get 1,000 rows and a `@odata.nextLink` for the rest. Use the feed's own links rather
than `$skip`, which the feed does not support.

## Read only what changed

A warehouse loader that refreshes often can read only the rows changed since its last read, instead
of the whole set:

1. Read an entity set in full with the header `Prefer: odata.track-changes`. Follow every
   `@odata.nextLink` as usual. Leave out `$filter`: delta links are issued for unfiltered reads
   only (a filtered read is answered without one, and a delta link read with a `$filter` answers
   `400`), so apply any filter on your side.
2. The last page ends with `@odata.deltaLink` instead. Keep that address.
3. Next time, read the `@odata.deltaLink`. It returns the rows changed since step 1, and its last
   page ends with a new `@odata.deltaLink` for the time after that.

A changed row comes back whole. A deleted assessment, payment or Fund comes back as a deleted-entity
entry, in OData 4.0's form: `"@odata.context"` ending `$metadata#<EntitySet>/$deletedEntity`, an
`"id"` such as `"Payments(123)"`, `"reason": "deleted"` and the record's key, on the first page of
the delta read; the entry is dated from when the feed
recorded the deletion, a few seconds after it happened, so a delta read in that gap still gets it
next time. The other entity sets carry no
deleted-entity entries: most of their records are never deleted (they close, withdraw or decline, and
reach you as a changed row), but an Administrator can delete an application or a registration
entity, and such a record simply stops appearing without a removal entry. If your model must drop
deleted applications or organisations, schedule a full reload of `Applications`, `Applicants` and
`RegistrationEntities` (weekly is usual) beside the incremental refresh of the rest.

A delta link works only for the credential it was issued to, and only for 30 days after the read
that issued it. An older, unknown or another credential's delta link answers `410` with
`delta_token_expired`: read the entity set in full again, with `Prefer: odata.track-changes`, and
start over from the new link.

Power BI and Excel don't use delta links: each refresh reads the tables in full.

## Load the feed into Power BI Desktop

The simplest route is Power BI's own `OData feed` connector, signing in with the credential:

1. In Power BI Desktop, select `Get data`, then `OData feed`.
2. Under `URL`, enter your organisation's address plus `/odata/v4/`, such as
   `https://your-subdomain.backed.giving/odata/v4/`, and select `OK`.
3. When Power BI asks how to connect, choose `Basic`. Enter the `Client ID` as the `User name` and
   the `Client Secret` as the `Password`, and select `Connect`.
4. In the `Navigator`, tick the entity sets you want, then select `Load` or `Transform Data`.

Power BI keeps the pair with its data source settings, not in the `.pbix` file. If you would rather
the feed see a token, start from a blank query that asks for one and then opens the feed:

1. In Power BI Desktop, select `Get data`, then `Blank query`.
2. In the Power Query editor, select `Advanced Editor`, paste the sample below, and replace the
   three settings at the top with your organisation's address, `Client ID` and `Client Secret`.
3. Select `Done`. The query lists the ten entity sets; select `Table` beside each one you want.
4. If Power BI asks how to connect to your organisation's address, choose `Anonymous`: the query
   sends its own token.
5. Set column types where you need to, then select `Close & Apply`.

```powerquery
let
    // Your organisation's address and your credential's pair.
    BaseUrl = "https://your-subdomain.backed.giving",
    ClientId = "your-client-id",
    ClientSecret = "your-client-secret",

    // A token, asked for at every refresh (each lasts five days).
    Token = Json.Document(
        Web.Contents(BaseUrl, [
            RelativePath = "oauth/token",
            Headers = [#"Content-Type" = "application/x-www-form-urlencoded"],
            Content = Text.ToBinary(Uri.BuildQueryString([
                grant_type = "client_credentials",
                client_id = ClientId,
                client_secret = ClientSecret
            ]))
        ])
    )[access_token],

    // The feed, with the token on every request it makes.
    Feed = OData.Feed(
        BaseUrl & "/odata/v4/",
        null,
        [Headers = [Authorization = "Bearer " & Token], Implementation = "2.0"]
    )
in
    Feed
```

Filters and column choices you make in Power Query are sent to the feed as `$filter` and
`$select`, so only what you keep is read. A step the feed can't answer, such as `Remove Top Rows`
(sent as `$skip`) or a text filter like `Begins With` (sent as `startswith`), fails with `501`. Put
such a step after a `Table.Buffer` step, so Power BI applies it to rows it has already read.

### Refresh on a schedule

1. In Power BI Desktop, select `Publish` and choose a workspace.
2. In the Power BI service, open the semantic model's `Settings`.
3. Under `Data source credentials`, select `Edit credentials` for your organisation's address. For
   a model built with the `OData feed` connector, choose `Basic` and enter the `Client ID` and
   `Client Secret` again; for one built with the sample query, choose `Anonymous`.
4. Under `Refresh`, turn on `Configure a refresh schedule`, choose the times, and select `Apply`.

With `Basic`, every refresh signs in with the pair; the sample asks for a new token at every
refresh. Either way, the five-day token limit never stops it. If a
refresh fails with `401 Unauthorized`, the credential has been deleted or its secret is wrong; with
`403`, it has lost `Read reporting feed`. Keep the `.pbix` file to people who may see the
credential's secret, as it is written in the query.

## Load the feed into Excel

Excel's `Get Data > From Other Sources > From OData Feed` signs in as Power BI's `OData feed`
connector does: choose `Basic` and enter the `Client ID` and `Client Secret`. To send a token
instead, use the same query as Power BI:

1. In Excel, select `Data`, then `Get Data`, `From Other Sources`, `Blank Query`.
2. Select `Advanced Editor`, paste the Power BI sample above, and replace the three settings.
3. Select `Done`, choose `Anonymous` if Excel asks how to connect, pick the entity sets you want,
   then select `Close & Load`.

`Refresh All` on the `Data` tab asks for a new token each time.

## Tableau and other tools

Tableau Desktop reads the feed through its `OData` connector, signing in with the credential:

1. In Tableau Desktop, under `Connect`, select `To a Server`, then `OData`.
2. Under `Server`, enter your organisation's address plus `/odata/v4/`, such as
   `https://your-subdomain.backed.giving/odata/v4/`.
3. Under `Authentication`, choose `Username and Password`. Enter the `Client ID` as the `Username`
   and the `Client Secret` as the `Password`, and select `Sign In`.
4. Pick the entity set you want, such as `Applications`, and build your view. Add another OData
   connection for each further entity set.

Any other tool that can send an `Authorization` header with each request can read the feed, with a
token or with the credential's pair as a user name and password.

A loader that runs on a schedule should ask for a new token at each run, keep each entity set's
`@odata.deltaLink`, and read in full again when a delta link answers `410`.

## Who read what

Every read of an entity set is written to your organisation's `Audit log` as `reporting_feed.read`,
with the entity set, the credential and the number of rows. Reading the list of entity sets or
`$metadata` is not recorded, as neither holds any of your records.

## See also

- [Reporting feed for analytics tools](power-bi.md)
- [API access](api-access.md)
- [Developer documentation](developer-documentation.md)
- [Build a data table](/administrator/reports/building-a-data-table)
- [Frequently asked questions about integrations](faq-integrations.md)
