Skip to main content

Partner API: Admin SSO & Provisioning

Configure SAML SSO for an organization's admins with just-in-time provisioning and role mapping.

Admin SSO and provisioning

Configure single sign-on for an organization's admins (the people who edit and publish handbooks), with just-in-time provisioning and role mapping, so you never have to invite admins manually.

PUT /organizations/{orgId}/sso/admin | Access token

{
"protocol": "saml",
"idp": {
"entityId": "https://idp.acme.com/saml/metadata",
"ssoUrl": "https://idp.acme.com/saml/sso",
"certificate": "-----BEGIN CERTIFICATE-----\nMIID…\n-----END CERTIFICATE-----"
},
"jitProvisioning": true,
"roleAttribute": "airmason_role",
"roleMapping": {
"hr-admin": "owner",
"hr-editor": "handbook_editor",
"people-ops": "admin"
},
"defaultRole": null,
"loginRedirectUrl": "https://idp.acme.com/login",
"logoutRedirectUrl": "https://idp.acme.com/logout?from=airmason",
"enabled": true,
"enforced": true
}

Field

Notes

protocol

saml only. oidc returns 400 unsupported_protocol; contact support if you need it.

idp

Your identity provider's entity ID, SSO URL and signing certificate (PEM). certificate holds exactly one certificate. To trust two during a key rotation, send certificates instead (an array of 1 or 2, not both fields). An expired, not yet valid, duplicate or unparseable certificate is rejected with 400.

jitProvisioning

When true, an admin who signs in via SSO for the first time is created automatically with the mapped role — no invitation needed

roleAttribute

Name of the SAML attribute carrying the user's role

roleMapping

Your role values → AirMason roles. Exactly these values are accepted: owner, admin, handbook_editor, handbook_publisher, employee_admin, viewer. Custom organization roles are rejected. Required even if your identity provider already sends these names: a value with no mapping falls back to defaultRole. To send them directly, map them to themselves ("admin": "admin").

defaultRole

Role for users whose attribute value has no mapping. null means such users are refused.

loginRedirectUrl

Optional https URL of your sign-in page. Admins who signed in through your SSO are sent here instead of the AirMason sign-in page (see Login and logout redirects).

logoutRedirectUrl

Optional https URL. Admins who signed in through your SSO are sent here when they sign out (see Login and logout redirects).

enabled

Defaults to true. Set false to keep the configuration but stop offering SSO. With enabled: false the enforced value is ignored rather than rejected: password sign-in is restored and the response reports enforced: false.

enforced

When true, password sign-in is removed from the organization's admin login methods. Any Okta or Microsoft sign-in method the organization already had is kept.

The response describes both sides of the handshake. Certificates are write-only: only their expiry dates come back, in certificateExpiries (one per certificate, in the order you sent them). certificateExpiresAt is the first certificate's expiry.

{
"protocol": "saml",
"enabled": true,
"enforced": true,
"idp": {
"entityId": "https://idp.acme.com/saml/metadata",
"ssoUrl": "https://idp.acme.com/saml/sso",
"certificateExpiresAt": "2028-03-01T00:00:00.000Z",
"certificateExpiries": ["2028-03-01T00:00:00.000Z"]
},
"sp": {
"entityId": "airmason-admin:1f2a7b3c-9d4e-4f5a-8b6c-0d1e2f3a4b5c",
"acsUrl": "https://api.airmason.com/v1/sso/saml/partner/1f2a7b3c-9d4e-4f5a-8b6c-0d1e2f3a4b5c/login"
},
"jitProvisioning": true,
"roleAttribute": "airmason_role",
"roleMapping": { "hr-admin": "owner", "hr-editor": "handbook_editor", "people-ops": "admin" },
"defaultRole": null,
"loginRedirectUrl": "https://idp.acme.com/login",
"logoutRedirectUrl": "https://idp.acme.com/logout?from=airmason"
}

Configure sp.entityId (audience) and sp.acsUrl in your IdP. Both are per organization: sp.entityId is airmason-admin:<organization id> and sp.acsUrl is /v1/sso/saml/partner/<organization id>/login. If you reuse one identity-provider application across several tenants, make sure each assertion carries the audience of the organization it is for. Role, name and email changes in your IdP are applied on the user's next sign-in.

GET returns the current configuration in the same shape (404 when nothing is configured). DELETE returns 204, disables SSO, re-enables password login and erases the stored certificates.

PATCH /organizations/{orgId}/sso/admin changes only the fields you send and leaves the rest as configured. It takes the same fields and validation as PUT, returns the full configuration, and answers 404 when nothing is configured (it never creates one). Sending idp.certificate or idp.certificates replaces all stored certificates:

{ "idp": { "certificates": ["-----BEGIN CERTIFICATE-----\nMIID…(new)…", "-----BEGIN CERTIFICATE-----\nMIID…(current)…"] } }

Roles

Role

Can

Admin app preset

owner

Everything (the organization's account owner)

Owner (not offered when inviting)

admin

Everything: handbooks, employees, settings

Account Admin

handbook_publisher

Create, edit, publish and delete handbooks

Document Content Manager

handbook_editor

View and edit handbooks (not create, publish or delete)

Document Content Editor

employee_admin

Manage employees

—

viewer

View handbooks, signatures and analytics (no changes)

Viewer

Identifying the user

AirMason reads the assertion's NameID and these SAML attributes (in the AttributeStatement, by Name):

Attribute

Required

Notes

NameID (the assertion's Subject)

Always

Your stable id for the person, at most 100 characters, compared case-insensitively. It identifies the admin, so it must never change and never be reused for someone else. Any format is accepted; persistent is recommended.

email (or mail, urn:oid:0.9.2342.19200300.100.1.3)

Always

The admin's sign-in email. Used to find an existing admin the first time a NameID is seen. The attribute name cannot be configured.

firstName, lastName

To create a new admin (JIT)

Re-applied on every sign-in. If one is missing on a later sign-in, the stored value is kept.

your roleAttribute

If you use roleMapping

See roleMapping above.

  • The NameID identifies the admin. Send your stable user id (your directory's immutable id, not the email). AirMason does not request a NameID format, so the one your IdP is configured with is used (persistent recommended). On each sign-in AirMason looks the admin up by NameID first:

  • Known NameID: this is the admin. If the email changed, the new email is saved on their account; the sign-in is refused (account_conflict) when another AirMason user already has that email.

  • New NameID: AirMason looks for an admin with the email and links your NameID to them. If that account is already linked to a different NameID, the sign-in is refused (account_conflict).

  • Neither: with jitProvisioning on, a new admin is created; otherwise the sign-in is refused (not_a_member).

  • Use the same id as sourceId in admin sync (below): an admin you sync who then signs in is one account.

  • An assertion without email or NameID, or a new admin without firstName / lastName, is refused ("Looks like there is an issue with configured claims/attributes").

Signing and security requirements

Requirement

Signature

The Assertion must be signed. Signing the Response as well is optional. A signed Response around an unsigned Assertion is rejected.

Certificate

The signature must verify against one of the configured certificates. To rotate keys without downtime: PATCH idp.certificates with the new and the current certificate, switch your IdP to the new key, then PATCH idp.certificates with the new one only.

Algorithms

RSA-SHA256 with a SHA-256 digest (recommended) or RSA-SHA512 / SHA-512. SHA-1 is deprecated and HMAC is not accepted. Use exclusive C14N with the enveloped-signature transform.

Encryption

Encrypted assertions are not supported. Send a signed plaintext assertion over HTTPS.

Assertions

Exactly one per Response.

Audience / Issuer

Audience = sp.entityId. Set Issuer to your idp.entityId.

Requests

AirMason does not sign AuthnRequests. Don't require signed requests.

Clock

Up to 3 minutes of clock skew is tolerated on NotBefore / NotOnOrAfter.

Login and logout redirects

Both fields apply only to sessions that started with your SSO for this organization. AirMason uses the URL exactly as configured (no parameters, no token).

Field

The admin is sent there when

logoutRedirectUrl

they sign out. Use your IdP's logout endpoint to sign them out of both systems, or a landing page to sign them out of AirMason only.

loginRedirectUrl

their session expires (2 hours idle), they open an AirMason link with no session, or AirMason rejects your assertion.

  • Admins who signed in with a password are never redirected. The URL comes from the organization they signed in through, even if they switch organizations later.

  • For expired sessions and links, loginRedirectUrl only works in a browser that has already signed in through your SSO. In a new browser the AirMason sign-in page is shown. If a second assertion is rejected within 2 minutes, the admin sees the AirMason error page with its reference code, which prevents a redirect loop.

  • PUT replaces the whole configuration, so send both fields on every PUT (send null to remove one), or use PATCH to change only one. The employee endpoint rejects both with 400 validation_error.

When sign-in fails

The admin sees a message ending in a reference code, for example "Single sign-on failed. Please contact your administrator. Reference: 3f9a1c". (If AirMason rejects your assertion and loginRedirectUrl is set, the admin is sent there instead.) Each failure is also recorded in GET /organizations/{orgId}/activity:

{
"type": "sso.login_failed",
"occurredAt": "2026-10-14T15:02:11.000Z",
"handbookId": null,
"actor": { "type": "system" },
"employeeId": null,
"description": null,
"details": { "referenceCode": "3f9a1c", "category": "role_not_mapped", "email": "jane@acme.com" }
}

category

Cause

assertion_rejected

Signature, certificate, expiry, audience, encryption or more than one assertion (no email is recorded)

missing_email

No email attribute

missing_name_id

No NameID, a blank one, or one longer than 100 characters

missing_name_attributes

A new admin without firstName / lastName

role_not_mapped

No roleMapping match and defaultRole is null

not_a_member

Not an admin of the organization and jitProvisioning is off

member_not_activated

Invited but not activated, and jitProvisioning is off

login_method_not_allowed

SSO is not among the organization's admin login methods

account_conflict

The email belongs to an AirMason user outside your organizations, to another user (after an email change), or to an admin linked to a different NameID

  • Filter with ?type=sso.login_failed. The same feed also returns sso.user_provisioned (details: email, role) and sso.config_updated (details: action, platform, configurationId).

  • At most 20 failures per minute are recorded per organization.

  • When you contact support, include the reference code.

Admin sync

Besides just-in-time creation at SAML sign-in, you can create, update and deactivate an organization's admins directly. Both endpoints use the organization's access token.

Import or update admins

PUT /organizations/{orgId}/admins — 1 to 100 admins per request.

{
"admins": [
{ "sourceId": "u-1842", "email": "jane@acme.com", "firstName": "Jane", "lastName": "Doe", "role": "admin" },
{ "sourceId": "u-2207", "email": "john@acme.com", "firstName": "John", "lastName": "Roe", "role": "viewer" }
]
}

Field

Notes

sourceId

Required. Your stable id for the person, 1 to 100 characters, compared case-insensitively. Use the same value as the SAML NameID (see Identifying the user). It must never change and never be reused for someone else. In responses, admin.sourceId is null for an admin you have not linked to a sourceId yet.

email

Required. The admin's sign-in email.

firstName, lastName

Required, at most 50 characters each.

role

One of owner, admin, handbook_editor, handbook_publisher, employee_admin or viewer (the same roles as SSO role mapping).

Each admin is matched by sourceId first, then by email:

  • Known sourceId: this is the admin. A different email updates their account's email (same id, same role and membership, reported as updated), unless another AirMason user already has that email.

  • New sourceId: AirMason looks for someone with the email and links your sourceId to them. If they are already linked to a different sourceId, the row fails.

  • Neither: a new admin is created and added to the organization.

Someone you deactivated earlier is reactivated. Rows are processed in order and each one is reported separately, so one failure does not stop the others:

{
"summary": { "created": 1, "updated": 1, "failed": 0 },
"results": [
{
"index": 0,
"sourceId": "u-1842",
"email": "jane@acme.com",
"status": "created",
"admin": {
"id": 10112,
"sourceId": "u-1842",
"email": "jane@acme.com",
"firstName": "Jane",
"lastName": "Doe",
"role": "admin",
"status": "active",
"activatedAt": "2026-10-13T16:06:11.000Z"
}
},
{ "index": 1, "sourceId": "u-2207", "email": "john@acme.com", "status": "updated", "admin": { … } }
]
}
  • A row that cannot be applied comes back with "status": "failed" and an error object: 409 "This email is already used by another AirMason user" (the sourceId is known and its new email belongs to someone else), 409 "This email belongs to an admin with a different sourceId" (that account is already linked to another sourceId), 409 "This email belongs to an AirMason user who cannot be added by this partner" (an AirMason account outside your organizations), 409 "Cannot change the role of the last owner of the organization", 409 "This user cannot be managed through the Partner API" (AirMason staff accounts), or 400 when the role is not available for the organization.

  • The whole request is rejected with 400 validation_error for an empty list or more than 100 admins, a missing or too long sourceId, an invalid email, a missing first or last name, an unknown role, or the same email or sourceId twice (case-insensitive); and with 409 if the organization is deactivated.

  • Re-sending the same admin with the same values is a no-op reported as updated.

  • If the email already belongs to an admin of another of your organizations, they are added to this organization with the requested role, but their name is not changed; the response shows the name on their existing account. An email already used by an AirMason account outside your organizations is refused (409).

  • Imported admins are not sent an invitation email and have no password. They sign in through the organization's admin SSO (above), or set a password with "Forgot password" on the admin sign-in page.

Deactivate an admin

POST /organizations/{orgId}/admins/{adminRef}/deactivate — {adminRef} is the admin's id from the import response, ext:<sourceId> (for example ext:u-1842), or their email. This removes access to this organization only; the person keeps any other organizations they belong to. It is idempotent: deactivating an already-deactivated admin returns 200 with "status": "inactive" again. Importing the admin again reactivates them.

Errors: 404 "Admin not found" (not a member of this organization), 409 for the last owner of the organization, an AirMason staff account, or a deactivated organization.

Employee SSO

Employee-facing SSO (for the handbook portal) is configured at PUT /organizations/{orgId}/sso/employee (and updated with PATCH, as above) with the same protocol, idp, enabled and enforced fields, but it is not configured the same way as admin SSO:

  • jitProvisioning, roleAttribute, roleMapping, defaultRole, logoutRedirectUrl and idp.certificates are rejected with 400 validation_error (send one certificate as idp.certificate), and the response carries no role fields. Employees are matched to the records you created through the API.

  • sp.entityId and sp.acsUrl are both <portal url>/custom_saml/saml2, for example https://books.airmason.com/acme-corp/custom_saml/saml2.


Partner API documentation:

Did this answer your question?