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

# Authentication

> Sign in with email and password, then call any patient endpoint with a bearer token.

export const AgentPrompt = ({prompt, title = "AI agent prompt"}) => {
  const [copied, setCopied] = React.useState(false);
  const handleCopy = e => {
    e.stopPropagation();
    if (!prompt) return;
    navigator.clipboard.writeText(prompt.trim()).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    });
  };
  const agents = ["Claude", "Claude Code", "Cursor", "Codex", "Windsurf", "Copilot"];
  return <div className="hd-agent-card">
      <div className="hd-agent-titlebar">
        <span className="hd-agent-mark" aria-hidden="true">
          <svg width="15" height="15" viewBox="0 0 100 100" fill="none">
            <rect x="42" y="26" width="16" height="48" rx="5" fill="currentColor" />
            <rect x="26" y="42" width="48" height="16" rx="5" fill="currentColor" />
          </svg>
        </span>
        <span className="hd-agent-filename">{title}</span>
        <button type="button" className={`hd-agent-copy ${copied ? "hd-agent-copy-copied" : ""}`} onClick={handleCopy} title="Copy prompt to clipboard" aria-label={copied ? "Copied" : "Copy prompt to clipboard"}>
          {copied ? <>
              <svg width="13" height="13" viewBox="0 0 16 16" fill="none">
                <path d="M3 8.5l3.5 3.5L13 4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
              <span>Copied</span>
            </> : <>
              <svg width="13" height="13" viewBox="0 0 16 16" fill="none">
                <rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" strokeWidth="1.5" />
                <path d="M11 5V3.5A1.5 1.5 0 0 0 9.5 2h-6A1.5 1.5 0 0 0 2 3.5v6A1.5 1.5 0 0 0 3.5 11H5" stroke="currentColor" strokeWidth="1.5" />
              </svg>
              <span>Copy</span>
            </>}
        </button>
      </div>

      <pre className="hd-agent-body"><code>{prompt.trim()}</code></pre>

      <div className="hd-agent-footer">
        <span className="hd-agent-footer-label">Paste into</span>
        <div className="hd-agent-chips">
          {agents.map(name => <span key={name} className="hd-agent-chip">{name}</span>)}
        </div>
      </div>
    </div>;
};

The Hi-Doctor API uses JSON Web Tokens. You exchange an email and password for
an **access token** and a **refresh token**, then send the access token on
every subsequent request.

The card below is the whole flow written for an AI agent — copy it into a
system prompt, or read the annotated version underneath it.

<AgentPrompt
  title="Authentication prompt"
  prompt={`Goal — authenticate against the Hi-Doctor API and call a patient endpoint.

Base URL:
https://api.hi-doctor.ai

Step 1 — sign in (no auth required):
POST /v1/users/token/
Headers:
Content-Type: application/json
X-Brand-Slug: hi-doctor        <- REQUIRED on sign-in. Omitting it fails the request.
Body:
{ "email": "<email>", "password": "<password>" }

curl:
curl -X POST https://api.hi-doctor.ai/v1/users/token/ \\
-H "Content-Type: application/json" \\
-H "X-Brand-Slug: hi-doctor" \\
-d '{"email":"patient@example.com","password":"<password>"}'

Response 200:
{ "access": "<JWT>", "refresh": "<JWT>" }

Step 2 — call any patient endpoint:
Authorization: Bearer <access>
X-Brand-Slug is NOT needed here, only on sign-in.

curl https://api.hi-doctor.ai/v1/users/profile/me/ \\
-H "Authorization: Bearer <access>"

Step 3 — refresh when the access token expires:
POST /v1/users/token/refresh/  with { "refresh": "<refresh>" }

Scoping:
Every endpoint is scoped to the authenticated account. There is no patient id
to pass. Supplying another account's record id returns 404, not 403 — do not
treat a 404 as "retry with a different id".

Failure modes:
- 401 -> wrong email or password, or a missing/expired access token.
- 403 code "user_inactive" -> the account is disabled.
- 400 code "google_account" -> created via Google sign-in and has no password.
This account CANNOT use password login. Do not retry; tell the user.
- 429 -> rate limited. Back off; do not loop.
- 400 error_name "PROFILE_INCOMPLETE" with missing_fields[] -> PATCH
/v1/users/profile/me/ with those fields, then retry.

Never store the password. Exchange it once and keep the tokens.

If you are an AI assistant acting for a patient, prefer the MCP connector at
https://mcp.hi-doctor.ai/mcp instead — it uses OAuth, so you never handle the
patient's password. See https://docs.hi-doctor.ai/mcp/overview`}
/>

## 1. Sign in

```bash theme={null}
curl -X POST https://api.hi-doctor.ai/v1/users/token/ \
  -H 'Content-Type: application/json' \
  -H 'X-Brand-Slug: hi-doctor' \
  -d '{
    "email": "patient@example.com",
    "password": "your-password"
  }'
```

```json theme={null}
{
  "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "is_doctor": false,
  "is_backoffice_admin": false,
  "is_marketer": false
}
```

The three role flags are always present. For a patient account they are always
`false` — they exist so the web app can route staff sign-ins, and they are not
a permission grant. Nothing in this reference becomes available by reading
them.

<Warning>
  The `X-Brand-Slug: hi-doctor` header is required on sign-in. Without it the
  request is rejected.
</Warning>

### Sign-in failures

| Response                            | Meaning                                                         |
| ----------------------------------- | --------------------------------------------------------------- |
| `401` `code: "invalid_credentials"` | Wrong email or password, **or the account is not yet verified** |
| `403` `code: "user_inactive"`       | The account is disabled                                         |
| `400` `code: "google_account"`      | Created with Google sign-in, so it has no password              |

Repeated failures are rate limited.

<Warning>
  A `401` on sign-in is deliberately non-specific — it does not tell you whether
  the address exists. Do not present it to a user as "no such account".

  Sending the request **without** `X-Brand-Slug` also returns the same `401`, so
  a missing header looks exactly like a wrong password. If sign-in fails
  unexpectedly, check the header before assuming the credentials are wrong.
</Warning>

## 2. Call an endpoint

Send the access token as a bearer token:

```bash theme={null}
curl https://api.hi-doctor.ai/v1/users/profile/me/ \
  -H 'Authorization: Bearer <access token>'
```

Every endpoint in this reference is scoped to the authenticated account. You
can only ever read or write your own data — there is no patient or user
identifier to pass, and supplying another account's record id returns `404`.

## 3. Refresh the access token

Access tokens are short-lived. When one expires, exchange the refresh token:

```bash theme={null}
curl -X POST https://api.hi-doctor.ai/v1/users/token/refresh/ \
  -H 'Content-Type: application/json' \
  -d '{ "refresh": "<refresh token>" }'
```

## Errors

Errors are JSON, but **there are five different envelopes** depending on which
layer refused the request. The message key is `error` on the auth endpoints and
`detail` elsewhere — and neither is present on the most common failure of all,
field validation, which returns an object keyed by field name:

```json Field validation — no detail, no code theme={null}
{ "email": ["This field is required."], "password": ["This field is required."] }
```

```json Sign-in and registration theme={null}
{ "error": "Invalid credentials", "code": "invalid_credentials" }
```

```json Token failures theme={null}
{ "detail": "Given token not valid for any token type", "code": "token_not_valid" }
```

Branch on `code` or `error_name` where present — those are stable. The prose in
`error` and `detail` is written for humans and may change.

<Note>
  [Errors](/api-reference/errors) has all five envelopes, every status code, and
  the correct client response to each. Read it before writing your error
  handling — reading only `detail` loses the message on most validation
  failures.
</Note>

## Prefer the MCP connector

If you are building an AI assistant rather than a direct integration, use the
[MCP connector](/mcp/overview) instead. It handles OAuth, scoping and
permissions for you, and never exposes the patient's password to your
application.
