Memberships
Create and manage memberships, the isolated tenants of ErtisAuth.
A membership is an isolated tenant: it owns users, roles, applications and every other resource, and it holds the authentication settings of its users. See Core Concepts.
The first membership is created by the setup. Further memberships are created through this API.
The membership object#
{
"_id": "66f1c0d2a4b5c6d7e8f90123",
"name": "My Company",
"slug": "my-company",
"expires_in": 3600,
"scoped_token_expires_in": 900,
"refresh_token_expires_in": 86400,
"reset_password_token_expires_in": 1800,
"secret_key": "<at least 32 bytes>",
"hash_algorithm": "ARGON2ID",
"encoding": "UTF-8",
"default_language": "en",
"user_activation": "active",
"code_policy": "tv-codes",
"otp_settings": {
"host": "https://app.example.com/reset-password",
"policy": {
"length": 6,
"contains_letters": false,
"contains_digits": true,
"expires_in": 300,
"max_attempts": 5
}
},
"mail_providers": [
{
"type": "SmtpServer",
"name": "Company SMTP",
"slug": "company-smtp",
"host": "smtp.example.com",
"port": 587,
"tls_enabled": true,
"username": "no-reply@example.com",
"password": "<password>"
}
],
"sys": { "created_at": "2026-01-01T12:00:00Z", "created_by": "system" }
}| Field | Required | Description |
|---|---|---|
name | yes | Display name. Also the iss claim of the tokens. |
slug | no | URL-friendly name, unique in the installation. Derived from the name when omitted. Also the aud claim of the tokens. |
expires_in | yes | Access token lifetime, in seconds. Must be greater than 0. |
refresh_token_expires_in | yes | Refresh token lifetime, in seconds. Must be greater than 0. |
scoped_token_expires_in | no | Scoped token lifetime, in seconds. 12 hours when 0 or omitted. |
reset_password_token_expires_in | no | Lifetime of password reset tokens, in seconds. 2 hours when omitted. |
secret_key | yes | The key all tokens of the membership are signed with (HMAC-SHA256). At least 32 bytes in the membership's encoding. |
hash_algorithm | yes | The password hash algorithm (see below). There is no default. |
encoding | no | Text encoding used to turn passwords and keys into bytes. UTF-8 by default. |
default_language | no | ISO 639-1 code of a database locale (e.g. en, tr). |
user_activation | no | active: new users must activate their account from an email before they can sign in. passive (default): new users are active immediately. |
code_policy | no | Slug of the code policy used by the device code flow. |
otp_settings | no | One-time password settings. host is required when it is set; policy.max_attempts must be at least 1 (5 by default). |
mail_providers | no | The mail providers used by mail hooks, see below. |
secret_key signs every token of the membership. Anyone who knows it can forge tokens for any user. Changing it invalidates all tokens issued so far, so every user has to sign in again.Password hash algorithms#
| Value | Recommendation |
|---|---|
ARGON2ID | Recommended for new memberships. |
PBKDF2-SHA512, PBKDF2-SHA256 | Good choices where Argon2 is not acceptable (e.g. FIPS environments). |
SHA2-224, SHA2-256, SHA2-384, SHA2-512, SHA2-512-224, SHA2-512-256, SHA3-224, SHA3-256, SHA3-384, SHA3-512 | Fast hashes, supported for importing existing user databases. Not recommended for new memberships. |
SHA1, MD5 | Legacy, only for importing old user databases. |
Fast hashes (SHA, MD5) can be brute-forced quickly if your database leaks. Prefer ARGON2ID unless you are importing password hashes from another system.
The values are also accepted with underscores (SHA2_512).
Mail providers#
mail_providers is a list of the email services the membership can send through. A mail hook refers to one by its slug.
type | Fields | Delivery |
|---|---|---|
SmtpServer | name, slug, host, port, tls_enabled, username, password | ErtisAuth renders the HTML and sends it over SMTP |
SendGrid | name, slug, apiKey | ErtisAuth renders the HTML and sends it with the SendGrid API |
MailChimp | name, slug, apiKey | A template stored in Mailchimp Transactional (Mandrill) is sent; see Mail Hooks |
"mail_providers": [
{ "type": "SendGrid", "name": "SendGrid", "slug": "sendgrid", "apiKey": "<api_key>" },
{ "type": "MailChimp", "name": "Mandrill", "slug": "mandrill", "apiKey": "<api_key>" }
]type is case-sensitive.
memberships.read only to the operators of the installation.Endpoints#
| Method | Route | Permission |
|---|---|---|
GET | /memberships/{id} | memberships.read.{id} |
GET | /memberships | memberships.read |
POST | /memberships/_query | memberships.read |
GET | /memberships/search?keyword= | memberships.read |
POST | /memberships | memberships.create |
PUT | /memberships/{id} | memberships.update.{id} |
DELETE | /memberships/{id} | memberships.delete.{id} |
GET | /memberships/settings | memberships.read |
GET | /memberships/settings/encodings | memberships.read |
GET | /memberships/settings/encodings/default | memberships.read |
GET | /memberships/settings/hash-algorithms | memberships.read |
GET | /memberships/settings/hash-algorithms/default | memberships.read |
GET | /memberships/settings/db-locales | memberships.read |
GET | /memberships/settings/db-locales/default | memberships.read |
The membership routes are installation-wide: they are not bound to the membership of the caller's token.
Get a membership#
GET /memberships/{id}
Authorization: Bearer <access_token>{id} is the id or the slug. Answers 404 MembershipNotFound when it does not exist.
List, query and search#
GET /memberships, POST /memberships/_query and GET /memberships/search?keyword= work as described in API Conventions.
Create a membership#
curl -X POST https://auth.example.com/memberships \
-H 'Authorization: Bearer <access_token>' \
-H 'Content-Type: application/json' \
-d '{
"name": "Mobile App",
"expires_in": 3600,
"refresh_token_expires_in": 2592000,
"secret_key": "<random string of at least 32 bytes>",
"hash_algorithm": "ARGON2ID",
"encoding": "UTF-8"
}'Generate the secret key with a secure random generator, for example openssl rand -base64 48.
Response 201 Created: the membership.
The new membership is empty. Create an admin role, a user type and the first users through its endpoints with a token that may access it, or use a separate installation and the setup for a completely new tenant.
| Error | When |
|---|---|
400 ModelValidationError | A required field is missing or invalid; data lists every problem |
409 MembershipAlreadyExists | The slug is taken |
Update a membership#
PUT /memberships/{id}The body has the same fields as the create request; the id in the route is the one updated, an _id in the body is ignored.
These fields keep their current value when they are omitted (or empty, or 0): name, secret_key, hash_algorithm, encoding, expires_in and refresh_token_expires_in. All other fields are replaced by what you send: omitting mail_providers, otp_settings, code_policy, user_activation, default_language, scoped_token_expires_in or reset_password_token_expires_in clears them. Read the membership first and send it back with your changes.
hash_algorithm, and existing hashes are not converted. Changing the algorithm of a membership that has users makes all their passwords stop working; they would all have to reset their password. Choose the algorithm when you create the membership.Delete a membership#
DELETE /memberships/{id}Answers 204 No Content. A membership that still has resources (users, roles, applications…) can't be deleted: 409 MembershipCouldNotDeleted.
Settings#
GET /memberships/settings returns the values a membership can use, in one response:
{
"encodings": [ { "displayName": "Unicode (UTF-8)", "name": "UTF-8" } ],
"defaultEncoding": "UTF-8",
"hashAlgorithms": [ "MD5", "SHA1", "SHA2-224", "SHA2-256", "SHA2-384", "SHA2-512", "SHA2-512-224", "SHA2-512-256", "SHA3-224", "SHA3-256", "SHA3-384", "SHA3-512", "ARGON2ID", "PBKDF2-SHA256", "PBKDF2-SHA512" ],
"defaultHashAlgorithm": "ARGON2ID",
"dbLocales": [ { "Name": "None", "ISO6391Code": "none" }, { "Name": "Turkish", "ISO6391Code": "tr" } ],
"defaultDbLocale": "none"
}The individual lists are also available under /memberships/settings/encodings, /hash-algorithms and /db-locales, each with a /default endpoint. defaultHashAlgorithm is the recommended algorithm; a membership has no default and must always name one.
Found a mistake in the docs? Open an issue