# auth.md

Authenticating with SchemaStack.

Machine-readable guidance for AI agents and API clients. SchemaStack is a
hosted data platform where the spreadsheet builds the database: editing the UI
creates real SQL schema in a PostgreSQL or MySQL database the customer owns,
with structural changes previewed as migrations first. SchemaStack stores only
metadata; customer records live in the customer's database, never here.

Human documentation: <https://docs.schemastack.io/>

## Can an agent obtain its own credentials?

**It can register, but it cannot authorise itself.** A client may register with
no credential at all (RFC 7591, below) and then take part in the OAuth flow —
which requires a signed-in person to consent at a browser. There is no path from
registration to data without that person.

An `mcp_` key or a Workspace API key is still issued only by a signed-in human
from the admin app at <https://schemastack.io/admin>.

Nothing here is a paywall an agent can clear on its own.

## Surfaces

| Surface | Endpoint | Credential |
|---|---|---|
| MCP server | <https://schemastack.io/mcp> | `mcp_…` key, or OAuth 2.0 access token |
| Workspace API (generated per workspace) | `https://data.schemastack.io/api/v1/{orgSlug}/{workspaceSlug}/{Entity}` | `sk_live_…` key, or OAuth 2.0 access token |
| OAuth 2.0 authorization server | <https://schemastack.io/.well-known/oauth-authorization-server> | none — it is how you get one |
| Metadata API | <https://schemastack.io/api/> | session JWT (human sign-in) |

### MCP server

Streamable HTTP transport, MCP protocol `2025-03-26`. All traffic is `POST` to
the single `/mcp` endpoint; SSE transport is not offered.

```
Authorization: Bearer mcp_…
Content-Type: application/json
Accept: application/json, text/event-stream
```

Either credential opens it: an `mcp_` key, or an OAuth 2.0 access token obtained
through the flow described below. Optionally append `?workspace=<uuid>` to name
the workspace you mean; the credential already determines it, but when the
parameter is present it has to agree, so a credential for one workspace cannot
be pointed at another.

The first call must be `initialize`. The response carries an `Mcp-Session-Id`
header that every subsequent request must echo back — a client that skips the
handshake gets `Mcp-Session-Id header not found`.

Roughly 57 tools are exposed, covering schema inspection, view and column
management, data queries, record writes, validation constraints, relationships,
indexes and bulk jobs. Call `tools/list` for the authoritative set.

**Two gates apply, and the effective permission is the lower of the two:** what
the credential itself allows, and the workspace ceiling set by an administrator.
The ceiling defaults to `DISABLED`, so a valid key on an unconfigured workspace
is refused by design — that is not a malfunction to retry around. Levels are
`DISABLED`, `READ_ONLY`, `DATA_ONLY` (data writes, no schema changes) and
`FULL`. A key may additionally be scoped to named views.

An OAuth access token is capped lower than a key: `workspace:read` grants
`READ_ONLY` and `workspace:write` grants `DATA_ONLY`, never `FULL`. Consenting
to a scope named "write" is consenting to write records, not to alter the shape
of a database. Schema modification — adding or dropping columns, changing types,
re-importing a schema — requires an `mcp_` key, which an administrator issues
deliberately. The workspace ceiling still applies on top. Revoking a grant, or
disabling the application, shuts out the tokens already issued as well as future
ones: a token minted before the revocation is refused from that moment.

Setup guide: <https://docs.schemastack.io/guide/ai-integration>

### Workspace API — API key

```
Authorization: Bearer sk_live_…
```

Each key is bound to one organisation and workspace; using it elsewhere returns
`401`. Permission is `Read & Write` or `Read Only`, and a read-only key
attempting a write returns `403`. Keys may carry an expiry, after which they
return `401`.

Reference: <https://docs.schemastack.io/api/authentication>

### OAuth 2.0 — for MCP and the Workspace API

For an application acting on behalf of a person who signs in, rather than
holding a workspace key. One access token covers both that workspace's MCP
endpoint and its Workspace API.

Discover it rather than hard-coding it:

- <https://schemastack.io/.well-known/oauth-authorization-server> (RFC 8414)
- <https://schemastack.io/.well-known/oauth-protected-resource/mcp> (RFC 9728)

An MCP request with no credential is answered `401` with a `WWW-Authenticate`
header naming that second document, so a client handed only a URL can find its
way from there.

- Authorization endpoint: <https://schemastack.io/admin/oauth2/consent> — a page,
  opened in a browser. (`/api/oauth2/authorize` is the JSON behind it, for that
  page's own use; it is not the endpoint to send a user to.)
- Token endpoint: <https://schemastack.io/api/oauth2/token>
- Revocation endpoint: <https://schemastack.io/api/oauth2/revoke>
- Registration endpoint: <https://schemastack.io/api/oauth2/register>
- Grants: `authorization_code`, `refresh_token`
- PKCE: **required**, `code_challenge_method=S256` only
- Client authentication: `none` — public clients only, there is no secret
- Scopes: `workspace:read`, `workspace:write`

#### Registering

`POST /api/oauth2/register` with an RFC 7591 body. No credential needed. A
`client_name` is required — it is what the person deciding whether to grant
access will read — and each `redirect_uri` must be `https`, or `http` on
loopback. Up to five of them. The response has a `client_id` and no secret.

A client registered this way belongs to no workspace, so a workspace has to be
settled before it can reach anything:

#### Naming the workspace

Either the client names it, or the person consenting picks it.

To name it, pass `resource=https://schemastack.io/mcp?workspace=<uuid>` on the
authorization request (RFC 8707) — the same URL as the MCP endpoint:
<https://schemastack.io/mcp?workspace=<uuid>>. The consent screen shows the
workspace it resolves to, and the person consenting must already have access to
it.

To let them pick it, omit `resource`. The consent screen then lists the
workspaces that person can reach and they choose one; the choice is sent back as
the same `resource` value and checked the same way, so omitting it moves who
decides and changes nothing about what is verified. Prefer this when you do not
already know a workspace UUID — nothing needs to be looked up first.

A client an administrator registered already has a workspace and may omit
`resource` entirely.

### End-user identity via an external provider

A workspace can be configured to accept OIDC tokens from Auth0, Clerk or
Firebase, so end users call the Workspace API with their own identity. Those
tokens are subject to row-level security; API keys and SchemaStack OAuth tokens
are workspace-owner credentials and are not.

Configuring this is an administrator action and is **not reachable with an
`mcp_` key or an OAuth token**, whatever their access level: trusting an issuer
grants standing access to everyone holding its tokens and outlives revoking the
credential that configured it. Use the admin UI or `PUT
/api/workspaces/{uuid}/external-idp` with a signed-in session. An `mcp_` key
with full access may *read* the configuration.

Reference: <https://docs.schemastack.io/api/external-identity-providers>

## Behaviour worth knowing

- **Errors mean what they say.** `401` is a bad or expired credential, `403` is
  a permission ceiling. Neither is fixed by retrying.
- **Schema migrations are temporary.** During a column type change or constraint
  addition, a table can reject reads or writes with a message naming the
  operation and a suggested wait. Retry after the stated interval.
- **Writes are real.** Record creation, bulk jobs and schema changes act on a
  production database belonging to the customer. `preview_column_change` is a
  dry run for schema edits; there is no undo for data writes.
- **Rate limits** apply per key on the Workspace API and return `429`.

## Contact

Security reports and integration questions: <https://schemastack.io/about>
