message.comDevelopers

Auth API Live

The user-facing auth endpoints: sign up a workspace, log in, request a password-reset email, complete a reset, and log out. Rate limits and authentication requirements vary by endpoint; see Errors, rate limits, versioning.

For agent invitations from inside a workspace, see the Agents API. For choosing the right credential type, see the Authentication API.

Register

POST/api/v1/auth/registerAuth: Bearer

Creates a new workspace and admin agent in one step. Returns a fresh workspace JWT bound to the new agent. The onboarding wizard at app.message.com/register calls this endpoint.

Request body

FieldTypeDescription
emailrequiredstringAgent email. Becomes the admin contact for the workspace.
passwordrequiredstringAt least 12 characters with uppercase and lowercase letters, a number and a symbol; at most 72 UTF-8 bytes. Common passwords are rejected.
workspaceNamerequiredstringDisplay name for the workspace. Shown in the dashboard top bar.
namerequiredstringAdministrator display name, 1–200 characters.
slugrequiredstringUnique workspace address: 3–63 lowercase letters, numbers or dashes. Reserved addresses are rejected.
websiteoptionalstringOptional public business website. Do not send siteUrl; that is not a recognized field.
timezoneoptionalstringOptional IANA timezone.
Body
{
  "email": "[email protected]",
  "password": "Example-Only!47Replace",
  "name": "Alex Rivera",
  "workspaceName": "Acme Support",
  "slug": "acme-support",
  "website": "https://acme.com"
}
201 Created
{
  "token": "eyJhbGci...",
  "onboardingRunId": null,
  "website": "https://acme.com",
  "slug": "acme-support",
  "products": [],
  "setupPath": "/setup/start",
  "agent": {
    "id": "uuid",
    "email": "[email protected]",
    "name": "Alex Rivera",
    "role": "admin",
    "workspaceId": "uuid",
    "status": "offline"
  }
}

Login

POST/api/v1/auth/loginAuth: Bearer

Exchanges email and password for { token, agent } when no additional verification is required. An account with extra security instead returns { mfaRequired: true, challenge, methods }; do not assume every successful HTTP response contains a token. Use Message.com’s sign-in screen to complete the security challenge. An optional workspaceSlug selects a workspace.

Body
{
  "email": "[email protected]",
  "password": "your-password"
}

Code samples

cURL
curl -X POST 'https://app.message.com/api/v1/auth/login' \
  -H 'Content-Type: application/json' \
  -d '{"email":"[email protected]","password":"your-password"}'
JavaScript
const res = await fetch('https://app.message.com/api/v1/auth/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email, password })
});
const { token, agent } = await res.json();
Python
import requests
r = requests.post(
    "https://app.message.com/api/v1/auth/login",
    json={"email": email, "password": password},
)
token = r.json()["token"]
Ruby
require "net/http"
require "json"
uri = URI("https://app.message.com/api/v1/auth/login")
res = Net::HTTP.post(uri, { email: email, password: password }.to_json, "Content-Type" => "application/json")
token = JSON.parse(res.body)["token"]
PHP
<?php
$ctx = stream_context_create([
  "http" => [
    "method" => "POST",
    "header" => "Content-Type: application/json",
    "content" => json_encode(["email" => $email, "password" => $password]),
  ],
]);
$body = file_get_contents("https://app.message.com/api/v1/auth/login", false, $ctx);
$token = json_decode($body, true)["token"];

Forgot password

POST/api/v1/auth/forgot-passwordAuth: Bearer

Sends a password-reset email to the address (if it matches a real agent). Returns HTTP 200 with { ok: true } regardless of whether the email exists, so we do not leak account enumeration.

Body
{ "email": "[email protected]" }

The email contains a one-time token that expires in 30 minutes. Each new request invalidates the previous token, so the most recent email is always the valid one.

Reset password

POST/api/v1/auth/reset-passwordAuth: Bearer

Completes a password reset using the token from the email plus the new password. Returns HTTP 200 with { ok: true }. Existing sessions are revoked; sign in again. It does not return a new token.

Body
{
  "token": "the-token-from-the-email",
  "newPassword": "Different-Example!83Replace"
}

Logout

POST/api/v1/auth/logoutAuth: Bearer

Revokes the bearer token by bumping the agent's tokenVersion. Returns HTTP 200 with { ok: true }. Logout is global: every device signed in as this agent will be force-logged on next request.

Errors

CodeWhen
400 invalid_bodyPayload failed validation (password too weak, email malformed).
401 invalid_credentialsEmail + password mismatch. Generic on purpose.
409 email_takenRegister: that email is already an agent on another workspace.
400 invalid_or_expired_tokenReset: the token has expired or was superseded.
429 rate_limitedPer-IP or per-email cap hit. See rate-limit table.

Per-route caps

EndpointCap
POST /auth/register5/min per IP
POST /auth/login20/min per IP, plus email and email/IP failed-attempt lockouts
POST /auth/forgot-password3/min per IP
POST /auth/reset-password5/min per IP

Common pitfalls

  • Treating 401 from /login as "wrong password". It might be "email does not exist". We deliberately return one generic error for both.
  • Storing the password in plaintext anywhere. We bcrypt server-side. Your client should never write the password to disk or to a cookie.
  • Showing different UI for "email taken" on register. Pair this with the 409 response and you teach attackers which emails are valid. Use a neutral message on both branches.