Skip to content

Expose an existing database

When builders need data that already lives in an enterprise system, you expose selected tables read-only, under governance: no one touches the source system’s code, and sensitive columns never leave the boundary. Use the admin console for the whole workflow: register, discover, review the schema, choose what to expose, and disable, enable, or delete the source. Claude can drive the same operations if you prefer to automate them. There are no scripts to run.

Phase 1 sources are Azure SQL Database with Entra sign-in, Fabric Warehouse and lakehouse SQL analytics endpoints (fabric-dw) - see § 5 below for what is available today - and, newly registrable, Power BI / Fabric semantic models (fabric-semantic), addressed by workspace and item GUID; their schema (tables and measures) is discovered as the signed-in Ops user once the registration runbook’s grants are applied, and their tables and measures expose read-only, always queried under each viewer’s own identity - see § 6 below. Other engines (PostgreSQL, MySQL, Cosmos) are out of scope for now.

Use the Azure SQL logical server name or its exact <server>.database.windows.net host. IP addresses, private aliases, and other host suffixes are rejected. Private Link remains supported because the standard Azure SQL host resolves through your private DNS.

1. Register the source in the admin console

Section titled “1. Register the source in the admin console”

Sign in to the admin console with the Ops role, open Sources, and select Register source. Enter:

  • a stable source ID and display name;
  • the Azure SQL logical server, database, and schema;
  • the Entra groups intended to read from the source.

The reader-group field lists existing per-app builder and viewer groups as checkboxes. Use the free-text field for another exact group name, including a group you are pre-granting before it exists.

Do not provide a connection string, username, password, authentication mode, or Key Vault secret. The form does not accept them. sprig stores only the structured server, database, schema, and reader-group metadata needed for Entra on-behalf-of access.

Select Register and discover. sprig tries to read the schema as you (your Entra identity). The first attempt is expected to fail: your account and the sprig groups do not yet have a login on that database.

Every source shows where its access is enforced, next to its status. Azure SQL sources read per-user (OBO): when a builder or an app queries the source, sprig exchanges that person’s Entra token and the database authorizes their own identity, so what any one person can read is decided by your grants in the database, not by sprig. This is a recorded property of the source, not a guess, and it is the only mode Azure SQL can be registered in.

This describes the query path. It is not a claim that sprig holds no access at all: the data layer keeps a standing read-only login of its own, which it uses to read the schema at startup. That grant is section 2 below, and the reasons it exists and how it is contained are set out there.

The other value, gateway-enforced, means a service identity connects to the source and sprig filters per viewer above the database. Azure SQL cannot be registered in this mode; fabric-dw (§ 5) is registered in it only, because that is the only mode it supports today. Choosing it is always an explicit decision, and sprig writes an audit event for it, so the weaker guarantee is always visible in the audit trail rather than buried in a configuration.

The source ID, server, and database are fixed after registration. Select the source in the grid, then select Edit to change the display name, schema, or reader groups. Change the schema before exposing entities; once entities are exposed, remove them before changing schema. The console uses the displayed source version to prevent one operator from overwriting another operator’s change.

When discovery is denied, the admin console gives you the exact T-SQL for the database owner to run once - the source DBA’s only involvement:

CREATE USER [sprig-ops] FROM EXTERNAL PROVIDER;
GRANT SELECT ON SCHEMA::[dbo] TO [sprig-ops];
CREATE USER [sprig-analytics-builders] FROM EXTERNAL PROVIDER;
GRANT SELECT ON SCHEMA::[dbo] TO [sprig-analytics-builders];
-- Required: the sprig data layer reads this schema at startup as its own
-- managed identity. Without this it cannot serve ANY query from this source.
CREATE USER [id-sprig-serving] FROM EXTERNAL PROVIDER;
GRANT SELECT ON SCHEMA::[dbo] TO [id-sprig-serving];

The first principal lets sprig discover the schema (as you); the reader groups let builders query later. Select Copy SQL, forward it, wait for it to run, then select Retry discovery. Discovery now returns the list of tables and views.

The groups above are only ever used on behalf of a signed-in person, so nothing sprig holds can read your data on its own. The serving identity is the one exception, and it is worth understanding before you approve the grant.

The sprig data layer reads each source’s schema when it starts, as itself, not as any end user. It has to know the shape of your tables before it can accept a query, and at that moment no one is signed in. So it needs a standing read-only login in the source database. Without it the data layer will not start against that source and every query fails, whatever else is configured.

Run the grant before the source goes into service, not after. The data layer is all or nothing about the sources it is given: if it cannot reach one of them at startup it refuses to start at all, rather than serving the others. A source that is missing this grant therefore stops queries against every other source too, not just its own. Sprig protects you from the worst of this by returning the platform to its previous configuration when a newly exposed source cannot be served, so the failure surfaces as a failed exposure rather than an outage.

What that does and does not mean:

  • It is SELECT only, and only on the schema you exposed.
  • It reads schema shape at startup. Row data is still only ever read on behalf of a signed-in user, under that person’s own permissions.
  • It is one identity for the platform, named id-<prefix>-serving, not one per app or per builder.

If a standing principal in the source database is not acceptable to your data owners, sprig cannot serve that database, and the right answer is to expose a replica or a dedicated reporting database instead.

Open the source from Sources. The schema view shows every discovered table and view, its columns and types, how the tables relate, and which columns look sensitive.

Tick the tables you want to expose, and clear the checkbox on any column that must stay hidden. Columns flagged as sensitive are marked inline, so exposing one is a deliberate, visible act rather than an oversight. Select Save exposure when the selection is right.

The saved selection is the exposure: anything left unticked is withdrawn. Revisit the source at any time to see what is exposed today and change it in place.

If you would rather automate this, Claude performs the same operation and writes through the same service:

“Expose Claims, Policies and Customers, read-only, hide Customers.SSN and Customers.Phone, for the analytics-builders group.”

Grant the app’s own group, not only a general one

Section titled “Grant the app’s own group, not only a general one”

If the data is going to be read by a published app, the app’s own group has to be among the groups that can read the source. A general group such as analytics-builders is enough for a builder querying the source during a session, but it is not enough for an app: when a request comes from a published app, sprig presents that app’s own group, so the source has to name it. This is deliberate, and it is what stops any app from reading a source just because the person using it has rights to that source by some other route.

You normally do not add the app’s group here by hand. The app’s owner files a source access request (at provision time or later), and your approval adds the app’s group across the source’s exposed entities in one step; see App access to sources. Granting the app’s group directly on an exposure remains possible and has the same effect.

The symptom when the app’s group is missing everywhere is a plain 403 from the app, even though the person using it is in one of the groups you granted. If you see that, check the app’s source access requests first, then the exposure’s groups.

Expect a few minutes before it takes effect. Sprig has to create the matching permission and assign the group to it, and Entra takes time to put that into newly issued sign-ins. A retry that still fails a minute later is not evidence that the grant was wrong.

sprig records the selection, regenerates the data API’s configuration, and rolls it out (about a minute). Excluded fields are removed at the API level: a builder mapped to the source can’t see or query them, and they never appear in the source description a builder sees.

Everything (the registration, the schema discovery, the selection and the excludes) lands in the audit trail. Registering and exposing sources requires the Ops role.

Select one source in the admin console grid, then use the actions in the selection bar. Each action uses the version shown for that source.

  • Disable keeps the registration, discovered schema, and exposed-entity choices, but removes the source from service.
  • Enable runs discovery again before returning a disabled source to service. If database access changed, you receive the DBA grant instructions again.
  • Delete removes the source registration, schema, and exposed-entity metadata only after withdrawing it from service. Its audit history remains.

While an action is in progress, the selection bar offers refresh only. If another operator changes the source first, the console refreshes the list and asks you to review the current version instead of retrying the change silently. Sprig currently requires at least one served data source, so disabling or deleting the final one is refused without withdrawing it.

5. Fabric Warehouse and lakehouse sources (fabric-dw)

Section titled “5. Fabric Warehouse and lakehouse sources (fabric-dw)”

You can register a Fabric Warehouse or a lakehouse SQL analytics endpoint today, using the exact <item>.datawarehouse.fabric.microsoft.com host Fabric gives you for that item, plus the database name. There is no default database: type it in yourself.

This kind is read-only and gateway-enforced only: sprig connects with its own service identity rather than yours, and per-viewer access is enforced at the gateway rather than by the warehouse itself. The console shows this as the gateway-enforced badge, and the registration writes an audit event recording the choice, the same as any other gateway-enforced source.

Registering a Fabric source reads its tables, views, and columns, and the console shows them the same way it shows an Azure SQL source’s. Exposing them to apps works too. Two things differ from Azure SQL, and both matter before you register.

sprig reads the Fabric endpoint as its own service identity, not as you. Discovery therefore succeeds or fails on that identity’s access, which is also the access that decides whether the source can be served at all. If it has no access yet, registration still succeeds and sprig hands you the exact grant runbook: a Fabric workspace role assignment for the sprig serving identity and the Ops discovery group, then a GRANT SELECT on the schema. Entra group role assignments can take up to an hour to reach a Fabric SQL endpoint after they are applied; if you have already granted access, wait and retry rather than granting again.

Every table you expose needs a declared primary key, and this is what decides which Fabric tables you can serve. The data API refuses a table that has no primary key, and it refuses to start at all rather than skipping that one table, so sprig checks it when you expose and rejects the table rather than letting it reach the data API.

  • On a Fabric Warehouse you can satisfy it yourself, by adding PRIMARY KEY NONCLUSTERED (<key columns>) NOT ENFORCED to the table. Those tables are servable today.
  • On a lakehouse SQL analytics endpoint you cannot: the endpoint is read-only, its tables carry no SQL primary key, and it does not accept ALTER TABLE. Lakehouse tables without a primary key are not servable yet. They will be once sprig lets you nominate the key columns yourself instead of expecting them from Fabric. That is the next step on our side, and it does not affect warehouse sources.

Views are not exposable on this kind either, for the same reason: sprig serves Fabric entities by their primary key, and a view has none to serve them by.

6. Fabric semantic models (fabric-semantic)

Section titled “6. Fabric semantic models (fabric-semantic)”

A semantic model is registered by its Fabric workspace and item GUIDs (there is no server or database): pick “Fabric semantic model” in the console’s kind picker, or pass --kind fabric-semantic --workspace-id <guid> --item-id <guid> on the CLI. The kind is always db-delegated: every query runs under the viewer’s own identity, so row-level security defined in the model applies as-is (a service-identity mode does not exist for this kind, because a service principal forfeits the model’s RLS).

One-time setup, listed by the registration flow with a copy-pastable runbook:

  1. The “Dataset Execute Queries REST API” tenant setting must be enabled.
  2. Reader groups need item permission Read + Build on the model (grantable in the portal, or per group via the Power BI API; the runbook shows the exact call).
  3. Every viewer needs a Power BI Pro/PPU license, or the workspace must run on an F64-or-larger capacity; an unlicensed viewer’s queries fail with “UserNotLicensed”.

Discovery lists the model’s tables and measures; entities expose under the model. prefix (for example model.Sales), with measures as read-only fields.

Model tables are often named with spaces or punctuation, such as Sales Orders or Date (Fiscal), and an API entity name cannot contain either. Exposing one from the console generates a legal name for it automatically: punctuation becomes underscores, so Sales Orders is served as Sales_Orders, and if two tables would end up with the same name the second gets a numeric suffix. The name it chose is shown on the table once the exposure is saved, and it is what builders see in the API. A Direct Lake model that has never been refreshed reports no tables at all (the engine only materializes them on first refresh), so if discovery comes back empty for a model you know has tables, ask the model owner to refresh it once and re-run discovery. Exposed entities serve read-only through the app data API with $select, $filter (eq ne gt ge lt le and or not), $orderby, and $first/$after paging. Date and time fields are selectable and orderable but not yet filterable (the request is refused with a clear error); the same applies to $filter on field names containing spaces. Permission and role changes can take a while to reach the model’s query engine (about half an hour has been observed); the grant probe reports “granted, propagating” distinctly from “denied”, so wait and retry rather than re-granting.

Fabric sources need one extra Entra app registration, supplied to the installer. Until it is, registering a Fabric source still succeeds and the console shows it as registered, but its schema view stays empty.

Register an application in your Entra tenant. It needs no redirect URI, no client secret and no API permissions: sprig uses it only as the audience of an internal token, so all you need from it is its Application ID URI and its client id.

Two of the four steps below are easy to miss, and both fail the same way: every schema read returns an error. Creating the application does not create its service principal, and a new registration issues v1.0 access tokens where sprig requires v2.0.

Terminal window
# 1. The application object.
az ad app create --display-name sprig-fabric-connector
# 2. Its service principal in your tenant. This is a SEPARATE object from the
# application, and it is the one Entra checks when sprig asks for a token for
# this audience. Without it the request fails with AADSTS500011 ("resource
# principal not found") and every schema read fails.
az ad sp create --id <your-connector-client-id>
# 3. Version 2 access tokens. A new registration issues version 1, which sprig
# refuses.
az ad app update --id <your-connector-client-id> \
--set api.requestedAccessTokenVersion=2
# 4. The Application ID URI you will pass to the installer.
az ad app update --id <your-connector-client-id> \
--identifier-uris api://<your-connector-client-id>

In the portal, step 2 happens automatically when you add the application to Enterprise applications, and step 3 is App registrations > your app > Manifest > requestedAccessTokenVersion, changed from null to 2.

Then set all three values in the environment and install:

Terminal window
export SPRIG_FABRIC_CONNECTOR_IMAGE=<digest-pinned image from your release>
export SPRIG_FABRIC_CONNECTOR_AUDIENCE=api://<your-connector-client-id>
export SPRIG_FABRIC_CONNECTOR_APP_ID=<your-connector-client-id>
sprig infra install --prefix <prefix> --region <azure-region>

The image comes from the release you are installing, pinned by digest like every other image the installer takes. The other two are different values and neither substitutes for the other.

Fabric support is enabled by all three together, so setting only some of them is refused before the install starts, naming what is missing. All three need to be set for sprig infra upgrade too.

If you register a Fabric source before doing this, nothing is lost: re-register it afterwards and its schema is read then.