> ## 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.

# Sign up a new patient

> Create a Hi-Doctor account programmatically, verify it, and sign in — everything a chat interface needs.

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>;
};

A chat interface can take someone from "no account" to "signed in" without ever
leaving the conversation. It takes three calls, with one step that requires the
person to read a code from their inbox.

<Warning>
  Hi-Doctor is a **medical** service, not an emergency one. If someone using your
  interface reports severe or sudden symptoms — chest pain, difficulty breathing,
  a severe allergic reaction, suicidal thoughts — direct them to their local
  emergency number rather than continuing a signup or consultation.

  See [Safety](/essentials/safety) for the rules that apply to anything built on
  this API.
</Warning>

<AgentPrompt
  title="Signup prompt"
  prompt={`Goal — take a person from "no Hi-Doctor account" to "signed in", inside a chat.

Base URL: https://api.hi-doctor.ai
Every call below needs: Content-Type: application/json and X-Brand-Slug: hi-doctor

Step 1 — register:
POST /v1/users/register/
Body: { "email", "password", "first_name"?, "last_name"?, "locale"? }
- Only email and password are required.
- locale is one of en, es, de, nl, it, fr, pt. Defaults to en. It sets the
language of the account's emails.
Response 200: { "message": "Please verify your email with OTP" }
Note this is 200, not 201 — no account exists yet. The registration is held
pending until the emailed code is verified.

Register failure modes:
- 400 -> the password failed the strength rules, or the address belongs to an
ACTIVE account. Registering over an active, privileged, or Google-backed
account is refused by design (accepting an OTP would be an account-takeover
path). Only an inactive, unverified password signup can be resumed — that
case reissues a code instead of creating a duplicate.
- 429 error_name "REGISTRATION_RATE_LIMITED" -> back off. There is also an OTP
email cooldown: a throttled retry deliberately does NOT invalidate the code
already sitting in the person's inbox, so tell them to check for the
earlier email rather than promising a new one.
- 406 error_name "REGISTRATION_EMAIL_UNDELIVERABLE" -> the address is
permanently undeliverable (hard bounce or spam complaint). This is per
address and permanent. Retrying can NEVER succeed — ask for a different
email address.

Step 2 — verify the emailed code:
A one-time code is emailed. The account CANNOT sign in until it is verified —
attempting to do so returns 401. There is no way to skip this and no test
bypass: you must ask the person for the code and pass it through.

POST /v1/users/verify-email/
Body: { "email", "otp" }
- Both fields required; omitting either returns
error_name "VERIFY_EMAIL_INVALID_REQUEST".
- Wrong codes are rate limited. After too many attempts the API says
"Too many incorrect codes. Please request a new one shortly."
DO NOT retry in a loop — surface the message and wait.
- There is no resend endpoint. If the code is lost or expires, call
/v1/users/register/ again with the same address to resume and reissue —
subject to the cooldown above.

Step 3 — sign in:
POST /v1/users/token/  with { "email", "password" }
Response 200: { "access", "refresh" }

Step 4 — complete the profile before checkout:
Checkout refuses with:
{ "error_name": "PROFILE_INCOMPLETE", "missing_fields": [...] }
Fix with:
PATCH /v1/users/profile/me/  (Authorization: Bearer <access>)
{ "date_of_birth": "YYYY-MM-DD", "country_of_residence": "<ISO-3166 alpha-3>" }
The patient must be 18+ and resident in a supported country.

Google accounts:
POST /v1/users/google-login/ signs in or signs up with a Google credential.
An account created this way has NO password: it cannot use /v1/users/token/
and cannot complete MCP connector sign-in, which is password-based.

Never store the password — exchange it once and keep the tokens.

Once the account exists, an AI assistant should switch to the MCP connector at
https://mcp.hi-doctor.ai/mcp so it never handles the password again. Signup
itself must go through this API, because the connector authorises an account
that already exists.`}
/>

The three calls, annotated:

<Steps>
  <Step title="Register">
    ```bash theme={null}
    curl -X POST https://api.hi-doctor.ai/v1/users/register/ \
      -H 'Content-Type: application/json' \
      -H 'X-Brand-Slug: hi-doctor' \
      -d '{
        "email": "patient@example.com",
        "password": "a-strong-password",
        "first_name": "Alex",
        "last_name": "Moreno",
        "locale": "en"
      }'
    ```

    ```json theme={null}
    { "message": "Please verify your email with OTP" }
    ```

    Only `email` and `password` are required. `locale` accepts any live
    language (`en`, `es`, `de`, `nl`, `it`, `fr`, `pt`) and sets the language of
    the account's emails; it defaults to `en`.

    The password is checked against Hi-Doctor's strength rules and is rejected
    with `400` if it is too weak.

    <Note>
      This returns **`200`, not `201`** — no account exists yet. The registration
      is held pending until the emailed code is verified.
    </Note>

    | Response                                 | Meaning                                                        |
    | ---------------------------------------- | -------------------------------------------------------------- |
    | `400`                                    | Weak password, or the address belongs to an **active** account |
    | `429` `REGISTRATION_RATE_LIMITED`        | Too many attempts — back off                                   |
    | `406` `REGISTRATION_EMAIL_UNDELIVERABLE` | Address permanently undeliverable                              |

    Registering over an **active**, privileged, or Google-backed account is
    refused by design: accepting an OTP for it would be an account-takeover
    path. Only an inactive, unverified password signup can be resumed, and that
    reissues a code rather than creating a duplicate.

    <Warning>
      A `406` is permanent for that address — a hard bounce or spam complaint.
      Retrying can never succeed. Ask for a different email address rather than
      looping.
    </Warning>
  </Step>

  <Step title="Verify the emailed code">
    A one-time code is emailed to the address. **The account cannot sign in
    until it is verified** — attempting to do so returns `401`.

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

    Ask the person for the code and pass it straight through. Both fields are
    required; omitting either returns `error_name: "VERIFY_EMAIL_INVALID_REQUEST"`.

    <Warning>
      Incorrect codes are rate limited. After too many attempts the API responds
      *"Too many incorrect codes. Please request a new one shortly."* and further
      attempts are refused for a period. Do not retry in a loop — surface the
      message and wait.
    </Warning>
  </Step>

  <Step title="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": "a-strong-password" }'
    ```

    Returns an `access` and `refresh` token pair. From here, follow
    [Authentication](/api-reference/authentication).
  </Step>
</Steps>

## Complete the profile

Checkout requires a complete profile. If anything is missing it refuses with:

```json theme={null}
{ "error_name": "PROFILE_INCOMPLETE", "missing_fields": ["date_of_birth", "country_of_residence"] }
```

Fill the gaps and retry:

```bash theme={null}
curl -X PATCH https://api.hi-doctor.ai/v1/users/profile/me/ \
  -H 'Authorization: Bearer <access token>' \
  -H 'Content-Type: application/json' \
  -d '{ "date_of_birth": "1990-04-12", "country_of_residence": "ESP" }'
```

The patient must be **18 or over** and resident in a supported
[country](/essentials/regions).

## Google accounts

`POST /v1/users/google-login/` signs in or signs up with a Google credential.

<Warning>
  An account created through Google has **no password**. It cannot sign in via
  `/v1/users/token/`, and it cannot currently complete
  [MCP connector](/mcp/overview) sign-in, which is password-based.
</Warning>

## Forgotten passwords

`POST /v1/users/forgot-password/` emails a reset link; `POST /v1/users/reset-password/`
sets the new password from the token in that email. Both are unauthenticated.

## What a chat interface should know

* **You cannot skip verification.** There is no way to activate an account
  without the emailed code.
* **There is no resend-code endpoint.** If a code is lost or expires, the
  person registers again with the same address, which resumes the pending
  signup and reissues a code — subject to an email cooldown. A throttled retry
  deliberately does *not* invalidate the code already in their inbox, so tell
  them to check for the earlier email rather than promising a new one.
* **Some failures are permanent.** A `406`
  (`REGISTRATION_EMAIL_UNDELIVERABLE`) means that address can never receive
  mail from Hi-Doctor. Ask for a different one instead of retrying.
* **Never store the password.** Exchange it for tokens once and keep the
  tokens; refresh with `/v1/users/token/refresh/`.
* For an AI assistant, prefer the [MCP connector](/mcp/overview). It now
  handles **signup too** — a patient with no account can create one, verify the
  emailed code and approve without ever leaving the connector, and your
  application never handles their password. Use this API directly only when you
  are not going through MCP.
