Sign in users: email, tokens and API keys
Register and sign in users with email and password, manage sessions with access and refresh tokens, reset passwords, and know when to use an API key.
Last updated
Every request your app makes to the Anythink REST API is made as someone: a signed-in user or an API key. This page covers the user side. A user signs in with an email address and password, gets an access token and a refresh token, and sends the access token with each request. Anythink identifies the user from it and applies their role's permissions and any row-level security, on every call.
Authentication answers who is calling. What they may do is decided by roles and permissions, and which records they may see by groups and row-level security.
| Task | Endpoint |
|---|---|
| Register | POST /org/{project_id}/auth/v1/register |
| Confirm an email address | POST /org/{project_id}/auth/v1/confirm-email |
| Sign in | POST /org/{project_id}/auth/v1/token |
| Refresh a session | POST /org/{project_id}/auth/v1/refresh |
| Sign out | POST /org/{project_id}/auth/v1/logout |
| Get the signed-in user | GET /org/{project_id}/users/me |
| Reset a forgotten password | POST /org/{project_id}/auth/v1/send-password-reset then POST /org/{project_id}/auth/v1/confirm-password-reset |
| Change your password | PUT /org/{project_id}/users/me/password |
| Accept an invitation | POST /org/{project_id}/users/invites/{invite_token} |
Your project ID is the number after /org/ in the dashboard address. The examples use https://api.my.anythink.cloud as the API URL. The register, sign-in, refresh, sign-out, confirmation, reset and invitation endpoints need no token, because the caller isn't signed in yet.
Other ways in:
- Users who already have an Apple, Google, GitHub or LinkedIn account can use social sign-in. It returns the same tokens as this page.
- React apps can use the Anythink SDK, which wraps every call below and refreshes the session for you.
Let users register
Self-registration is controlled by three project settings. They apply to the register endpoint, and to nothing else: you can always create users yourself, whatever these say.
| Setting | What it does |
|---|---|
| Allow registrations | Turns the register endpoint on. Off, it returns 400 Organisation does not allow registrations. |
| Require email confirmation for new users | On, a new user gets a confirmation email and can't sign in until they confirm. Off, they can sign in straight away. |
| Default role for new users | The role every self-registered user gets. Required when registrations are on. |
Pick a default role that holds only what a new user should be able to do. Everything a user can read or write comes from their role. See Roles and permissions.
In the Anythink dashboard
- Open Settings › Users.
- In the User access card, tick Allow registrations.
- Choose a Default role for new users. Administrator roles aren't offered here.
- Tick Require email confirmation for new users if users must confirm their address before they sign in.
- Click Save.
With the CLI
Note: These project settings are managed in the dashboard. Use the steps above.
With an AI assistant (MCP)
Note: These project settings are managed in the dashboard. Your assistant can walk you through the steps above.
Register with email and password
A registration creates the user in your project with the default role. The response is plain text, not tokens: the user signs in as a separate step.
In the Anythink dashboard
To add a user yourself rather than let them register:
- Open Settings › Users and click Create Project User.
- Enter the First Name, Last Name and Email, and choose a Role.
- Click Create Project User. Anythink emails the user an invitation to set their own password. See Invite people and team members.
With the CLI
The CLI has no command for end-user registration. Call the endpoint with anythink fetch, which adds the active project's path for you:
anythink fetch /auth/v1/register --method POST --body '{
"first_name": "Alice",
"last_name": "Smith",
"email": "alice@example.com",
"password": "Str0ng!passphrase"
}'
With an AI assistant (MCP)
Register alice@example.com as a user in this project, first name Alice, last name Smith.
The assistant uses the cli tool to fetch /auth/v1/register. To create a user without choosing a password for them, ask it to invite them instead: it creates the user with fetch /users, which sends the invitation. Connect an AI assistant sets up the MCP server.
The raw request:
POST /org/{project_id}/auth/v1/register
Content-Type: application/json
{
"first_name": "Alice",
"last_name": "Smith",
"email": "alice@example.com",
"password": "Str0ng!passphrase"
}
Registration successful
Optionally add referral_code, or a signup_context object with utm_source, utm_medium, utm_campaign, utm_term, utm_content, referrer and signup_country. Anythink records the context against the new user.
The rules:
| Field | Rule |
|---|---|
first_name, last_name |
Required, up to 50 characters. |
email |
Required, a valid address, up to 255 characters. |
password |
At least 8 characters, with an uppercase letter, a lowercase letter, a digit and a special character. |
A request that breaks a rule returns 400 with an errors object listing each failed field, for example:
{
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Password": [
"Password must be at least 8 characters long",
"Password must contain at least one uppercase letter"
]
}
}
| Status | Body | Cause |
|---|---|---|
200 |
Registration successful |
The user was created. |
400 |
Organisation does not allow registrations |
Allow registrations is off. |
400 |
Organisation does not have a default role for new users |
No default role is set, or the role no longer exists. |
400 |
validation errors | A field broke a rule above. |
Confirm an email address
With Require email confirmation for new users on, the new user is sent an email containing a confirmation token. The token lasts 24 hours. Until the user confirms, sign-in returns 401 User email address is not confirmed.
Your app passes the email address and token from the email to Anythink:
POST /org/{project_id}/auth/v1/confirm-email
Content-Type: application/json
{
"email": "alice@example.com",
"token": "7F3A9C1E5B2D4A6890E1C3B5D7F92A4C"
}
Confirmation successful
An unknown or expired token returns 400 Invalid confirmation token.
The email comes from your project's confirmation template. You can restyle it in Email templates.
To send a fresh confirmation email, call the resend endpoint. It always answers 204 No Content, whether or not the address exists, and sends at most one email a minute per user:
POST /org/{project_id}/auth/v1/resend-confirm-email?email=alice@example.com
In the Anythink dashboard
An administrator can fix a stuck account without a token. Open the user under Settings › Users, then click Resend Confirmation Email or Mark as Confirmed.
With the CLI
anythink fetch '/auth/v1/resend-confirm-email?email=alice@example.com' --method POST
To confirm a user without an email, an administrator can call anythink fetch /users/42/mark-as-confirmed --method POST, using the user's ID from anythink users list.
With an AI assistant (MCP)
Resend the confirmation email to alice@example.com, or mark user 42 as confirmed.
Sign in
Signing in exchanges an email and password for a session.
In the Anythink dashboard
Your own app provides the sign-in screen, so there is nothing to configure per sign-in. To check that an account can sign in, open Settings › Users. A user who has confirmed their address and has an active account shows Active. A user who hasn't accepted an invitation shows Invite Sent.
With the CLI
anythink fetch /auth/v1/token --method POST --body '{
"email": "alice@example.com",
"password": "Str0ng!passphrase"
}'
The CLI itself signs in as you, not as an end user. Use anythink users me to see which account the CLI is using. See Get started with the CLI.
With an AI assistant (MCP)
Sign in alice@example.com with her password and tell me when the access token expires.
The assistant uses the cli tool to fetch /auth/v1/token. Anything you paste into a chat passes through the assistant, so use a test account, not a real user's password.
The raw request:
POST /org/{project_id}/auth/v1/token
Content-Type: application/json
{
"email": "alice@example.com",
"password": "Str0ng!passphrase"
}
{
"access_token": "eyJhbGciOiJIUzI1NiIs…",
"refresh_token": "OW+kSomn10…",
"expires_in": 1800
}
| Status | Body | Cause |
|---|---|---|
200 |
access_token, refresh_token, expires_in |
Signed in. |
401 |
Invalid email or password |
No user with that email, or the password is wrong. |
401 |
User email address is not confirmed |
The user hasn't confirmed their address yet. |
401 |
User is not active |
The account is inactive. |
Sessions: access and refresh tokens
A sign-in returns a pair of tokens.
| Token | Lifetime | Used for |
|---|---|---|
| Access token | 30 minutes (expires_in is 1800 seconds) |
Sent as Authorization: Bearer … on every API call. |
| Refresh token | 30 days | Exchanged for a new pair when the access token expires. |
The access token is a signed JWT. Treat it as opaque in your app: read the user from GET /users/me, not from the token's claims.
Refresh tokens are single use. Each refresh returns a new access token and a new refresh token, and revokes the one you sent. Always store the newest pair. A refresh token is tied to the project that issued it. Keep it somewhere only your app can read, such as an HTTP-only cookie or the device's secure storage, because anyone holding it can open a session until it expires.
A request with a missing, malformed or expired access token returns 401 Unauthorized. That is your cue to refresh, replay the request once with the new token, and send the user back to sign in if the refresh also fails.
Call the API as the user
Send the access token as a bearer token:
GET /org/{project_id}/orders
Authorization: Bearer eyJhbGciOiJIUzI1NiIs…
What comes back depends on the user's role and any row-level security on the entity. See the REST API overview for the endpoints themselves.
Get the signed-in user
In the Anythink dashboard
Open Settings › Users to see each user's role, status and last login. Open a user to see their confirmation and invitation details.
With the CLI
anythink users me
This shows the account the CLI is using. To see another user, run anythink users get 42.
With an AI assistant (MCP)
Show me the current user, and the user with ID 42.
The raw request:
GET /org/{project_id}/users/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIs…
{
"id": 42,
"gid": "368f7b33-940e-4aad-a3d8-5a28215935bd",
"first_name": "Alice",
"last_name": "Smith",
"email": "alice@example.com",
"role_id": 104,
"role_name": "Standard User",
"is_active": true,
"confirmed_at": "2026-10-04T13:31:50.114391Z",
"last_login_at": "2026-10-04T13:31:50.307407Z",
"auth_provider": null,
"permissions": [
{ "name": "orders:read", "description": "…", "is_active": true }
]
}
The response carries more fields than shown. permissions lists everything the user's role allows, so your app can show or hide controls. These related endpoints take the same bearer token:
| Endpoint | Returns |
|---|---|
GET /users/me/permissions |
Just the permissions list. |
GET /users/me/has-permission?permissionName=orders:read |
true or false. |
GET /users/me/has-role?roleName=Standard User |
true or false. |
GET /users/me/is-admin |
true or false. |
PUT /users/me |
Updates first_name and last_name. Returns 204. |
Without a valid token, GET /users/me returns 401.
Refresh a session
With the CLI
anythink fetch /auth/v1/refresh --method POST --body '{ "token": "OW+kSomn10…" }'
With an AI assistant (MCP)
Refresh this session with the refresh token in my REFRESH_TOKEN variable.
In the Anythink dashboard
The dashboard refreshes its own session automatically. There is nothing to do.
The raw request:
POST /org/{project_id}/auth/v1/refresh
Content-Type: application/json
{
"token": "OW+kSomn10…"
}
{
"access_token": "eyJhbGciOiJIUzI1NiIs…",
"refresh_token": "d4Qm1xZ8…",
"expires_in": 1800
}
The body field is token, and it holds the refresh token. An unknown, revoked or expired refresh token, or one from a different project, returns 401 Invalid refresh token. Sending the same refresh token a second time also returns it, because the first use revoked it.
Sign out
Signing out revokes the refresh token so it can't open another session. Your app should also discard both tokens on the device.
With the CLI
anythink fetch /auth/v1/logout --method POST --body '{ "token": "d4Qm1xZ8…" }'
anythink logout is different: it removes the CLI's own saved profile, and doesn't sign an end user out.
With an AI assistant (MCP)
Sign out the session with the refresh token in my REFRESH_TOKEN variable.
In the Anythink dashboard
Choose Sign Out in the user menu to end your own dashboard session.
The raw request:
POST /org/{project_id}/auth/v1/logout
Content-Type: application/json
{
"token": "d4Qm1xZ8…"
}
It returns 200. Refreshing with that token afterwards returns 401 Invalid refresh token.
Reset and change passwords
Reset a forgotten password
A reset has two steps. Your app asks for a reset email, then submits the code from that email with the new password.
In the Anythink dashboard
Your app provides the forgot-password screen, so use the endpoints below.
With the CLI
anythink fetch '/auth/v1/send-password-reset?email=alice@example.com' --method POST
anythink fetch /auth/v1/confirm-password-reset --method POST --body '{
"email": "alice@example.com",
"token": "482913",
"password": "An0ther!passphrase"
}'
With an AI assistant (MCP)
Send a password reset email to alice@example.com.
The assistant uses the cli tool to fetch /auth/v1/send-password-reset. Finish the reset in your app, so the new password never passes through the chat.
The raw requests:
POST /org/{project_id}/auth/v1/send-password-reset?email=alice@example.com
This always returns 204 No Content, whether or not the address belongs to a user, so it can't be used to find out who has an account. It sends at most one email a minute per user.
POST /org/{project_id}/auth/v1/confirm-password-reset
Content-Type: application/json
{
"email": "alice@example.com",
"token": "482913",
"password": "An0ther!passphrase"
}
| Status | Body | Cause |
|---|---|---|
204 |
none | The password was changed. The user signs in with it. |
400 |
Invalid password reset token |
The code is wrong, or has been used. |
400 |
Password reset token has expired |
The code is older than an hour. Request another. |
400 |
validation errors | The new password breaks the password rules. |
The reset code is single use. Requesting a new reset replaces any earlier code. The email comes from your project's password reset template (Email templates).
Change your own password
A signed-in user can change their password with their access token.
In the Anythink dashboard
Your app provides the change-password screen, so use the endpoint below.
With the CLI
anythink fetch /users/me/password --method PUT --body '{
"current_password": "Str0ng!passphrase",
"new_password": "An0ther!passphrase"
}'
This changes the password of the account the CLI is signed in as.
With an AI assistant (MCP)
Change passwords in the dashboard or in your app, not in a chat.
The raw request:
PUT /org/{project_id}/users/me/password
Authorization: Bearer eyJhbGciOiJIUzI1NiIs…
Content-Type: application/json
{
"current_password": "Str0ng!passphrase",
"new_password": "An0ther!passphrase"
}
It returns 204 No Content. The next sign-in uses the new password. Choose a new password that meets the password rules.
Invite people and team members
Invite people when you want to choose who joins, rather than let anyone register. The invited user gets an email with a link to set their name and password. The invitation lasts 7 days.
In the Anythink dashboard
- Open Settings › Users and click Create Project User.
- Enter the name and Email, choose a Role, and click Create Project User. The invitation is sent.
- To follow up, open the user and use Resend Invitation Email. The users list shows Invite Sent until they accept.
With the CLI
anythink fetch /users --method POST --body '{"first_name":"Alice","last_name":"Smith","email":"alice@example.com","role_id":104}'
Find role IDs with anythink roles list.
With an AI assistant (MCP)
Invite alice@example.com, Alice Smith, as a Standard User.
The assistant runs roles list to find the role, then creates the user with fetch /users.
When the user opens the link, your app (or the hosted page) completes the invitation. Both calls need no token:
GET /org/{project_id}/users/invites/{invite_token}
This returns the invited user's name, email and role, so you can pre-fill your form. An unknown or expired token returns 404.
POST /org/{project_id}/users/invites/{invite_token}
Content-Type: application/json
{
"first_name": "Alice",
"last_name": "Smith",
"password": "Str0ng!passphrase"
}
It returns 204 No Content, and the user can then sign in with that email and password. A bad token returns 400 Invalid invite token. The password follows the password rules.
For teams, roles, project switching and access across several projects, see Manage app users and team members.
API keys or user sessions
Use a user session when a person is using your app, so each request carries that person's identity, role and row-level access. Use an API key when code you run yourself calls the API with nobody signed in: a nightly job, a CI step, a backend service. You send it in the x-api-key header instead of a bearer token:
GET /org/{project_id}/customers
x-api-key: ak_your_key_here
| User session | API key | |
|---|---|---|
| Who it represents | One signed-in person | The user who created the key |
| Credential | Authorization: Bearer <access token> |
x-api-key: ak_… |
| Lifetime | 30 minutes, renewable for 30 days | 30 to 365 days, chosen at creation |
| Where it belongs | Browsers and mobile apps | Servers, scripts and CI |
Never put an API key in a browser or mobile app. Anyone who opens the app can read it.
Limits and trade-offs
| Limit | Detail |
|---|---|
| Access token | 30 minutes. |
| Refresh token | 30 days, single use. |
| Confirmation token | 24 hours. Resend limited to one email a minute per user. |
| Password reset code | 1 hour, single use. One reset email a minute per user. |
| Invitation | 7 days. |
| Password rules | 8 or more characters with an uppercase letter, a lowercase letter, a digit and a special character. |
| Names and email | 50 characters for each name, 255 for the email. |
| Registration settings | Managed in the dashboard. |