# Authentication (/docs/authentication)



The Factory OS API uses [Supabase Auth](https://supabase.com/docs/guides/auth) JWTs. Every request (except `/health` and public auth endpoints) must include a valid bearer token.

## Token format [#token-format]

```
Authorization: Bearer <supabase_access_token>
```

Tokens are scoped to the user who issued them. The API automatically enforces organization and factory access based on the user's role assignments.

User access tokens must have this project's Supabase issuer, the `authenticated`
audience, an expiration, and a UUID user subject. Anonymous-user sessions, anonymous
API keys, and service-role tokens are not accepted as signed-in user credentials.
The API verifies that the Auth account still exists and is not currently banned,
then resolves its current application role from `user_profiles` on each request.
An old token cannot preserve a platform role after the profile is downgraded.

A signed-in invitee may authenticate before a profile exists, but receives no
application role until onboarding grants one.

## Getting a token [#getting-a-token]

<Tabs items="[&#x22;From the dashboard&#x22;, &#x22;Programmatic sign-in&#x22;, &#x22;Service tokens (coming soon)&#x22;]">
  <Tab value="From the dashboard">
    Sign in to the dashboard at [app.xentr.ai](https://app.xentr.ai). Your session token lives in local storage under `sb-*-auth-token`. This is fine for quick testing.
  </Tab>

  <Tab value="Programmatic sign-in">
    Use the Supabase JS client to sign in with email/password, OTP, or OAuth:

    ```ts
    import { createClient } from '@supabase/supabase-js';

    const supabase = createClient(
      process.env.SUPABASE_URL!,
      process.env.SUPABASE_ANON_KEY!,
    );

    const { data } = await supabase.auth.signInWithPassword({
      email: 'you@company.com',
      password: '...',
    });

    const token = data.session?.access_token;
    ```
  </Tab>

  <Tab value="Service tokens (coming soon)">
    Long-lived service tokens for CI/CD and server-to-server integrations are on the roadmap.
  </Tab>
</Tabs>

## Roles [#roles]

Xentr has seven canonical roles that gate API access:

| Role                    | Scope                                         |
| ----------------------- | --------------------------------------------- |
| `xentr_platform_owner`  | Full platform access                          |
| `xentr_account_manager` | Customer provisioning                         |
| `organization_admin`    | Everything within an org                      |
| `production_engineer`   | Machines, routings, telemetry                 |
| `production_technician` | Floor operations — run machines, log downtime |
| `production_planner`    | Work orders, scheduling                       |
| `production_qa`         | Quality checks, defects                       |

Each API endpoint documents the roles required in its reference page.

Organization member-role updates accept only the five customer roles:
`organization_admin`, `production_engineer`, `production_technician`,
`production_planner`, and `production_qa`. The caller must be an organization
administrator within that organization, an assigned account manager, or a platform
owner. This endpoint cannot grant platform roles or modify an existing platform
staff member's role.

Profile roles and organization memberships are managed through the API, not by
direct anonymous or authenticated database writes. Migration
`0085_protect_authority_tables.sql` enforces this boundary while retaining
authenticated self-profile reads and the Supabase access-token hook. Apply it to
both production and staging when releasing these authorization changes.

The dashboard's Andon diagnostics proxy is restricted to a current
`xentr_platform_owner` session and same-origin requests. Its vendor credentials
cover the whole fleet, so account-manager assignments cannot safely scope access
to that proxy.

## Errors [#errors]

| Status             | Meaning                                                                            |
| ------------------ | ---------------------------------------------------------------------------------- |
| `401 Unauthorized` | Missing, invalid, or expired token. Re-authenticate.                               |
| `403 Forbidden`    | Token is valid but the user lacks the role required for this endpoint or resource. |

## CORS [#cors]

Browser requests from these origins are allowed:

* `https://app.xentr.ai`
* `https://xentr.ai`
* `https://www.xentr.ai`
* `http://localhost:3000` (local development)

For requests from other origins, contact support.
