Identity and tokens
Identity (AUTH_BASE_URL) issues the access tokens that Frontline and GTFS-RT
require. All token requests are form-encoded POSTs to /connect/token. The
Identity route reference lists every published route.
Accounts and applications
Section titled “Accounts and applications”Your account is who you are. A grant type is how you prove it.
- Service account: a machine identity for an external integration, created
and owned by an approved admin. Its
client_idis its own OAuth application, so it authenticates as itself. This is currently the only way for external integrators to get access; see get access. - Human account: an existing, approved person. Humans sign in through an
approved public OAuth application (
HUMAN_OAUTH_CLIENT_IDbelow), which is not the username. Get its ID from your MyBusz contact. New human accounts can’t currently be created.
An application can use only the grants it is registered for. Confidential applications must also authenticate as a client, from a trusted server.
Grant types
Section titled “Grant types”| Grant type | Account | Proof | Token amr |
Refresh token |
|---|---|---|---|---|
urn:custom:nonce-challenge |
Service account | Nonce signed with a registered public key | pubkey |
No |
client_credentials |
Service account | Client secret | secret |
No |
urn:custom:password-totp |
Human | Password plus TOTP or recovery code | pwd, otp |
Yes |
urn:custom:passkey |
Human | WebAuthn assertion | passkey |
No |
Service accounts use only the first two grants. Password, TOTP, and passkeys are
for humans only. pubkey and passkey tokens get the higher Frontline
rate limit.
Access tokens currently last one hour. Schedule renewal from expires_in.
Validate tokens
Section titled “Validate tokens”Request under AUTH_BASE_URL |
Purpose |
|---|---|
GET /.well-known/openid-configuration |
Issuer and endpoint metadata |
GET /.well-known/jwks |
Public keys for verifying access-token signatures |
Read issuer and jwks_uri from discovery instead of hardcoding them or pinning
one signing key. Verify the signature, issuer, audience (mybusz-api), and
expiry; decoding a JWT is not validation. Scopes come from the account’s current
authority, not from what the client requests.
Service accounts
Section titled “Service accounts”Client secret
Section titled “Client secret”Send grant_type=client_credentials with client_id and client_secret, as in
getting started.
Public key
Section titled “Public key”Keep the private key on your own trusted signer.
1. Request a nonce.
curl --fail-with-body --silent --show-error \ --data-urlencode "clientId=${CLIENT_ID}" \ "${AUTH_BASE_URL}/connect/challenge"The response is JSON with a nonce string. This form field is clientId;
the token request uses client_id.
2. Sign the nonce. Sign the exact UTF-8 bytes of nonce (no quotes, newline,
or JSON wrapping) as a detached signature, not a JWT or JWS. Encode it with
standard Base64, not Base64url.
3. Exchange the signature. Set SIGNED_NONCE_BASE64 to the signature:
curl --fail-with-body --silent --show-error \ --data-urlencode 'grant_type=urn:custom:nonce-challenge' \ --data-urlencode "client_id=${CLIENT_ID}" \ --data-urlencode "signed_nonce=${SIGNED_NONCE_BASE64}" \ "${AUTH_BASE_URL}/connect/token"Supported algorithms are ES256, ES384, ES512, RS256, RS384, and RS512,
plus ML-DSA-44, ML-DSA-65, ML-DSA-87, SLH-DSA-SHA2-128s, and
SLH-DSA-SHAKE-128s where the server runtime supports them.
- Use the algorithm registered for your key, not your SDK’s default. A classical
JWK without
algresolves by curve (P-256, P-384, P-521 to ES256, ES384, ES512), and RSA resolves to RS256. - ECDSA signatures use fixed-width
r || s(IEEE P1363), not ASN.1 DER. - Post-quantum signatures use an empty context.
A nonce expires after five minutes and is used up by the exchange, even if the signature then fails. Each client ID has one pending nonce, and a new challenge replaces it, so run this flow one at a time per credential. After a failure or uncertain result, start again with a new challenge.
Switch from a secret to a public key
Section titled “Switch from a secret to a public key”POST /client/pubkey (deprecated) lets an eligible secret-based machine client,
including a service account, switch to public-key authentication. Call it with
that client’s bearer token (amr=secret) and JSON publicKeyJson: a string
containing a public JWK Set or supported public-key PEM. Never send a private key.
The switch is one-way. The secret is cleared, client_credentials stops working,
and you must use the nonce flow from then on. There is no self-service rollback,
and it can’t replace a key that is already registered.
Human accounts
Section titled “Human accounts”Human grants need an existing, approved account and the approved public
application’s client_id. They are not admin-only sign-in methods.
Password and TOTP
Section titled “Password and TOTP”The account must be approved, not locked out, and have TOTP set up. This custom grant is not the standard OAuth password grant.
curl --fail-with-body --silent --show-error \ --data-urlencode 'grant_type=urn:custom:password-totp' \ --data-urlencode "client_id=${HUMAN_OAUTH_CLIENT_ID}" \ --data-urlencode "username=${USERNAME}" \ --data-urlencode "password=${PASSWORD}" \ --data-urlencode "totp_code=${TOTP_CODE}" \ "${AUTH_BASE_URL}/connect/token"totp_code takes the current authenticator code or an unused recovery code;
recovery codes are used up. This is the only grant that returns a refresh token.
Refresh a session
Section titled “Refresh a session”curl --fail-with-body --silent --show-error \ --data-urlencode 'grant_type=refresh_token' \ --data-urlencode "client_id=${HUMAN_OAUTH_CLIENT_ID}" \ --data-urlencode "refresh_token=${REFRESH_TOKEN}" \ "${AUTH_BASE_URL}/connect/token"Use the same application as the original sign-in. Refresh tokens currently last seven days. Refresh one request at a time and store the new tokens from each response. Refresh rechecks the account’s existence, approval, lockout, roles, and security stamp; if it fails, sign in again instead of retrying.
Passkeys
Section titled “Passkeys”Passkeys work for existing human accounts without an admin role, but don’t bypass approval or account checks.
Run passkey ceremonies on an approved HTTPS browser origin that matches the
server’s WebAuthn relying party; agree on the origin with your MyBusz contact
first. Request bodies use the service’s FIDO2 JSON format, so binary challenges,
credential IDs, and authenticator responses need converting. Don’t just
JSON.stringify a browser credential. JSON fields are camelCase (totpCode),
while the token form uses totp_code. The route reference doesn’t include these
body schemas; get them from your MyBusz contact.
Add a passkey
Section titled “Add a passkey”POST /passkey/register-optionswithusername,password, andtotpCode. It returns the creation options.- Run the WebAuthn credential-creation ceremony with those options.
POST /passkey/register-completewith the sameusername, the serializedattestationResponse, and an optionalfriendlyName. It returns acredentialId, not a token.
The options expire after five minutes and are single-use. Each account can have one registration in progress.
Sign in with a passkey
Section titled “Sign in with a passkey”POST /passkey/authenticate-optionswithusername, or{}for a discoverable credential (which must already be registered). Keep the returnedsessionIdandoptions.- Run the WebAuthn authentication ceremony.
POST /passkey/authenticate-completewithsessionIdand the serializedassertionResponse. It returns anassertion_token.- Form-POST
/connect/tokenwithgrant_type=urn:custom:passkey, the application’sclient_id, andassertion_token.
The options last five minutes and the assertion_token two minutes; both are
single-use. Restart after expiry or an uncertain result. The token has
amr=passkey and no refresh token.
List or remove passkeys
Section titled “List or remove passkeys”POST /passkey/listwithusername,password, andtotpCodereturnspasskeys, each withid,friendlyName,createdAt,lastUsedAt, andaaGuid.POST /passkey/revokewith the same fields pluspasskeyIddeletes that passkey.passkeyIdis the integeridfrom the list, not the Base64credentialId.
Both require password and TOTP proof in the body; an access token isn’t enough. Removing a passkey doesn’t invalidate access tokens already issued.
Rotate TOTP
Section titled “Rotate TOTP”POST /account/reset-totpwithusername,password, andtotpCode(current TOTP or an unused recovery code; a password alone isn’t enough). It returnstotpSetupUriandtotpSharedKeyfor the new secret.- Add the new secret to your authenticator. TOTP sign-in is unavailable until you verify it, and existing refresh sessions can be invalidated.
POST /account/verify-totpwithusernameand atotpCodefrom the new authenticator. It returns newrecoveryCodes; store them securely.
Revoke tokens
Section titled “Revoke tokens”Form-POST /connect/revocation with token, an optional token_type_hint
(access_token or refresh_token), and the application’s client identity and
authentication. The application must be allowed to revoke that token, and the
nonce grant can’t authenticate this endpoint. Success can be an empty response
even for an already-invalid token; it isn’t a status check.
Transit APIs validate access tokens locally, so revoking a token doesn’t reject it everywhere immediately. On sign-out, also delete your local token copies.