Authentication
This page is for developers writing their own programs against almyty’s HTTP API (the web addresses programs call to do what the app does, such as listing agents or starting a run). Every call has to prove who is making it, and this page explains how: sign in for a short-lived token, or use an API key (a long secret string that works like a password for programs). For example, a script that runs your nightly report agent sends an API key with each call. If you only use the app, you can skip this section; you sign in as usual.
The almyty API uses JWT (JSON Web Tokens) for authentication. The web UI
receives its token as an httpOnly SameSite=lax cookie so JavaScript
running in the page can’t read it, cross-site scripting and compromised
npm packages can’t steal the session. Programmatic clients and CLIs use
a Bearer Authorization header with an API key, minted by the CLI login
flow or with POST /auth/api-keys.
Every token carries iss: "almyty" + aud: "almyty-api" claims and is
signed with HS256. Both claims are enforced on verification, tokens
issued by another service that happens to share the signing secret are
refused with “jwt issuer invalid”.
Rate limits on auth endpoints
Brute-force, enumeration, and credential-stuffing surface is gated at the route level, on top of the global 100 req/60s throttle:
| Endpoint | Limit |
|---|---|
POST /auth/register | 10 per hour per IP |
POST /auth/login | 10 per 5 minutes per IP |
POST /auth/token | 10 per 5 minutes per IP |
POST /auth/forgot-password | 5 per hour per IP |
POST /auth/refresh | 20 per 5 minutes per IP |
GET /auth/check-organization-name | 30 per minute per IP |
POST /:org/:gw/register (OAuth dynamic client registration) | 10 per 5 minutes per IP |
Registration
Create a new account:
curl -X POST https://api.almyty.com/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "SecurePass123!",
"firstName": "Jane",
"lastName": "Doe",
"organizationName": "Acme Corp"
}'Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Valid email address |
password | string | Yes | Minimum 8 characters, must include uppercase, lowercase, and number |
firstName | string | Yes | User’s first name |
lastName | string | Yes | User’s last name |
organizationName | string | Yes | Name for the default organization |
Response
The session is the access_token cookie in the Set-Cookie header. The
body carries no token:
{
"success": true,
"data": { "expiresIn": 86400 },
"message": "Registration successful"
}Login
POST /auth/login is the browser login. It sets the httpOnly
access_token cookie and returns no token in the body, so nothing a
script on the page can read ever holds one:
curl -X POST https://api.almyty.com/auth/login \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{
"email": "user@example.com",
"password": "SecurePass123!"
}'{
"success": true,
"data": { "expiresIn": 86400 },
"message": "Login successful"
}Login for non-browser clients
POST /auth/token takes the same body and returns an access token and a
refresh token in the body, and sets no cookie. It refuses any request
that carries an Origin header, which every browser sends on a POST, so a
page cannot call it. For scripts and CI, an API key (below) is usually the
better fit.
curl -X POST https://api.almyty.com/auth/token \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "SecurePass123!"
}'{
"success": true,
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "eyJhbGciOiJIUzI1NiIs...",
"expiresIn": 86400
},
"message": "Login successful"
}Using the token
Include the access token in the Authorization header of API requests:
curl https://api.almyty.com/apis \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Token expiration
Access tokens expire after 24 hours. A client that signed in with
/auth/token redeems its refresh token at POST /auth/refresh for a new
pair; each refresh token works once, and presenting a spent one ends the
session. Like /auth/token, /auth/refresh refuses browser pages. For
long-lived programmatic access, use API keys instead.
API keys
API keys are long-lived tokens for scripts, CI pipelines, and CLI tools. They expire only when created with an expiresAt, stop working when revoked, and act with the full permissions of the user who minted them. Keys scoped to one gateway are separate: see Gateway authentication.
Getting an API key
The simplest way is npx @almyty/auth login: it opens your browser, mints a key named after the machine and date, and stores it at ~/.almyty/credentials.json so every CLI picks it up.
From a script, with a session cookie or an existing key:
curl -X POST https://api.almyty.com/auth/api-keys \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "CI pipeline" }'name is required; expiresAt (an ISO date) and organizationId are optional. The response carries apiKey, the full key, once. The key looks like almyty_3f9c.... Use it anywhere a Bearer token is expected:
curl https://api.almyty.com/apis -H "Authorization: Bearer almyty_3f9c..."GET /auth/api-keys lists your keys (name, prefix, last use, expiry, whether active); DELETE /auth/api-keys/:keyId revokes one.
Profile
Get profile
curl https://api.almyty.com/auth/profile \
-H "Authorization: Bearer $TOKEN"Update profile
curl -X PATCH https://api.almyty.com/auth/profile \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Jane Smith",
"timezone": "Europe/Berlin"
}'timezone is an IANA time zone name (null clears it back to UTC). Emails that wait for the morning, such as the daily model digest, go out at 08:00 in it.
Change password
curl -X PATCH https://api.almyty.com/auth/change-password \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"currentPassword": "OldPass123!",
"newPassword": "NewPass456!"
}'Organization context
Most API endpoints are scoped to an organization. How the backend picks the current organization depends on how many memberships the caller has:
- Single-org users: the backend auto-selects your one membership. No extra header is required.
- Multi-org users: you must send the
X-Organization-Idheader on every request. Without it the API returns400 NO_ORGANIZATION. This is a deliberate choice so that multi-org users never silently operate on the wrong org’s data.
# Multi-org user: pick which org to operate on
curl https://api.almyty.com/gateways \
-H "Authorization: Bearer $TOKEN" \
-H "X-Organization-Id: 9c0e2f3a-..."Some endpoints also accept the org id as a path parameter (e.g.
/organizations/{orgId}/tools). In that case the path wins and the
header is ignored, so the path must match a membership you actually
have.
Switching orgs from a multi-org account
If your account belongs to more than one org, just change the
X-Organization-Id header on your next request. The backend validates
that the requested org matches one of your memberships; otherwise the
request is rejected with 400 NO_ORGANIZATION.
Logout
curl -X POST https://api.almyty.com/auth/logout \
-H "Authorization: Bearer $TOKEN"This invalidates the current token server-side.