Open Morse

Reference

Auth

13 routes, generated from Morse’s own OpenAPI document. Every path below hangs off the base URL, and every one needs the bearer header.

The examples are built from each route’s schema, so the shapes and types are exactly what the API declares. The values are illustrative, and no one has run them.

Finish signing up, setting a password, or resetting one: send the 6-digit code from the email, and a new password where the flow asked for one. Signs the browser in on success.

POST/auth/confirm

Signing in here is deliberate. They have just proved they hold the mailbox and, for two of the three purposes, chosen the password — a login form immediately afterwards would add a step and no security. **What the code is for is decided here, not sent by the caller.** The purpose is read from the live row (auth_codes.purpose_of), so a reset_password code cannot be presented as a verify_email one. The HMAC is bound to the purpose as well, so even a caller that could choose would get nowhere. **And it is not told to the caller either, except by answering.** Since register gives the same 202 to a new address and an existing one, the confirm page cannot know whether to ask for a password — and a redirect carrying the purpose would rebuild exactly the oracle the 202 closed. So the page sends the code alone; a verify_email code finishes, and anything else comes back password_required with the code still live, to be sent again with the password. Only someone already holding a valid code learns which flow they are in, and the email told them that already.

POST/auth/confirm

curl -X POST "$MORSE/auth/confirm" \
  -H "Authorization: Bearer $MORSE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "code": "4KJ9P2", "email": "priya@neuralarc.ai" }'
200
{
  "email": "priya@neuralarc.ai",
  "id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60",
  "name": "Priya Shah"
}

Signing in connects the calendar too (plan 009 D14): one consent screen, once. Only where calendars are configured — asked of a server with no key to keep the answer in, it would be consent for nothing.

GET/auth/google

Offline access, and no prompt. prompt=consent would show the screen on every sign-in; without it Google shows it only while something is still ungranted, and returns a refresh token then. Someone already connected signs in without seeing it. include_granted_scopes keeps a grant made through /api/calendar/connect in the token rather than replacing it.

GET/auth/google

curl "$MORSE/auth/google" \
  -H "Authorization: Bearer $MORSE_TOKEN"

Where Google sends a browser back after signing in: finds or makes the account, signs the browser in and returns to the app.

GET/auth/google/callback

Not for scripts.

GET/auth/google/callback

curl "$MORSE/auth/google/callback" \
  -H "Authorization: Bearer $MORSE_TOKEN"

Google Sign-In for the mobile app.

POST/auth/google/mobile

The browser flow above cannot work in-app: /google is a 302 to accounts.google.com, and Google blocks OAuth inside embedded WebViews. The device SDK hands the app a signed ID token instead, so this route verifies that token and ends exactly where google_callback ends — same checks, same cookie — leaving nothing downstream able to tell the two apart. The audience is our **Web** OAuth client ID, which is also what the client must pass as serverClientId. A platform client ID on either side fails every token with an audience mismatch.

POST/auth/google/mobile

curl -X POST "$MORSE/auth/google/mobile" \
  -H "Authorization: Bearer $MORSE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "id_token": "mp_YOUR_MORSE_TOKEN" }'
200
{
  "email": "priya@neuralarc.ai",
  "id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60",
  "name": "Priya Shah"
}

Sign a browser in with an email and password.

POST/auth/login

403 with reason: "unverified" for an account that has never confirmed its address, so the page can offer Resend instead of repeating "incorrect password" at someone whose password is correct. That does reveal that an unverified account exists — accepted, and only after the right password was supplied, because the alternative is a dead end a real user cannot escape.

POST/auth/login

curl -X POST "$MORSE/auth/login" \
  -H "Authorization: Bearer $MORSE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "email": "priya@neuralarc.ai", "password": "…" }'
200
{
  "email": "priya@neuralarc.ai",
  "id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60",
  "name": "Priya Shah"
}

Sign a browser out by clearing its session cookie.

POST/auth/logout

Tokens are unaffected: revoke those in Settings.

POST/auth/logout

curl -X POST "$MORSE/auth/logout" \
  -H "Authorization: Bearer $MORSE_TOKEN"

Who's signed in: your profile and preferences.

GET/auth/me

With a token, the token's owner.

GET/auth/me

curl "$MORSE/auth/me" \
  -H "Authorization: Bearer $MORSE_TOKEN"
200
{
  "accent": "…",
  "avatar_choice": "…",
  "avatar_original": true,
  "avatar_uploads": true,
  "avatar_url": "https://onmorse.com/priya",
  "avatar_version": "…",
  "background": "…",
  "bio": "…",
  "bookable": true,
  "booking_handle": "…",
  "clock": "…",
  "company": "…",
  "email": "priya@neuralarc.ai",
  "extra_language": "en",
  "extra_language_default": "en",
  "id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60",
  "is_admin": true,
  "name": "Priya Shah",
  "onboarded_at": "2026-10-02T10:30:00+05:30",
  "organisation_id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60",
  "share_tasks": true,
  "theme": "…",
  "timezone": "Asia/Kolkata",
  "title": "Pricing review"
}

Lets the sign-in pages render only the methods this deployment accepts, rather than offering a form that would 404.

GET/auth/methods

GET/auth/methods

curl "$MORSE/auth/methods" \
  -H "Authorization: Bearer $MORSE_TOKEN"
200
{
  "google": true,
  "password": true
}

Start a password reset: we email a 6-digit code. Finish it at POST /auth/confirm with that code and the new password — there is no second endpoint, because a reset and a first password are the same act.

POST/auth/password/forgot

Always 202. An account that signs in with Google and has no password gets an email saying so rather than a code: the alternative is a flow that promises a code which cannot exist, and teaches the person nothing.

POST/auth/password/forgot

curl -X POST "$MORSE/auth/password/forgot" \
  -H "Authorization: Bearer $MORSE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "email": "priya@neuralarc.ai" }'
202
{
  "detail": "…"
}

A just-issued ID token for *this* account's Google identity.

POST/auth/reauth/google

Any valid token is not enough: a token for another account proves nothing about this session, and an old one proves only that the app once held it.

POST/auth/reauth/google

curl -X POST "$MORSE/auth/reauth/google" \
  -H "Authorization: Bearer $MORSE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "id_token": "mp_YOUR_MORSE_TOKEN" }'
200
{
  "expires_at": "2026-10-02T10:30:00+05:30",
  "token": "mp_YOUR_MORSE_TOKEN"
}

Re-enter your password for a five-minute proof that it's you, sent as X-Morse-Recent-Auth to delete a voice note permanently.

POST/auth/reauth/password

Only where password sign-in is on; an API token needs no proof.

POST/auth/reauth/password

curl -X POST "$MORSE/auth/reauth/password" \
  -H "Authorization: Bearer $MORSE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "password": "…" }'
200
{
  "expires_at": "2026-10-02T10:30:00+05:30",
  "token": "mp_YOUR_MORSE_TOKEN"
}

Start an account with an email and a password. We email a 6-digit code; nothing is signed in until POST /auth/confirm carries that code back.

POST/auth/register

202 and not 201, and no session: at this point all we know is that someone typed an address. 202 and not 409 for an address that already has an account, either — the 409 this route used to return was an account-existence oracle, which the comment above BAD_CREDENTIALS has always said login must not be. Registration had been left out of that rule (plan 036 §5.1).

POST/auth/register

curl -X POST "$MORSE/auth/register" \
  -H "Authorization: Bearer $MORSE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "email": "priya@neuralarc.ai", "name": "Priya Shah", "password": "…" }'
202
{
  "detail": "…"
}

Send another 6-digit code, for whichever flow the person is part-way through. The previous code stops working.

POST/auth/resend

Always 202, with the same body as register — including for an address with no account, an unknown purpose, and a purpose with nothing to resend. Each of those answering differently would be the oracle again, in a route that needs no authentication at all. **purpose in the body is a hint and nothing more.** The client cannot have been told which flow it is in — that is the point of register's identical 202 — so it is guessing, and a guess that issued the wrong kind of code would be a dead end: the person would hold a verify_email code for an account that needs a password. What they already have decides, and failing that, what the account looks like.

POST/auth/resend

curl -X POST "$MORSE/auth/resend" \
  -H "Authorization: Bearer $MORSE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "email": "priya@neuralarc.ai" }'
202
{
  "detail": "…"
}