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

# Authentication

> User tokens for prescribers and machine tokens for your backend, and which one can sign.

Every request to Photon carries an OAuth token in the `Authorization` header. A token belongs to one organization. There are two kinds:

| | **User token** | **Machine token** |
| - | - | - |
| Who it acts for | A prescriber who signed in | Your backend |
| Credential | **Application**: a public `client_id` | **Backend**: a `client_id` and a `client_secret` |
| Obtained by | Signing in at `auth.neutron.health` in a browser | The client credentials grant, server to server |
| [Permissions](#permissions) | Whatever the prescriber's role allows | Everything but signing |
| Can sign prescriptions | Yes | **No** |

<Warning>
  **Machine tokens can't sign prescriptions.** Signing needs the `write:prescription` permission, which machine tokens can never be given. Your backend can sync patients, draft prescriptions and draft orders, but a signed-in prescriber always signs. That step happens in a browser, with a user token.
</Warning>

That's why Photon ships [Prescribe](/docs/prescribe/overview). It signs the prescriber in, holds their token, and runs the signing and sending, so your frontend never handles a token or calls GraphQL itself. We don't recommend calling the API directly from a frontend. When you need lower-level control in the browser, use the [JavaScript client](/docs/prescribe/embed/javascript): it handles sign-in, tokens and the workflow rules for you.

## Permissions

| Permission | Lets a token | User token | Machine token |
| - | - | - | - |
| `read:patient` | Read patients | ✓ | ✓ |
| `write:patient` | Add, edit and update patients | ✓ | ✓ |
| `read:prescription` | Read, look up and **draft** prescriptions | ✓ | ✓ |
| `write:prescription` | **Sign** prescriptions | Prescribers only | Never |
| `read:order` | Read and **draft** orders | ✓ | ✓ |
| `write:order` | **Send** orders | ✓ | ✓ |

`read:` covers drafting. `write:` covers the step that commits: changing a patient, signing a prescription, sending an order.

A user token carries the permissions of the person's role. Prescribers have all of them. Other roles, such as medical operations, have everything except `write:prescription`.

A machine token can send an order once a prescriber has signed every prescription on it.

## Credentials

Find both kinds in your dashboard under [Settings → Developers](https://app.neutron.health/settings/developers).

* **Application** credentials are a public `client_id` and a list of allowed URLs. The list must include every domain your app runs on. Use them with Prescribe's React components or the JavaScript client.
* **Backend** credentials are a `client_id` and a `client_secret`. Keep the secret on your server, out of client-side code and version control.

Credentials come with every Neutron sandbox account. In production they're a paid feature. If you can't see them on a paid account, ask your organization's admin.

<Tip>
  The [iframe](/docs/prescribe/embed/iframe) needs no credentials. It signs the prescriber in to Photon on its own.
</Tip>

## User tokens

With Prescribe, you don't handle user tokens:

| You use | Sign-in |
| - | - |
| [iframe](/docs/prescribe/embed/iframe) or [deep link](/docs/prescribe/app/deep-links) | Photon's own. No credentials needed. |
| [React components](/docs/prescribe/embed/react) | `<PhotonProvider clientId="…">` signs the prescriber in with your Application credential. |
| [JavaScript client](/docs/prescribe/embed/javascript) | `photon({ auth: { clientId } })` does the same without React. |
| Your own Auth0 sign-in | Pass `photon({ auth: { getToken } })`, and Photon calls your function before each request. |

Behind each of these, the prescriber signs in at `auth.neutron.health` with Auth0's authorization code flow (with PKCE), for the audience `https://api.neutron.health`, and picks their organization. The token they get can do whatever their role allows, including signing.

## Machine tokens

Use a machine token from your backend to sync patients, draft prescriptions and draft orders. Exchange your Backend credentials for one:

<CodeGroup>
  ```bash cURL theme={"dark"}
  curl https://auth.neutron.health/oauth/token \
    --request POST \
    --header "Content-Type: application/json" \
    --data '{
      "client_id": "YOUR_CLIENT_ID",
      "client_secret": "YOUR_CLIENT_SECRET",
      "audience": "https://api.neutron.health",
      "grant_type": "client_credentials"
    }'
  ```

  ```ts Node.js theme={"dark"}
  const response = await fetch("https://auth.neutron.health/oauth/token", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      client_id: process.env.PHOTON_CLIENT_ID,
      client_secret: process.env.PHOTON_CLIENT_SECRET,
      audience: "https://api.neutron.health",
      grant_type: "client_credentials",
    }),
  });
  const { access_token, expires_in } = await response.json();
  ```
</CodeGroup>

```json Response theme={"dark"}
{
  "access_token": "YOUR_ACCESS_TOKEN",
  "scope": "read:patient write:patient read:prescription read:order write:order",
  "expires_in": 86400,
  "token_type": "Bearer"
}
```

A machine token lasts 24 hours (`expires_in: 86400`). Cache it and request a new one shortly before it expires. Don't request one for every call.

## Calling the API

Send either kind of token as a bearer token to the GraphQL endpoint:

```bash theme={"dark"}
curl https://network.neutron.health/graphql \
  --request POST \
  --header "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{ "query": "{ ping }" }'
```

`ping` is a health check that touches no records. Use it to test a token.

## When a token is refused

A missing, invalid or expired token fails the whole request. The response has a top-level `errors` array and no `data`:

```json theme={"dark"}
{ "errors": [{ "message": "An authentication token is not present", "extensions": { "code": "EMPTY_AUTHORIZATION_HEADER" } }] }
```

A valid token that isn't allowed to do something gets an `UnauthorizedError` in the mutation's result. For example, a machine token that tries to sign gets one:

```json theme={"dark"}
{
  "__typename": "UnauthorizedError",
  "code": "PRESCRIPTION_SIGNING_UNAUTHORIZED",
  "message": "You are not authorized to sign prescriptions."
}
```

When Photon knows which permission is missing, it names it in `requiredPermission`. Retrying with the same token gets the same error. See [Errors](/docs/network/errors).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.