Skip to content

POST /auth/sessions

Sign in with email and password

The password sign-in: an email, a password, and the answer is an access/refresh pair plus the session row both hang off. The access token lives fifteen minutes and verifies by its signature; the refresh token lives thirty days, rotates on every use, and a rotated-out token replayed later invalidates the whole family, because a replay means two holders. Where the workspace has switched the password off (sign_in_policy.methods without PASSWORD), every password sign-in is refused with auth.password_not_offered, for every address alike and before any account is looked up; the stored password is kept, and works again when the password is switched back on. The one exception is a workspace left with no way in that works - no provider switched on there, whatever the cause - where the password opens as the fallback (password_fallback on the sign-in rules), and each sign-in through it is recorded in the workspace's trail as auth.password_fallback. The tenant is resolved before the credential is checked - from the subdomain or the X-Hubtask-Tenant header in multi mode, and in single mode there is only one - never from the request body. A contradiction between an authenticated caller and the host stays 403 tenant_mismatch. Every failure is one generic refusal, byte for byte the same for a wrong password and an address nobody holds: whether an account exists is exactly what a guessing client is trying to learn. Behind that one answer sit the progressive delay and the lockout, per account and per IP, and the auth rate-limit bucket in front of everything.

Request body application/json — SignIn

NameTypeRequiredDescription
identifierstring
  • minLength: 1
  • maxLength: 254
optionalThe address the account signs in with, or its sign-in name where it has one instead (a managed account).
emailstring (email)
  • maxLength: 254
optionalThe address, for a client that knows only addresses.
passwordstring
  • maxLength: 1024
requiredChecked in constant shape against the stored Argon2id hash.

Responses

  • 201 Signed in, with the pair shown for the only time SessionTokens
  • 202 The password was right and a second step is owed: a TOTP code where one is enrolled, or - under tenant enforcement, for an administrator not yet enrolled - the enrolment itself. MfaChallenge
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X POST "$HUBTASK_URL/auth/sessions" \  -H "Content-Type: application/json" \  -d '{  "identifier": "string",  "email": "anna@example.org",  "password": "string"}'

GET /auth/sessions

The caller's active sessions

One's own sessions, newest first: where each was opened (the user agent and the IP class recorded at sign-in - a hint for recognising one's own devices, never a precise address), when it was created, when it last acted, and which one is answering this very call. Never anybody else's, whatever the role: a session is the person's, and an administrator who suspects one acts by disabling the account, not by reading its sessions. Only open sessions are listed - those the next request accepts: one ended, run out, past the workspace's maximum age or idle time, or opened before it required a new password from everyone is absent.

Responses

  • 200 The sessions
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X GET "$HUBTASK_URL/auth/sessions" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

DELETE /auth/sessions

Sign out everywhere

Ends every session of the caller's account, this one included: every refresh family dies, and every access token still in flight refuses on its next request. The answer to "I left myself signed in somewhere" - and the reason the route needs nothing but the will to call it.

Responses

  • 204 Every session ended
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X DELETE "$HUBTASK_URL/auth/sessions" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

POST /auth/sessions:revoke-others

Sign out everywhere else

Ends every session of the caller's account except the one making this call: the answer to a lost phone, asked from the laptop that is still in hand. Every other refresh family dies, and every other access token still in flight refuses on its next request. Called with a personal access token there is no session of the caller's to keep, so every session ends. Journalled in the workspace's trail with the number ended.

Responses

  • 204 Every other session ended
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X POST "$HUBTASK_URL/auth/sessions:revoke-others" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

POST /auth/sessions:refresh

Exchange a refresh token for the next pair

Rotation, not renewal: the presented refresh token is retired in the same moment the new pair is minted, and presenting it again afterwards is treated as theft - two holders of one token - so the whole family is invalidated, the sign-in that opened it is over everywhere, and the reuse raises its own metric reason and an alert. The route is public the way sign-in is: the refresh token in the body is the whole credential, and demanding a bearer header beside it would demand the very thing this call exists to replace.

Request body application/json — SessionRefresh

NameTypeRequiredDescription
refresh_tokenstring requiredThe one being exchanged.

Responses

Example bash
curl -X POST "$HUBTASK_URL/auth/sessions:refresh" \  -H "Content-Type: application/json" \  -d '{  "refresh_token": "string"}'

DELETE /auth/sessions/{sessionId}

End one session

Ends one of the caller's own sessions: its refresh family dies and its access token refuses on its next request, immediately rather than at the end of the token's fifteen minutes. Somebody else's session is not found rather than forbidden - whether a session identifier exists is nobody's business but its holder's. Revoking twice is not an error.

Path parameters

NameTypeRequiredDescription
sessionIdstring (uuid) required

Responses

  • 204 Ended
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X DELETE "$HUBTASK_URL/auth/sessions/{sessionId}" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

POST /auth/invitations:redeem

Redeem an invitation and set the first password

The moment an invited account becomes a person: the redemption token from the invitation mail, a password under the policy (twelve characters at least), and the account moves from INVITED to ACTIVE - signed in, with the same answer sign-in gives, because making somebody who just proved control of the mailbox type the password again teaches nothing. Where the workspace has switched the password off, no password is set: the redemption is refused with auth.password_not_offered, and the invited person accepts the invitation by signing in through the workspace's provider, which activates the account. Where no provider is switched on either, the password is open as the fallback (password_fallback on the sign-in rules): the invitation is accepted with a password, and the workspace's trail records it as auth.password_fallback. The token was shown once, in the invitation, and is stored only as a hash under its own purpose label; it dies on redemption, so a second redemption is refused however fresh the token looks. An expired or unknown token is one indistinguishable refusal - which addresses hold unredeemed invitations is not for a probe to enumerate. A redemption is a sign-in for the workspace's second-factor rule: where mfa_required_for covers the invited person, the answer is 202 with the ENROLL step rather than 201 with the pair. The invitation is accepted either way - the password is set and the token spent - and the session opens only once the factor is set up with the pending credential.

Request body application/json — InvitationRedemption

NameTypeRequiredDescription
tokenstring requiredThe redemption token from the invitation, shown once and usable once.
passwordstring
  • minLength: 12
  • maxLength: 1024
requiredThe first password, under the workspace's password policy.
terms_urlstring, or null
  • maxLength: 2000
optionalThe terms of use the person agreed to on the invitation card - the terms_url of GET /auth/sign-in-rules as it was shown.
terms_versionstring, or null
  • maxLength: 64
optionalThe version of those terms as it was shown, where the terms carry one.

Responses

  • 201 Redeemed and signed in SessionTokens
  • 202 Redeemed, and the workspace demands a second factor of this person before the first session: the step is ENROLL, and email names the invited address. MfaChallenge
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X POST "$HUBTASK_URL/auth/invitations:redeem" \  -H "Content-Type: application/json" \  -d '{  "token": "string",  "password": "string",  "terms_url": "string",  "terms_version": "string"}'

POST /auth/invitations:inspect

Name the workspace and the address an invitation is for

What the invitation card says before anything is typed: the name of the workspace the person was invited to and the address the invitation went to. Answered only for a token that would redeem - an unknown, expired or accepted one is the redemption's one refusal, auth.redemption_failed - and nothing is spent: the token stays exactly as redeemable as it was. The token travels in the body, never in the address.

Request body application/json — InvitationInspection

NameTypeRequiredDescription
tokenstring requiredThe redemption token from the invitation.

Responses

Codes this operation names

Example bash
curl -X POST "$HUBTASK_URL/auth/invitations:inspect" \  -H "Content-Type: application/json" \  -d '{  "token": "string"}'

POST /auth/installation:set-up

Set up a fresh installation with the code its first start printed

The first start of an installation that has no workspace and nobody in its operator register prints a one-time setup code to the server's console, valid for thirty minutes. With it, this creates the first workspace with its defaults, its owner with the password, and the owner's entry in the operator register - in one step, recorded in the instance journal - and answers the same pair sign-in answers: the owner is signed in. In single mode the workspace's short name is not asked for and is derived from its name; in multi mode slug is required and the workspace answers at that subdomain. A wrong code is refused with setup.code_wrong, an expired one with setup.code_expired (restarting the server prints a new one). Once any workspace exists every code is spent and the call is refused with setup.already_done: the code is the only key, and after the first workspace there is nothing left for it to open. features.setup_open in /meta/capabilities says whether this is offered.

Request body application/json — InstallationSetUp

NameTypeRequiredDescription
codestring
  • maxLength: 64
requiredThe setup code the server printed at start.
display_namestring
  • minLength: 1
  • maxLength: 200
requiredThe workspace's name.
slugstring optionalMulti mode only, and required there: the subdomain the workspace answers on, 3-40 characters of lower-case letters, digits and hyphens.
owner_emailstring
  • maxLength: 320
requiredThe owner's address.
owner_display_namestring
  • maxLength: 200
optionalThe owner's name; the address stands in when it is omitted.
passwordstring
  • minLength: 12
  • maxLength: 1024
requiredThe owner's password, under the product's password policy.
default_localestring optionalBCP 47; the installation's default when omitted.
default_time_zonestring optionalAn IANA name; the installation's default when omitted.

Responses

Example bash
curl -X POST "$HUBTASK_URL/auth/installation:set-up" \  -H "Content-Type: application/json" \  -d '{  "code": "string",  "display_name": "string",  "slug": "string",  "owner_email": "string",  "owner_display_name": "string",  "password": "string",  "default_locale": "string",  "default_time_zone": "string"}'

POST /auth/sessions:verify

Present the second factor and receive the pair

The second step of a two-step sign-in: the pending credential the password answered, plus a TOTP code or one recovery code. The pending credential can do nothing but be presented here - it opens no route, reads nothing, and dies on use or after its few minutes. A code verifies within one step of drift either side and never twice: the same step presented again is refused, because a code that worked twice is a code somebody shoulder-read. A recovery code works exactly once, its consumption is audited, and the answer says how many remain. Failures count against the same attempt ledger sign-in uses. Where the password the sign-in began with no longer meets the workspace's rule - or is a start password Hubtask drew - the change step still stands after the code: the answer is then 202 with PASSWORD_CHANGE, and the pair comes from /auth/sessions:set-password.

Request body application/json — SignInCompletion

NameTypeRequiredDescription
pending_tokenstring required
codestring, or null
  • minLength: 6
  • maxLength: 8
optionalThe authenticator's current code.
recovery_codestring, or null optionalOne of the ten shown at enrolment.

Responses

  • 201 Signed in, with the pair shown for the only time SessionTokens
  • 202 The code was right and the password the sign-in began with must be changed first: the PASSWORD_CHANGE step, with the rule the new one is judged against. MfaChallenge
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X POST "$HUBTASK_URL/auth/sessions:verify" \  -H "Content-Type: application/json" \  -d '{  "pending_token": "string",  "code": "string",  "recovery_code": "string"}'

POST /auth/sessions:agree-terms

Agree to the terms of use and finish the sign-in

The TERMS step. Where terms of use are in force - a terms link set by the workspace or the installation - and the account has not agreed to the link and version in force, every sign-in ends here instead of in a session: after the password, the second factor, the change of an old password, a provider's return or a reset, whichever were owed. The step asks for nothing the account's proof would need; it comes after the whole proof. The request repeats the link and the version the person was shown. Where the terms changed while the step was open, the answer is 409 auth.terms_changed with the terms now in force as parameters, and the pending credential is not spent, so the card shows the new terms and the person agrees to those. Agreeing stores the link, the version and the moment on the account, records auth.terms_agreed in the workspace's trail with the version and the link - never the text, which Hubtask does not hold - and opens the session the sign-in would have opened, with the same method and provider. Declining needs no request: the pending credential can do nothing else and dies within five minutes. No session is opened.

Request body application/json — TermsAgreement

NameTypeRequiredDescription
pending_tokenstring requiredThe TERMS challenge's credential.
terms_urlstring
  • maxLength: 2000
requiredThe link of the terms agreed to, as the challenge showed it.
terms_versionstring, or null
  • maxLength: 64
optionalTheir version as the challenge showed it, where they carry one.

Responses

Codes this operation names

Example bash
curl -X POST "$HUBTASK_URL/auth/sessions:agree-terms" \  -H "Content-Type: application/json" \  -d '{  "pending_token": "string",  "terms_url": "string",  "terms_version": "string"}'

POST /auth/password

Change the password of the signed-in account

The password of the caller's own account, behind a step-up. The step-up is the proof of the old password, which is why there is no current-password field beside it: asking for both is asking twice for one thing (WCAG 2.2 SC 3.3.7). Every other session of the account is ended and this one is not - a person changing their password is saying the old one may be known, and the other sessions are what it opened. Personal access tokens keep working: they are their own credentials with their own expiry and their own list, and nobody mints one in a browser. A refused password answers 422 with one field_errors[] entry per rule it breaks, each carrying the auth.password_rule.* code the client predicted with - so one fact has one sentence whether the client saw it coming or the server sent it.

Headers

NameTypeRequiredDescription
X-Hubtask-Step-Upstring optionalThe proof a privileged operation demanded: the token POST /auth/step-up answered, consumed by this one call.

Request body application/json — PasswordChange

NameTypeRequiredDescription
passwordstring (password) requiredThe new password, judged against this workspace's rule.

Responses

  • 204 Changed.
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X POST "$HUBTASK_URL/auth/password" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/json" \  -d '{  "password": "string"}'

POST /auth/password:forgot

Ask for a password reset link

Answers 202 for an address that holds an account and for one that does not, byte for byte: which addresses have accounts is exactly what a probe is after. There is no error state for "no such address", because there is no such answer. Behind that one answer sits a job on the queue the invitation already uses, so an unreachable mail server never fails the request - and never becomes the difference a probe was looking for. The link is a single-use token that lives half an hour, stored only as a hash under its own purpose label, and it arrives in the URL's fragment so that nothing between the mail client and the interface sees it. An account that signs in only through its organisation's provider gets a different mail - saying so - and the same answer.

Request body application/json — PasswordForgot

NameTypeRequiredDescription
emailstring (email) requiredThe address to send the link to, if it holds an account.

Responses

  • 202 The request arrived.
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X POST "$HUBTASK_URL/auth/password:forgot" \  -H "Content-Type: application/json" \  -d '{  "email": "anna@example.org"}'

POST /auth/password:reset

Spend a reset link and set the password

The token from the reset mail, and a password under this workspace's rule. The token works once and for half an hour; unknown, spent and expired are one indistinguishable refusal - which of the three applies is not for the holder of a link they found somewhere to learn. A link mailed before the workspace switched the password off is refused afterwards with auth.password_not_offered, and the mail a request sends there points to the provider. Every session of the account ends. Somebody asking for a reset is saying the old password may be known, and the sessions are what it opened. Where the account has a second factor the answer is 202 with the challenge rather than 201 with the pair: control of a mailbox is one proof, and it does not replace the one the account already demanded.

Request body application/json — PasswordReset

NameTypeRequiredDescription
tokenstring requiredThe token from the reset mail.
passwordstring (password) requiredThe new password, judged against this workspace's rule.

Responses

  • 201 The password is set and the session is open SessionTokens
  • 202 The password is set and the account's second factor is still owed. MfaChallenge
  • 4XX An error per RFC 9457 Problem

Codes this operation names

Example bash
curl -X POST "$HUBTASK_URL/auth/password:reset" \  -H "Content-Type: application/json" \  -d '{  "token": "string",  "password": "string"}'

POST /auth/address:confirm

Confirm a mail address with the link that was sent to it

The token from the confirmation mail. Holding it proves the mailbox, and nothing else: it signs nobody in. The address becomes the account's, which is from then on an ordinary account that keeps its sign-in name. The token works once and for a day; unknown, spent, replaced and expired are one refusal, auth.address_link_invalid. An address that has meanwhile become another account's here is 409 accounts.address_taken.

Request body application/json — AddressConfirm

NameTypeRequiredDescription
tokenstring requiredThe token from the confirmation mail.

Responses

  • 204 The address is the account's
  • 4XX An error per RFC 9457 Problem

Codes this operation names

Example bash
curl -X POST "$HUBTASK_URL/auth/address:confirm" \  -H "Content-Type: application/json" \  -d '{  "token": "string"}'

POST /auth/password:check

Ask what only the server knows about a candidate password

The four rules a client cannot answer itself: the offline lists, the breach corpus, the last few passwords of this account, and whether the candidate is the one in force. The answer is rule identifiers and never sentences - the client already holds the message code for each. It demands the same proof the setting demands. A bearer for one's own account, or the token of the flow a password is being set in: an invitation, a reset, or the pending credential of a sign-in the change step interrupted. Without one the route would be an oracle that told anybody whether a word is on a blocklist or in somebody's history. 200 with a list rather than a refusal, because it is a question and not an attempt: a client asks it while somebody is still typing, and a 422 would be an error state on a form nobody has submitted. It consumes no credential, for the same reason - a question that spent the pending token would end the sign-in it was asked during. Its rate limit is its own, beside the auth bucket, because it costs Argon2 comparisons.

Headers

NameTypeRequiredDescription
X-Hubtask-Step-Upstring optionalThe proof a privileged operation demanded: the token POST /auth/step-up answered, consumed by this one call.

Request body application/json — PasswordCheck

NameTypeRequiredDescription
passwordstring (password) required
pending_tokenstring optionalThe pending credential of a sign-in the change step interrupted.
invitation_tokenstring optionalThe token from an invitation mail.
reset_tokenstring optionalThe token from a reset mail.

Responses

Example bash
curl -X POST "$HUBTASK_URL/auth/password:check" \  -H "Content-Type: application/json" \  -d '{  "password": "string",  "pending_token": "string",  "invitation_token": "string",  "reset_token": "string"}'

POST /auth/sessions:set-password

Set a new password and finish the sign-in

The fourth door a password is set through. The password was right and no longer meets the rule - too short for a tightened policy, older than the expiry, or older than the moment somebody asked everybody for a new one - so the sign-in continues by setting a new one. Confirming it is the sign-in, exactly as the enrolment step works. The pending credential can do this and nothing else, and it dies on use. Every session of the account ends: the rule refused the password that opened them. This is the whole of the enforcement. No job walks accounts and no job walks tenants - the rule is applied where the plaintext already is, which is the moment somebody uses it, so a workspace of ten and one of ten thousand cost the same.

Request body application/json — SignInPasswordChange

NameTypeRequiredDescription
pending_tokenstring requiredThe credential the PASSWORD_CHANGE challenge handed out.
passwordstring (password) requiredThe new password, judged against this workspace's rule in full.

Responses

  • 201 Set, and signed in SessionTokens
  • 202 Set; the terms of use in force are still to be agreed to (TERMS), and the session opens at /auth/sessions:agree-terms MfaChallenge
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X POST "$HUBTASK_URL/auth/sessions:set-password" \  -H "Content-Type: application/json" \  -d '{  "pending_token": "string",  "password": "string"}'

POST /auth/sessions:elevate

Raise this session to the control plane for an hour

A registered operator raises their own session to admin:tenants by passing a fresh step-up. One hour, on that session and no other, gone with it, and both the act and the moment it falls back in the installation's journal. This deliberately weakens the rule that admin:tenants is never carried by a session. It weakens it to: only for a registered operator, only after a fresh proof, only for an hour, only on the session that proved it, and written down. What it buys is that nobody has to mint a long-lived all-powerful token and paste it into a browser to change a switch - which is the outcome the strict rule produces in practice, and which is worse. It does not slide: activity extends a session's own horizon and never this, and a second hour needs a second proof. A running hour is neither replaced nor extended: raising a session that is raised already is auth.session_already_elevated (409), before any proof is asked, with the running end in params.until - so a client that lost its copy of the hour learns it. The register is read again on every request, so an operator removed while a raised session is open loses the scope on their next call rather than at the end of the hour. The personal access token stays exactly as it is, for automation.

Headers

NameTypeRequiredDescription
X-Hubtask-Step-Upstring optionalThe proof a privileged operation demanded: the token POST /auth/step-up answered, consumed by this one call.

Responses

Example bash
curl -X POST "$HUBTASK_URL/auth/sessions:elevate" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

GET /auth/sign-in-rules

What a sign-in screen may know before anybody has signed in

The four things a sign-in card needs and nothing else: which methods this workspace signs in with, which providers it offers, what a password has to meet, and the links its operator is obliged to show. Public the way sign-in is, and the workspace is resolved exactly as sign-in resolves it - from the subdomain or the X-Hubtask-Tenant header in multi mode, and in single mode there is only one. A host no workspace answers at gets the installation's own level, which is byte for byte what a workspace that has decided nothing answers: which hosts hold workspaces is exactly what a probe is after. Why a public route answers this at all: a configurable hint is otherwise wrong. A screen saying "at least twelve characters" in a workspace that demands fifteen is a screen that lies, and the password is refused after it was typed. What it deliberately does not answer is everything a guesser could use - the expiry, the history depth, the session timeouts, and the contents or sources of any list.

Responses

Example bash
curl -X GET "$HUBTASK_URL/auth/sign-in-rules"

POST /auth/mfa/totp:enroll

Begin TOTP enrolment

Mints the TOTP secret, sealed at rest through the envelope encryption every stored credential uses, and answers the provisioning URI and the ten recovery codes for the only time - the QR image is a client's job. The enrolment arms nothing yet: until a valid code confirms it, sign-in is unchanged, because an unconfirmed enrolment protects nobody and locks nobody out. Called with a bearer credential by a signed-in person, or with the pending credential a sign-in under tenant enforcement answered - the route an unenrolled administrator is routed into instead of into a session. Enrolling again before confirming replaces the unconfirmed secret; enrolling while armed is refused - disable first, with the password.

Request body application/json — TotpEnrollmentStart

NameTypeRequiredDescription
pending_tokenstring, or null optional

Responses

  • 201 The secret, the URI and the recovery codes, shown for the only time TotpEnrollment
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X POST "$HUBTASK_URL/auth/mfa/totp:enroll" \  -H "Content-Type: application/json" \  -d '{  "pending_token": "string"}'

POST /auth/mfa/totp:confirm

Confirm enrolment with a first valid code

Arms the enrolment: the caller proves the authenticator holds the secret by presenting one valid code, and from this moment sign-in is two-step. Called with a bearer credential, or with the pending credential of an enforcement sign-in - in which case a successful confirmation also answers the session pair, because the person has by now proved both factors.

Request body application/json — TotpConfirmation

NameTypeRequiredDescription
pending_tokenstring, or null optionalThe enforcement sign-in's credential; absent for a signed-in caller.
codestring
  • minLength: 6
  • maxLength: 8
required

Responses

Example bash
curl -X POST "$HUBTASK_URL/auth/mfa/totp:confirm" \  -H "Content-Type: application/json" \  -d '{  "pending_token": "string",  "code": "string"}'

POST /auth/step-up

Prove yourself again, for the irreversible

A fresh re-authentication on the current session, with whatever the account holds: the password; the TOTP code where a factor is armed; a recovery code, consumed exactly as at sign-in; or a fresh sign-in at the provider the account is connected to. The proof is recorded on the session with its moment and its method, answered as a token that is valid for a short window and consumed by the one privileged action that demanded it - deleting a tenant, changing the OWNER role, minting a token with an admin scope, a destructive restore, turning the second factor off. A second privileged action needs a second proof. The operation that found the proof missing answered 403 with auth.step_up_required and, in params.methods, the methods this account can answer with, in this order: a space-separated list drawn from PASSWORD (the account holds one), TOTP (a factor is armed), RECOVERY (a factor is armed and a recovery code is left) and PROVIDER (an identity is connected at a provider switched on for this workspace; params.provider names it). An empty list is an account with none of them. A client builds its prompt from that list, never from a guess. Exactly one method per request. PROVIDER takes two calls: POST /auth/step-up:provider answers where to send the browser, and the state and authorization_code the provider sends it back with are presented here. The proof counts only when the provider's ID token names the identity already connected to this account (auth.step_up_provider_mismatch otherwise) and carries an auth_time inside the step-up's own window (auth.step_up_provider_not_fresh otherwise - a provider that ignores max_age proves nothing fresh). This is where it goes with the answer. The token travels back in the X-Hubtask-Step-Up header - or, for the restore, in the request's step_up_token field.

Request body application/json — StepUpRequest

NameTypeRequiredDescription
passwordstring, or null
  • maxLength: 1024
optional
codestring, or null
  • minLength: 6
  • maxLength: 8
optional
recovery_codestring, or null
  • maxLength: 64
optionalOne of the account's recovery codes.
statestring, or null
  • maxLength: 512
optionalThe handle POST /auth/step-up:provider minted, as the provider echoed it.
authorization_codestring, or null
  • maxLength: 4096
optionalThe code the provider issued.

Responses

  • 201 Proved, with the token shown for the only time StepUpGrant
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X POST "$HUBTASK_URL/auth/step-up" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/json" \  -d '{  "password": "string",  "code": "string",  "recovery_code": "string",  "state": "string",  "authorization_code": "string"}'

POST /auth/step-up:provider

Begin a step-up at the provider this account is connected to

The first of PROVIDER's two calls. An authorization request at the provider this account is connected to and that is switched on for its workspace - the first in the workspace's order where there are several, the one params.provider named - with prompt=login and max_age=0, so the provider asks the person again rather than answering from its own session. The flow is bound to the session that asked: its state finishes a step-up of this session at POST /auth/step-up and nothing else, and a sign-in callback refuses it. The browser comes back to this installation's own callback address, the one every registration already permits. Refused with auth.step_up_no_provider for an account with no such provider, and with auth.step_up_session_required for a caller without a session.

Responses

Example bash
curl -X POST "$HUBTASK_URL/auth/step-up:provider" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

POST /auth/mfa/totp:replace

Begin replacing the authenticator

For a new phone, or an authenticator that is gone: a new secret, kept beside the armed one as a second, unconfirmed enrolment. Nothing changes yet - the armed factor and its recovery codes keep working until the new authenticator is confirmed, so there is never a moment without a factor, whatever the workspace's rule requires. Behind a step-up with whatever the account holds: replacing the factor is the same power as removing it. Offered while a factor is armed, also where the workspace requires one. Starting again replaces an unconfirmed replacement; one that is not confirmed within its window lapses. Bound to the session that began it: only that session confirms it. Refused with auth.mfa_not_enrolled where no factor is armed - there is nothing to replace, and setting one up is enrolment's.

Headers

NameTypeRequiredDescription
X-Hubtask-Step-Upstring optionalThe proof a privileged operation demanded: the token POST /auth/step-up answered, consumed by this one call.

Responses

Codes this operation names

Example bash
curl -X POST "$HUBTASK_URL/auth/mfa/totp:replace" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

POST /auth/mfa/totp/replacement:confirm

Confirm the new authenticator, and swap

A code from the new authenticator proves it holds the new secret. Then, in one statement, the new secret becomes the armed one and the old stops working, and ten new recovery codes replace the old ten - answered here, for the only time they are shown. Audited as auth.mfa_replaced. Only the session that began the replacement confirms it, and only inside its window; otherwise auth.mfa_replacement_unknown. A wrong code counts against the second factor's ledger and changes nothing.

Request body application/json — AuthenticatorReplacementConfirmation

NameTypeRequiredDescription
codestring
  • minLength: 6
  • maxLength: 8
requiredThe current code of the new authenticator.

Responses

  • 201 Swapped; the new recovery codes, for the only time they are shown RecoveryCodes
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X POST "$HUBTASK_URL/auth/mfa/totp/replacement:confirm" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/json" \  -d '{  "code": "string"}'

POST /auth/mfa/recovery:regenerate

Replace the ten recovery codes

Ten new ones, behind a step-up. The old set stops working in the same moment the new one is answered - one statement, because a set answered without the old one burned would be twenty live codes, and a set burned without a new one answered would lock somebody out of their own escape hatch. Shown once, exactly as at enrolment, and stored only as hashes. The codes are the account's rather than the factor's: somebody who has burned eight of ten gets ten back without taking their working second factor off for a minute. Nothing about them mentions TOTP, so they stay right whatever the second factor is.

Headers

NameTypeRequiredDescription
X-Hubtask-Step-Upstring optionalThe proof a privileged operation demanded: the token POST /auth/step-up answered, consumed by this one call.

Responses

Example bash
curl -X POST "$HUBTASK_URL/auth/mfa/recovery:regenerate" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

POST /auth/mfa:disable

Disable the second factor

Removes the TOTP enrolment and burns the remaining recovery codes. It demands a step-up like every privileged action - a stolen session removing the second factor is exactly the attack the factor exists against - and takes it with whatever the account holds: a code, a recovery code, its provider, or its password. Where the workspace's rule requires a factor of this person it cannot be disabled at all; the refusal names the rule. The body's password is the proof this route took before; it is still accepted for one release, deprecated, and goes then. A client sends the X-Hubtask-Step-Up header instead.

Headers

NameTypeRequiredDescription
X-Hubtask-Step-Upstring optionalThe proof a privileged operation demanded: the token POST /auth/step-up answered, consumed by this one call.

Request body application/json — MfaDisable

NameTypeRequiredDescription
passworddeprecatedstring
  • maxLength: 1024
optionalDeprecated: the proof this route took before the step-up did.

Responses

  • 204 Disabled
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X POST "$HUBTASK_URL/auth/mfa:disable" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/json" \  -d '{  "password": "string"}'

GET /auth/tokens

The caller's own personal access tokens

One's own tokens, newest first, with the scopes each carries, when it expires, when it was last used and whether it was revoked. Never anybody else's: a token is a credential its holder holds, and an administrator who could list one could not use it but could learn exactly which of somebody's automations to attack. The token itself is not among the fields. It is answered once, at creation, and stored only as a hash keyed on the installation secret; a token that was lost is revoked and minted again. A service account's tokens are listed here too, by naming it - which is the one exception, and needs the permission that manages members, because a service account is nothing but access and somebody has to answer for it.

Query parameters

NameTypeRequiredDescription
account_idstring (uuid) optionalWhose tokens.

Responses

  • 200 The tokens
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X GET "$HUBTASK_URL/auth/tokens" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

POST /auth/tokens

Mint a personal access token

Answers the credential, in clear, for the only time. What is stored is its hash keyed on the installation secret under its own purpose label, so a hash from here can never be replayed as a signed cursor or a calendar feed token, and nothing can turn the stored value back into the token. The expiry is mandatory and at most a year out. There is no default: a caller has to say how long the credential should live, because the alternative is a credential nobody ever revokes. The scopes are requested explicitly and are never defaulted to everything. Each has to be one the installation declares - the manifest at /meta/capabilities lists them - and a token can never do more than its holder may, whatever it asks for: the scope is a second bound on top of the role, not a grant. Asking for an admin scope is a privileged action: it demands a fresh step-up, presented in the X-Hubtask-Step-Up header.

Headers

NameTypeRequiredDescription
Idempotency-Keystring (uuid) optionalA UUID; identical requests return the same result for 24 h.
X-Hubtask-Step-Upstring optionalThe proof a privileged operation demanded: the token POST /auth/step-up answered, consumed by this one call.

Request body application/json — AccessTokenCreate

NameTypeRequiredDescription
namestring
  • minLength: 1
  • maxLength: 200
requiredWhat this token is for, in the owner's own words.
scopesarray of string requiredRequested explicitly, never defaulted to everything.
expires_atstring (date-time) requiredMandatory, in the future, and at most a year out.
account_idstring (uuid), or null optionalWhose token.

Responses

Example bash
curl -X POST "$HUBTASK_URL/auth/tokens" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Idempotency-Key: $(uuidgen)" \  -H "Content-Type: application/json" \  -d '{  "name": "string",  "scopes": [    "string"  ],  "expires_at": "2026-09-16T10:00:00Z",  "account_id": "018f6e2a-2b7c-7c1e-9b1a-3d5e6f7a8b9c"}'

DELETE /auth/tokens/{tokenId}

Revoke a personal access token

Takes effect on the next call: the hash is checked against the row on every request, so a revoked token refuses immediately rather than at the end of some cache's life. The row stays, stamped with the moment it was withdrawn, and that stamp is separate from the expiry - "it ran out" and "somebody pulled it" are different answers to "why did this stop working", and a reader of the trail is owed both. Revoking twice is not an error; somebody else's token is not found rather than forbidden.

Path parameters

NameTypeRequiredDescription
tokenIdstring (uuid) required

Responses

  • 204 Revoked
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X DELETE "$HUBTASK_URL/auth/tokens/{tokenId}" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

GET /auth/service-accounts

The workspace's service accounts

The accounts that exist only to be acted through: an integration, a script, a rule that has to keep running after the person who wrote it has left. Needs the permission that manages members, because that is the person who answers for who holds access.

Responses

  • 200 The service accounts
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X GET "$HUBTASK_URL/auth/service-accounts" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

POST /auth/service-accounts

Create a service account

Creates an account of kind SERVICE_ACCOUNT: no mail address, no sign-in anywhere, and tokens as its only credential. It holds rights the way a person does - through memberships granted to it - and it is what an automation rule runs as, so that a rule does not die with its author's departure.

Headers

NameTypeRequiredDescription
Idempotency-Keystring (uuid) optionalA UUID; identical requests return the same result for 24 h.

Request body application/json — ServiceAccountCreate

NameTypeRequiredDescription
display_namestring
  • minLength: 1
  • maxLength: 200
requiredWhat the audit trail records next to the identifier.

Responses

Example bash
curl -X POST "$HUBTASK_URL/auth/service-accounts" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Idempotency-Key: $(uuidgen)" \  -H "Content-Type: application/json" \  -d '{  "display_name": "string"}'

POST /auth/oidc:start

Begin a sign-in through the workspace's identity provider

The first half of authorization code + PKCE. The workspace is resolved the way a password sign-in resolves it - the subdomain, or the tenant header - and its configured provider answers the URL to send the browser to. Nothing about where the code comes back is taken from the request: the redirect URI is this installation's own, derived from its base URL, so a caller cannot point the authorization answer anywhere else. The state this returns is the handle for the callback; the code verifier and the nonce stay on the server and are never shown. Refused with a clear code when the workspace has no provider configured or has switched it off, and when discovery cannot be reached - a degradation in which local accounts keep signing in. A sign-in begun on the invitation card carries the invitation's token, and the flow remembers the invited account it accepts; an invitation that cannot be redeemed is refused here with auth.redemption_failed, before the browser leaves. A sign-in begun from the link a workspace without the password mails to connect its provider carries that link's token (connect_token): the flow remembers the link and asks the provider for a fresh sign-in; a link that cannot be used is refused here with auth.reset_failed, before the browser leaves.

Request body application/json — OidcStart

NameTypeRequiredDescription
provider_idstring (uuid), or null optionalWhich way in to use, from GET /auth/sign-in-rules.
login_hintstring, or null optionalAn address to pass the provider as login_hint, so somebody who typed it here does not type it again.
invitation_tokenstring, or null optionalThe redemption token of the invitation this sign-in accepts, when the person chose the provider on the invitation card.
connect_tokenstring, or null optionalThe token of the link Forgot your password?
terms_urlstring, or null
  • maxLength: 2000
optionalWith invitation_token: the terms of use the person agreed to on the invitation card, as GET /auth/sign-in-rules showed them.
terms_versionstring, or null
  • maxLength: 64
optionalThe version of those terms as it was shown, where they carry one.

Responses

Codes this operation names

Example bash
curl -X POST "$HUBTASK_URL/auth/oidc:start" \  -H "Content-Type: application/json" \  -d '{  "provider_id": "018f6e2a-2b7c-7c1e-9b1a-3d5e6f7a8b9c",  "login_hint": "string",  "invitation_token": "string",  "connect_token": "string",  "terms_url": "string",  "terms_version": "string"}'

POST /auth/oidc:callback

Finish the sign-in with the code the provider issued

The second half: the authorization code is exchanged at the provider's token endpoint with the verifier this installation kept, and the ID token is verified in full - the signature against the provider's JWKS, iss, aud, exp, the nonce this flow minted, an algorithm allowlist that never contains none, and a clock skew of at most sixty seconds. Any of those failing is one refusal. What it answers is the same pair a password sign-in answers, because it is the same session: how somebody proved themselves is an attribute of the session, not a class of it. The account is provisioned on first arrival under the provider's subject, or linked to an existing local account when the verified address matches inside the configured domains - and that linking is audited. An existing account that already holds a password is not linked on the provider's word: the answer is 202 with the single method LINK, and the account's password is presented at /auth/sessions:link. An account with no password but another provider that lets it in here cannot give that proof here and is refused with identity_provider.link_needs_own_way_in; one whose only way in has gone - an identity at an offer that ended or a provider switched off, or a second factor and nothing else - is refused with identity_provider.link_needs_mailbox, which points at Forgot your password?. An invited account is activated and connected only with a second proof: the sign-in started from the invitation's own link (invitation_token at the start), or the provider is authoritative for the address. Through the link the invited address is admitted even outside a domains or directories list. The provider's verified address must equal the invited one either way. Without the proof nothing is connected or activated and the answer is identity_provider.invitation_needs_link; a different address is identity_provider.invitation_address_differs; an invitation that ran out or was accepted meanwhile is auth.redemption_failed through the link. A refused arrival never spends the invitation. Whatever was connected to the invited account before the proof is dropped when it is activated. Authority for the address, and a domains or directories list, decide only who comes in new on the provider's word. Under Only people invited here a provider that is not authoritative, and under Only these domains/directories an address outside the list, still bring an existing active account to the LINK step - connected only with its password and its second factor - or through its connect link; neither ever creates an account, and an address nobody here holds is refused. An existing account that holds no credential at all is connected only by a provider authoritative for its address, in every mode; otherwise nothing is connected and the answer is identity_provider.link_needs_mailbox, whose sentence points at Forgot your password?. A sign-in begun from the link a workspace without the password mails (connect_token at the start) connects that link's account and no other. The mailbox and a fresh sign-in at the provider are its proof, in place of the password: an auth_time older than the step-up's window, one from before the flow left for the provider (beyond a minute's clock skew), or none, is identity_provider.connect_not_fresh; the provider's verified address must equal the account's (identity_provider.connect_address_differs); admission is that of an arrival bringing its own proof (identity_provider.not_admitted); and an identity already connected to another account here is identity_provider.connect_identity_taken. None of these spends the link. An armed second factor is still asked - the answer is then 202 with the TOTP step, and the provider is connected when that step completes. The link is spent in the transaction that connects the provider or hands on to the factor, so a second return is auth.reset_failed, as is a link whose password was switched back on meanwhile. No password is stored. A state that is unknown, already spent or expired is refused indistinguishably: a flow handle is single use.

Request body application/json — OidcCallback

NameTypeRequiredDescription
codestring requiredThe authorization code, exchanged once, at the provider's token endpoint.
statestring requiredThe handle /auth/oidc:start answered.

Responses

  • 201 Signed in SessionTokens
  • 202 The address matched an account with a password, which is asked for first - or a connection by mail reached an account with a second factor, which is asked for before the provider is connected MfaChallenge
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X POST "$HUBTASK_URL/auth/oidc:callback" \  -H "Content-Type: application/json" \  -d '{  "code": "string",  "state": "string"}'

GET /tenant/accounts-without-provider

How many people here no provider switched on here signs in

The number the password switch says before the password is switched off: the active people of this workspace who hold no identity at any provider that is a way in here now - the workspace's own switched on, the installation's taken and not past a withdrawal. Invited accounts have not chosen a way in yet and service accounts sign in with a token, so neither is counted. Where the password goes off, each of them connects a provider through Forgot your password? - or, holding a password, at the provider's first arrival with it. A number, never a list: the screen needs how many, not who. Needs the permission that manages structure, or the auditor's read-only configuration permission, as the list of providers does.

Responses

Example bash
curl -X GET "$HUBTASK_URL/tenant/accounts-without-provider" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

GET /identity-provider

The workspace's first identity provider

Kept, because nothing in this contract disappears. It was the whole of the provider surface before providers became plural, and a caller written against it keeps working: it reads the workspace's own first provider, in the order the collection lists them, and PUT writes that one - creating it where there is none. It cannot reach a second provider, it never answers one the installation offers, and it has no DELETE: removing one of several through a route that cannot name which would be a route that removes the wrong one. Use /identity-providers for all of that. Needs the permission that manages structure, or the auditor's read-only configuration permission. The client secret is never a member of the answer.

Responses

Example bash
curl -X GET "$HUBTASK_URL/identity-provider" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

PUT /identity-provider

Set the workspace's first identity provider

Sets the first provider whole, or creates it where the workspace has none. The same rules the collection applies - discovery before anything is stored, the mark held to its issuer, a public provider held to INVITED_ONLY. Auditable. The client secret is required when there is nothing to keep, and optional afterwards, as on the collection. And as there, enabled is not switched here: a created provider is off, and a changed value is refused with identity_provider.switch_in_list.

Headers

NameTypeRequiredDescription
X-Hubtask-Step-Upstring optionalThe proof a privileged operation demanded: the token POST /auth/step-up answered, consumed by this one call.

Request body application/json — IdentityProviderConfiguration

NameTypeRequiredDescription
issuerstring (uri) required
client_idstring required
client_secretstring, or null optionalSealed on the way in and never answered again.
display_namestring, or null
  • maxLength: 200
optionalThe name on the button.
kindIdentityProviderKind
  • GENERIC
  • GOOGLE
  • MICROSOFT
optionalAbsent is read from the issuer.
provisioningIdentityProviderProvisioning
  • INVITED_ONLY
  • DOMAINS
  • ANY
optionalAbsent is the safe value rather than the permissive one: INVITED_ONLY for a public provider, DOMAINS for everything else.
positioninteger
  • default: 0
  • minimum: 0
  • maximum: 99
optional
enableddeprecatedboolean optionalDeprecated: a workspace switches a provider on or off in its list of ways to sign in - POST /identity-providers/{providerId}:offer - and nowhere else.
allowed_email_domainsarray of string
  • default: []
optionalDomains this provider admits under DOMAINS, for a preset with no directory claim.
allowed_directoriesarray of string
  • default: []
optionalThe organisations this provider admits under DOMAINS, in its own identifiers: Microsoft tenant ids, Google Workspace domains.
newcomer_roleone of MembershipRole, anything, or null optionalWhat somebody this provider brings in new gets: this role on the whole workspace, granted in the same transaction that creates the account.
newcomer_group_idstring (uuid), or null optionalWhat somebody this provider brings in new gets instead of a role: membership of this group of the workspace.

Responses

Codes this operation names

Example bash
curl -X PUT "$HUBTASK_URL/identity-provider" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/json" \  -d '{  "issuer": "https://example.org",  "client_id": "string",  "client_secret": "string",  "display_name": "string",  "kind": "GENERIC",  "provisioning": "INVITED_ONLY",  "position": 0,  "enabled": true,  "allowed_email_domains": [],  "allowed_directories": [],  "newcomer_role": "OWNER",  "newcomer_group_id": "018f6e2a-2b7c-7c1e-9b1a-3d5e6f7a8b9c"}'

GET /identity-providers

The providers people can sign in to this workspace through

The workspace's own providers and the ones its installation offers every workspace, each with its issuer, the mark it draws, whom it lets in and whether it is switched on. scope says which level a row belongs to: an installation row is offered to this workspace and is not its to change. Needs the permission that manages structure, or the auditor's read-only configuration permission: which provider vouches for this workspace's people is configuration an auditor reads. The client secret is never a member of this answer. It is sealed at configuration time and read only by the token exchange.

Responses

  • 200 The providers in force
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X GET "$HUBTASK_URL/identity-providers" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

POST /identity-providers

Add a provider this workspace signs its people in through

Discovery runs before anything is stored, through the guarded client, so an issuer this installation cannot reach or that disagrees with its own metadata is refused rather than saved and discovered later by a person trying to sign in. Two refusals belong to the preset rather than to the form. A public provider - one anybody in the world holds an account at - may only be INVITED_ONLY, because anything else provisions every person alive an account here. And a preset whose addresses this installation cannot vouch for may not be INVITED_ONLY at all, because that mode hands an account that already exists to whoever arrives with its address. The secret is sealed on the way in and appears in no answer afterwards. Auditable. A new provider is created switched off - configuring is not offering. It becomes a way in when the list of ways to sign in switches it on (:offer); enabled: true here is refused with identity_provider.switch_in_list.

Headers

NameTypeRequiredDescription
X-Hubtask-Step-Upstring optionalThe proof a privileged operation demanded: the token POST /auth/step-up answered, consumed by this one call.

Request body application/json — IdentityProviderConfiguration

NameTypeRequiredDescription
issuerstring (uri) required
client_idstring required
client_secretstring, or null optionalSealed on the way in and never answered again.
display_namestring, or null
  • maxLength: 200
optionalThe name on the button.
kindIdentityProviderKind
  • GENERIC
  • GOOGLE
  • MICROSOFT
optionalAbsent is read from the issuer.
provisioningIdentityProviderProvisioning
  • INVITED_ONLY
  • DOMAINS
  • ANY
optionalAbsent is the safe value rather than the permissive one: INVITED_ONLY for a public provider, DOMAINS for everything else.
positioninteger
  • default: 0
  • minimum: 0
  • maximum: 99
optional
enableddeprecatedboolean optionalDeprecated: a workspace switches a provider on or off in its list of ways to sign in - POST /identity-providers/{providerId}:offer - and nowhere else.
allowed_email_domainsarray of string
  • default: []
optionalDomains this provider admits under DOMAINS, for a preset with no directory claim.
allowed_directoriesarray of string
  • default: []
optionalThe organisations this provider admits under DOMAINS, in its own identifiers: Microsoft tenant ids, Google Workspace domains.
newcomer_roleone of MembershipRole, anything, or null optionalWhat somebody this provider brings in new gets: this role on the whole workspace, granted in the same transaction that creates the account.
newcomer_group_idstring (uuid), or null optionalWhat somebody this provider brings in new gets instead of a role: membership of this group of the workspace.

Responses

Codes this operation names

Example bash
curl -X POST "$HUBTASK_URL/identity-providers" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/json" \  -d '{  "issuer": "https://example.org",  "client_id": "string",  "client_secret": "string",  "display_name": "string",  "kind": "GENERIC",  "provisioning": "INVITED_ONLY",  "position": 0,  "enabled": true,  "allowed_email_domains": [],  "allowed_directories": [],  "newcomer_role": "OWNER",  "newcomer_group_id": "018f6e2a-2b7c-7c1e-9b1a-3d5e6f7a8b9c"}'

PUT /identity-providers/{providerId}

Replace one of the workspace's identity providers

Set whole, not patched: a provider half-changed is a provider nobody can reason about. The client secret is the one exception - omitting it keeps the one that is sealed, which is the only way to change a name or a mode without retyping a value nothing can read back. A provider of the installation scope is answered 404 here: it is offered to this workspace and is not its to change. Auditable. Whether the provider is a way in is not set here: a changed enabled is refused with identity_provider.switch_in_list, and the list of ways to sign in switches it (:offer).

Path parameters

NameTypeRequiredDescription
providerIdstring (uuid) required

Headers

NameTypeRequiredDescription
X-Hubtask-Step-Upstring optionalThe proof a privileged operation demanded: the token POST /auth/step-up answered, consumed by this one call.

Request body application/json — IdentityProviderConfiguration

NameTypeRequiredDescription
issuerstring (uri) required
client_idstring required
client_secretstring, or null optionalSealed on the way in and never answered again.
display_namestring, or null
  • maxLength: 200
optionalThe name on the button.
kindIdentityProviderKind
  • GENERIC
  • GOOGLE
  • MICROSOFT
optionalAbsent is read from the issuer.
provisioningIdentityProviderProvisioning
  • INVITED_ONLY
  • DOMAINS
  • ANY
optionalAbsent is the safe value rather than the permissive one: INVITED_ONLY for a public provider, DOMAINS for everything else.
positioninteger
  • default: 0
  • minimum: 0
  • maximum: 99
optional
enableddeprecatedboolean optionalDeprecated: a workspace switches a provider on or off in its list of ways to sign in - POST /identity-providers/{providerId}:offer - and nowhere else.
allowed_email_domainsarray of string
  • default: []
optionalDomains this provider admits under DOMAINS, for a preset with no directory claim.
allowed_directoriesarray of string
  • default: []
optionalThe organisations this provider admits under DOMAINS, in its own identifiers: Microsoft tenant ids, Google Workspace domains.
newcomer_roleone of MembershipRole, anything, or null optionalWhat somebody this provider brings in new gets: this role on the whole workspace, granted in the same transaction that creates the account.
newcomer_group_idstring (uuid), or null optionalWhat somebody this provider brings in new gets instead of a role: membership of this group of the workspace.

Responses

Codes this operation names

Example bash
curl -X PUT "$HUBTASK_URL/identity-providers/{providerId}" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/json" \  -d '{  "issuer": "https://example.org",  "client_id": "string",  "client_secret": "string",  "display_name": "string",  "kind": "GENERIC",  "provisioning": "INVITED_ONLY",  "position": 0,  "enabled": true,  "allowed_email_domains": [],  "allowed_directories": [],  "newcomer_role": "OWNER",  "newcomer_group_id": "018f6e2a-2b7c-7c1e-9b1a-3d5e6f7a8b9c"}'

DELETE /identity-providers/{providerId}

Remove one of the workspace's identity providers

The configuration and its sealed secret go. Accounts provisioned through it keep their rows and their sessions - what they lose is the way to sign in again, which is why an account with no password is worth a thought before this call. Auditable.

Path parameters

NameTypeRequiredDescription
providerIdstring (uuid) required

Headers

NameTypeRequiredDescription
X-Hubtask-Step-Upstring optionalThe proof a privileged operation demanded: the token POST /auth/step-up answered, consumed by this one call.

Responses

  • 204 Removed
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X DELETE "$HUBTASK_URL/identity-providers/{providerId}" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

POST /identity-providers/{providerId}:offer

Switch a provider on or off as a way in here

One verb for two stores, because it is one question: is this a way into this workspace. For the workspace's own provider it is the row's enabled. For one the installation offers, it is this workspace's own switch - the row belongs to the installation and is not the workspace's to change, but whether it is offered here is: an offer of the installation reaches every workspace and is switched on in none, and each owner switches it on for their own. An installation's provider is off until a workspace switches it on. Offering every workspace a way in is the installation's decision; taking it is the workspace's. The last way in cannot be switched off: a workspace that turned off its only method would be a workspace nobody can reach. Auditable. A provider the installation's lock turns on cannot be switched off here. Where the installation locked the ways to sign in with providers among them, every provider it offers as Only people invited here is on in every workspace (installation_lock: TURNS_ON), and switching it off is refused with identity_provider.mandated, the lock's origin among its parameters. Switching it on is accepted and kept as this workspace's own choice, which applies again once the lock is lifted.

Path parameters

NameTypeRequiredDescription
providerIdstring (uuid) required

Headers

NameTypeRequiredDescription
X-Hubtask-Step-Upstring optionalThe proof a privileged operation demanded: the token POST /auth/step-up answered, consumed by this one call.

Request body application/json — ProviderOffer

NameTypeRequiredDescription
offeredboolean requiredWhether this provider is a way into this workspace.

Responses

Codes this operation names

Example bash
curl -X POST "$HUBTASK_URL/identity-providers/{providerId}:offer" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/json" \  -d '{  "offered": true}'

GET /identity-provider-presets

The providers Hubtask has a preset for, and what registering takes

Three rows and this installation's own callback address, which is the value every registration form at every provider asks for. instructions and particular are message codes rendered with redirect_uri; provisioning is the modes the preset permits, so a screen offers only what would be accepted rather than three of which two are refused. Behind the same permission as reading the providers. There is no reason for a signed-out visitor to read registration instructions.

Responses

  • 200 The presets
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X GET "$HUBTASK_URL/identity-provider-presets" \  -H "Authorization: Bearer $HUBTASK_TOKEN"