Skip to content

GET /admin/tenants

The installation's workspaces

The one legitimate tenant enumerator: provisioning and lifecycle are the control plane's job, and the control plane must see its rows. It reads through a deliberate installation-scoped path behind the admin:tenants scope - which no session carries: the credential is a personal access token minted for exactly this, behind a step-up. "Nothing enumerates tenants" remains the rule for jobs; this is not a job.

Responses

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

POST /admin/tenants

Provision a workspace

Creates the tenant with its defaults: a default hub, an example collection, standard buckets and labels, the locale and time zone, and the owner - an invited account whose redemption token is answered once, so the person the workspace is for can set their password and sign in. Idempotent under Idempotency-Key: provisioning twice under one key creates one.

Headers

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

Request body application/json — TenantProvision

NameTypeRequiredDescription
slugstring
  • pattern: ^[a-z0-9][a-z0-9-]{2,39}$
requiredThe subdomain the workspace answers on.
display_namestring
  • minLength: 1
  • maxLength: 200
required
default_localestring, or null optional
default_time_zonestring, or null optional
owner_emailstring (email)
  • maxLength: 254
required
owner_display_namestring, or null
  • maxLength: 200
optional

Responses

  • 201 Provisioned, with the owner's redemption token shown for the only time ProvisionedTenant
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X POST "$HUBTASK_URL/admin/tenants" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Idempotency-Key: $(uuidgen)" \  -H "Content-Type: application/json" \  -d '{  "slug": "string",  "display_name": "string",  "default_locale": "string",  "default_time_zone": "string",  "owner_email": "anna@example.org",  "owner_display_name": "string"}'

POST /admin/tenants/{tenantId}:suspend

Suspend a workspace

Flips the middleware: every API call of the tenant's own people answers 403 tenant_suspended on their very next request. The data remains, and the read export still works - the suspended are exactly who needs it. Refused with admin.multi_mode_required in single mode, where the one workspace is the installation.

Path parameters

NameTypeRequiredDescription
tenantIdstring (uuid) required

Responses

  • 204 Suspended
  • 4XX An error per RFC 9457 Problem

Codes this operation names

Example bash
curl -X POST "$HUBTASK_URL/admin/tenants/{tenantId}:suspend" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

POST /admin/tenants/{tenantId}:resume

Reactivate a workspace

One write. The next request of the tenant's people works again.

Path parameters

NameTypeRequiredDescription
tenantIdstring (uuid) required

Responses

  • 204 Active again
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X POST "$HUBTASK_URL/admin/tenants/{tenantId}:resume" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

POST /admin/tenants/{tenantId}:cancel-deletion

Call off a workspace's pending deletion

Returns a workspace pending deletion to where it stood when the deletion was requested - active or suspended; suspended where that is not known - and clears the deadline. The automation rules the request switched off are switched back on, a schedule from now on, without the occurrences it missed. Possible until the hard delete begins, also past the deadline while a legal hold keeps the workspace; refused with admin.tenant_deletion_underway once it has begun (purge_started_at on the listing). Cancelling a workspace that is not pending changes nothing. Legal holds stay as they are.

Path parameters

NameTypeRequiredDescription
tenantIdstring (uuid) required

Responses

  • 204 The deletion is called off
  • 4XX An error per RFC 9457 Problem

Codes this operation names

Example bash
curl -X POST "$HUBTASK_URL/admin/tenants/{tenantId}:cancel-deletion" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

POST /admin/tenants/{tenantId}:delete

Request a workspace's deletion

The most irreversible act this API has, and it behaves like it: it demands a fresh step-up in the X-Hubtask-Step-Up header and the workspace's display name, typed - the restore precedent. Refused with admin.multi_mode_required in single mode, where the one workspace is the installation. The tenant moves to PENDING_DELETION: access blocked, automations disabled, the export still answering. Until the hard delete begins, :cancel-deletion calls it off. The hard delete runs after the 30-day grace as a job seeded by this very write - it cascades across database rows, media bytes, search, outbox and queue, and leaves an installation-level evidence entry, because the tenant's own trail dies with it.

Path parameters

NameTypeRequiredDescription
tenantIdstring (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 — TenantDeletionRequest

NameTypeRequiredDescription
confirmationstring requiredThe workspace's display name, typed exactly - the restore precedent.

Responses

Codes this operation names

Example bash
curl -X POST "$HUBTASK_URL/admin/tenants/{tenantId}:delete" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/json" \  -d '{  "confirmation": "string"}'

POST /admin/tenants/{tenantId}:open-password

Open the password for one workspace

For a provider that is switched on but broken - unreachable, or admitting nobody - the operator opens the password for this one workspace, for a limited time: 24 hours unless hours says otherwise, at most 168. Every account there that holds a password signs in with it, under the workspace's own rules and second factor, whatever the workspace's own switch and any installation lock say; an account without one is mailed a link to set one when it asks (POST /auth/password:forgot). It ends on its own at password_opening.until, and :close-password ends it early. Demands admin:tenants, the operator register and a fresh step-up in the X-Hubtask-Step-Up header. The requester and the reason are recorded with the end in the workspace's trail (tenant.password_opened) and in the installation's journal, and the workspace's administrators are told. A second opening replaces the first. It reads and changes nothing of the workspace's content. Only an active workspace is opened: a suspended one refuses its people before any password is asked for (admin.password_opening_suspended), and one pending deletion is leaving (admin.tenant_leaving).

Path parameters

NameTypeRequiredDescription
tenantIdstring (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 — PasswordOpeningRequest

NameTypeRequiredDescription
hoursinteger
  • default: 24
  • minimum: 1
  • maximum: 168
optionalHow long the opening stands, counted from now.
requesterstring
  • minLength: 1
  • maxLength: 200
requiredWho asked for the opening.
reasonstring
  • minLength: 1
  • maxLength: 500
requiredWhy the password is opened - what is wrong with the provider.

Responses

  • 200 Opened; the workspace, with the opening in force AdminTenant
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X POST "$HUBTASK_URL/admin/tenants/{tenantId}:open-password" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/json" \  -d '{  "hours": 24,  "requester": "string",  "reason": "string"}'

POST /admin/tenants/{tenantId}:close-password

Close an operator's opening of the password early

Ends the opening :open-password made before its time. The workspace's own rules decide again from the next request on. Recorded in the workspace's trail (tenant.password_closed) and the installation's journal, and the workspace's administrators are told. No step-up: closing narrows the way in rather than widening it. Closing where nothing is open changes nothing and records nothing.

Path parameters

NameTypeRequiredDescription
tenantIdstring (uuid) required

Responses

  • 200 Closed, or there was nothing to close; the workspace AdminTenant
  • 4XX An error per RFC 9457 Problem

Codes this operation names

Example bash
curl -X POST "$HUBTASK_URL/admin/tenants/{tenantId}:close-password" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

POST /admin/tenants/{tenantId}:replace-legal-holds

Place the legal holds of a rewound period again

After a point-in-time recovery of the installation, before traffic is admitted, the operator places again the legal holds the rewind removed from this workspace. Each hold keeps its identifier, scope, reason, placer and moment; one released in the rewound period is placed in force all the same, and its owner releases it again. A hold the workspace already has is left as it is, so a second run changes nothing. A hold whose target the recovery removed is placed and reported. Demands admin:tenants. Each hold placed writes lifecycle.hold_replaced into the workspace's trail.

Path parameters

NameTypeRequiredDescription
tenantIdstring (uuid) required

Request body application/json — LegalHoldReplaceRequest

NameTypeRequiredDescription
recovery_pointstring (date-time) requiredThe moment the installation was recovered to; recorded with each hold placed.
holdsarray of LegalHoldRecord
  • maxItems: 1000
optional
idstring (uuid) required
scopeobject required
kindstring
  • TENANT
  • CONTAINER
  • ITEM
  • ACCOUNT
required
idstring (uuid), or null optional
reasonstring required
placed_bystring (uuid) required
placed_atstring (date-time) required
released_atstring (date-time), or null optionalWhen it was released in the rewound period, if it was.
archiveobject optionalA workspace archive at one of the workspace's backup targets.
target_idstring (uuid) required
pathstring requiredThe archive's path at the target, as the listing of the target's backups answers it.

Responses

Codes this operation names

Example bash
curl -X POST "$HUBTASK_URL/admin/tenants/{tenantId}:replace-legal-holds" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/json" \  -d '{  "recovery_point": "2026-09-16T10:00:00Z",  "holds": [    {      "id": "018f6e2a-2b7c-7c1e-9b1a-3d5e6f7a8b9c",      "scope": {},      "reason": "string",      "placed_by": "018f6e2a-2b7c-7c1e-9b1a-3d5e6f7a8b9c",      "placed_at": "2026-09-16T10:00:00Z"    }  ],  "archive": {    "target_id": "018f6e2a-2b7c-7c1e-9b1a-3d5e6f7a8b9c",    "path": "string"  }}'

POST /admin/tenants/{tenantId}:export

Export a workspace whole

A complete, documented archive of one workspace - JSON Lines per entity, media by reference with checksums, a manifest naming counts and the product version - written as a job to a configured backup target. It is the Hubtask archive format, documented well enough to build an importer against the documentation alone: the basis for access requests, provider migration, and the "no lock-in" promise. It works for ACTIVE, SUSPENDED and PENDING_DELETION workspaces alike - the suspended and the leaving are exactly who needs it - and it is audited with its target, never its content. The export runs through the same row-level-security path as the API, and the answer is a JobRef; the archive itself is at the target, so there is no result_url.

Path parameters

NameTypeRequiredDescription
tenantIdstring (uuid) required

Request body application/json — TenantExportRequest

NameTypeRequiredDescription
target_idstring (uuid) requiredThe configured backup target the archive is written to - the one discipline for "bytes leave the installation".

Responses

  • 202 The export job was accepted JobRef
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X POST "$HUBTASK_URL/admin/tenants/{tenantId}:export" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/json" \  -d '{  "target_id": "018f6e2a-2b7c-7c1e-9b1a-3d5e6f7a8b9c"}'

GET /admin/settings

What this installation has decided for every workspace on it

The level above the workspaces: the sign-in switches this installation set, which of them are locked, the operator's legal links, and the path to its own list of refused passwords. Only what was decided. A level that answered the product's defaults for everything else would be one nobody could tell apart from an operator who had chosen them - and "the operator decided nothing here" is a value the resolver acts on. Behind admin:tenants and the operator register, both checked: the scope says what a credential may reach and the register says whose credential it may be, and either alone is a hole.

Responses

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

PUT /admin/settings

Replace the installation's level

A PUT rather than a merge: a merge over eighteen switches that can each be absent has no way to say "unset this one", and being able to say it is the whole point of a level that distinguishes a decision from a default. A locked switch applies to every workspace and switches that workspace's control off - with the reason and with who set it, rather than hiding it. A setting that simply is not there is a setting somebody opens a support ticket about. One journal entry, naming the switches that moved and never their values: a journal carrying a blocklist's path or a legal URL would be configuration written into a place nothing ever deletes from.

Request body application/json — InstanceSettings

NameTypeRequiredDescription
sign_inobject optionalThe switches by name - the thirteen the password has, mfa_required_for, methods, the two session bounds, managed_accounts and private_hubs.
legalobject optionalThe four links, by name, and terms_version, the version of the terms link.
localisationobject optionallocale, time_zone and week_start: what a new workspace starts with, and, for the week start, what a workspace that set none of its own inherits.
quotasobject optionalOne ceiling per quota, by the quota's own name — the middle level of Effective(product, instance, plan, workspace).
aiobject optionalWhich AI a workspace may use.
blocklist_filestring optionalThe path to the operator's own list of refused passwords, read offline.
sourcestring optionalWhere the values in force came from - the database, or the path of the file enforcing them.
is_enforced_from_fileboolean optionalWhether a file is the source.

Responses

Example bash
curl -X PUT "$HUBTASK_URL/admin/settings" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/json" \  -d '{  "sign_in": {},  "legal": {},  "localisation": {},  "quotas": {},  "ai": {},  "blocklist_file": "string",  "source": "string",  "is_enforced_from_file": true}'

GET /admin/operators

Who operates this installation

The operator register. An empty one is the private installation: nothing was configured, one workspace, and its owner is the operator exactly as they were before the register existed. Each row names its account - a name, the address or sign-in name, the kind and the workspace - so that nobody has to read an identifier. Only accounts in the register are ever named; a workspace's other accounts are not listed anywhere at this level.

Responses

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

POST /admin/operators

Put an account in the register

A service account may be an operator: a purchase platform that provisions workspaces needs a credential that does not belong to a person who may leave, and the first day of a platform is the day that becomes true. An account that is already an operator is not an error - the caller asked for somebody to be one, and they are. Only an active account can be added (admin.operator_not_active). Adding the first entry adds the caller too. On an empty register the only workspace's owner is the operator, and the first row ends that; so where the register was empty, the caller is entered in the same statement, and both entries are in the journal.

Request body application/json — OperatorAdd

NameTypeRequiredDescription
account_idstring (uuid) optional
workspacestring optionalThe workspace's address, with email.
emailstring (email) optionalThe address the account signs in with, inside workspace.

Responses

  • 204 In the register
  • 4XX An error per RFC 9457 Problem

Codes this operation names

Example bash
curl -X POST "$HUBTASK_URL/admin/operators" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/json" \  -d '{  "account_id": "018f6e2a-2b7c-7c1e-9b1a-3d5e6f7a8b9c",  "workspace": "string",  "email": "anna@example.org"}'

DELETE /admin/operators/{accountId}

Take an account out of the register

The last operator cannot be removed: an installation with no operators is an installation nobody can operate. The refusal is in the statement rather than in a read, because two operators removing each other at the same moment would both read "there are two". The account alone identifies the row - an identifier is unique across the installation, and a caller that had to name the workspace too would have to read the register to find out which one it is. An account that is not in the register is admin.operator_not_registered (404); the last one is admin.operator_last (409).

Path parameters

NameTypeRequiredDescription
accountIdstring (uuid) requiredAn account identifier, always a UUID.

Responses

  • 204 Out of the register
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X DELETE "$HUBTASK_URL/admin/operators/{accountId}" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

GET /admin/overview

How big this installation is and how its workspaces stand

The dashboard's first screen: the workspaces by state, and how many accounts they hold between them. Counts and states, never rows. The tenant boundary is a database policy rather than a role, and this answer does not go around it - what it reads is a function that can answer five integers and nothing else. What an operator needs to run an installation is counts, states and limits; the health of the machinery is GET /meta/health, which is where it already lives. Behind admin:tenants and the operator register, both.

Responses

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

GET /admin/journal

The installation's own record

What was provisioned, suspended, resumed and deleted, and what the operator changed - newest first. Evidence of acts whose per-tenant trail cannot hold them: after a hard delete the workspace's own audit chain is gone by design, which is the reason this record exists. Identifiers, slugs, counts and moments. Never anybody's content, which is what makes it readable at all after the workspace it names is gone.

Query parameters

NameTypeRequiredDescription
cursorstring optional
sizeinteger
  • default: 50
  • minimum: 1
  • maximum: 200
optional

Responses

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

GET /admin/ai-providers

The AI models this installation offers every workspace

A workspace sees only each model's name, processor and jurisdiction, and switches one on itself - offering a model switches nothing on. Behind admin:tenants and the operator register, both. The key is not among the fields.

Responses

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

POST /admin/ai-providers

Offer an AI model to every workspace

The same rules as a workspace's own model, plus a name and a processor every workspace is shown before it agrees. A model outside the EEA is refused with ai.third_country_not_confirmed unless the installation confirmed such transfers. Journalled in the installation's own evidence.

Request body application/json — OfferedAiModelConfiguration

NameTypeRequiredDescription
display_namestring
  • maxLength: 200
requiredThe name workspaces choose it by.
processorstring
  • maxLength: 200
requiredWho processes what is sent to it, as the operator declares it.
kindAiProviderKind
  • NOOP
  • OPENAI_COMPATIBLE
  • OLLAMA
requiredWhich adapter answers.
base_urlstring (uri) required
completion_modelstring optional
embedding_modelstring optional
api_keystring optionalSealed on the way in and answered by nothing afterwards.
jurisdictionAiJurisdiction
  • SELF_HOSTED
  • EEA
  • ADEQUACY
  • THIRD_COUNTRY
requiredWhere the provider processes what is sent to it, as the operator declares it.

Responses

Codes this operation names

Example bash
curl -X POST "$HUBTASK_URL/admin/ai-providers" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/json" \  -d '{  "display_name": "string",  "processor": "string",  "kind": "NOOP",  "base_url": "https://example.org",  "completion_model": "string",  "embedding_model": "string",  "api_key": "string",  "jurisdiction": "SELF_HOSTED"}'

PUT /admin/ai-providers/{providerId}

Change an offered AI model

Set whole, with the key's one exception: omitting it keeps the one that is sealed, an empty string clears it. Journalled.

Path parameters

NameTypeRequiredDescription
providerIdstring (uuid) required

Request body application/json — OfferedAiModelConfiguration

NameTypeRequiredDescription
display_namestring
  • maxLength: 200
requiredThe name workspaces choose it by.
processorstring
  • maxLength: 200
requiredWho processes what is sent to it, as the operator declares it.
kindAiProviderKind
  • NOOP
  • OPENAI_COMPATIBLE
  • OLLAMA
requiredWhich adapter answers.
base_urlstring (uri) required
completion_modelstring optional
embedding_modelstring optional
api_keystring optionalSealed on the way in and answered by nothing afterwards.
jurisdictionAiJurisdiction
  • SELF_HOSTED
  • EEA
  • ADEQUACY
  • THIRD_COUNTRY
requiredWhere the provider processes what is sent to it, as the operator declares it.

Responses

Example bash
curl -X PUT "$HUBTASK_URL/admin/ai-providers/{providerId}" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/json" \  -d '{  "display_name": "string",  "processor": "string",  "kind": "NOOP",  "base_url": "https://example.org",  "completion_model": "string",  "embedding_model": "string",  "api_key": "string",  "jurisdiction": "SELF_HOSTED"}'

DELETE /admin/ai-providers/{providerId}

Withdraw an offered AI model

Removes the model and its sealed key. Every workspace that chose it gets no AI answer until it chooses again; nothing a workspace configured itself is touched. Journalled.

Path parameters

NameTypeRequiredDescription
providerIdstring (uuid) required

Responses

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

GET /admin/identity-providers

The providers this installation offers every workspace

Every workspace on this installation reads these and draws their buttons; none may change them, which is the row-level policy and not a check in a handler. Behind admin:tenants and the operator register, both.

Responses

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

POST /admin/identity-providers

Offer every workspace a way in

The same rules as a workspace's own provider - discovery before anything is stored, the mark held to the issuer it belongs to, a public provider held to INVITED_ONLY - because it is the same use case underneath. Journalled in the installation's own evidence, where no workspace's trail could hold 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 — 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

Example bash
curl -X POST "$HUBTASK_URL/admin/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 /admin/identity-providers/{providerId}

Replace one of the installation's providers

Set whole, with the secret's one exception: omitting it keeps the one that is sealed. Journalled. Whether the installation offers the provider is not changed here: an offer ends through :withdraw, announced for a date or confirmed with the count, and is kept with :cancel-withdrawal. A changed enabled is refused with identity_provider.withdraw_instead; the same value, or none, changes nothing.

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/admin/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 /admin/identity-providers/{providerId}

Remove a provider whose offer has ended

Removes the installation's provider and its sealed secret - once its offer has ended (:withdraw, its day reached) or where no workspace uses it. While the offer stands and a workspace uses it, removal is refused with identity_provider.withdraw_first, its count the number of workspaces - or, where a withdrawal is already announced, with identity_provider.remove_after_withdrawal, its date the moment the offer ends. An offer ends announced, and Withdraw now is the answer to a compromised provider. The connections between people and the provider are deleted and are not restored by offering it again; the accounts keep their rows and their live sessions. Journalled.

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/admin/identity-providers/{providerId}" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

POST /admin/identity-providers/{providerId}:withdraw

Announce the withdrawal of an offered provider, or withdraw it now

A withdrawal is announced for a date - fourteen days ahead where none is given - and until then the provider keeps working everywhere while every workspace that has it switched on tells its administrators when it ends; it can be cancelled. On the date the offer ends: off everywhere, the connected identities left in place. A date that is now or past is Withdraw now, the answer to a compromised provider: it additionally asks for confirm_count, the number of workspaces that have it switched on as the operator just read it - refused with identity_provider.withdraw_count_mismatch when it does not match. A date less than a day ahead is no notice either, and asks for the count the same way. A workspace left with no way in afterwards falls back to the password for the accounts that hold one. An offer that has already ended is not withdrawn again - refused with identity_provider.already_withdrawn, so a call cannot revive it; it is offered again only through :cancel-withdrawal. Journalled.

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 — ProviderWithdrawal

NameTypeRequiredDescription
withdraw_atstring (date-time), or null optionalWhen the offer ends.
confirm_countinteger, or null
  • minimum: 0
optionalRequired for Withdraw now: the number of workspaces that have the provider switched on, as the operator read it before confirming.

Responses

Example bash
curl -X POST "$HUBTASK_URL/admin/identity-providers/{providerId}:withdraw" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/json" \  -d '{  "withdraw_at": "2026-09-16T10:00:00Z",  "confirm_count": 0}'

POST /admin/identity-providers/{providerId}:cancel-withdrawal

Keep offering a provider whose withdrawal was announced

Clears the withdrawal. Before the date nothing changes for anybody; after it, the offer is back and the workspaces that had it switched on sign in through it again, because the connected identities were never removed. Journalled.

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

Example bash
curl -X POST "$HUBTASK_URL/admin/identity-providers/{providerId}:cancel-withdrawal" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

GET /admin/encryption

The keyring, and what still names each key

What a rotation needs before it can end: the master key new values are sealed under, and for every key the ring holds - and every key a stored value still names - how many values name it. Removing a key while a row still names it turns that row into a refusal, so the count is the gate rather than the calendar. Installation-scoped, behind the admin:tenants scope, and it names no workspace: a count per key and kind is what an operator needs, and which workspace holds a webhook secret is not. The installation's own secrets - its sign-in providers' and its offered AI models' - are counted once, beside the workspaces'.

Responses

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

POST /admin/encryption:reseal

Re-seal what older keys still hold

Rewraps every stored value that names a key other than the current one, so that the older key can finally leave the ring. The plaintext is untouched: the data key is unwrapped under the key its row names and wrapped again under the current one, and nothing above the port can tell the difference. One job per workspace rather than one sweep over the installation - nothing enumerates tenants for its own purposes, and this is an operator asking rather than a schedule - and one more for the installation's own sign-in providers and offered AI models. The call returns once the work is queued; progress is read back from GET /admin/encryption until every count but the active key's is zero. Asking twice before the first round has run queues nothing new.

Responses

Example bash
curl -X POST "$HUBTASK_URL/admin/encryption:reseal" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

PATCH /admin/tenants/{tenantId}/quotas

Set a workspace's quotas

The operator's write: each provided key becomes the workspace's ceiling, an explicit null clears the override back to the mode's default, and absent keys stay untouched. 0 means unlimited. Audited - a quota decides what a workspace may hold, and "who moved the wall" is a question with an answer.

Path parameters

NameTypeRequiredDescription
tenantIdstring (uuid) required

Request body application/merge-patch+json — TenantQuotas

NameTypeRequiredDescription
api_requests_per_minuteinteger (int64), or null
  • minimum: 0
optional
itemsinteger (int64), or null
  • minimum: 0
optional
media_bytesinteger (int64), or null
  • minimum: 0
optional
automation_runs_per_hourinteger (int64), or null
  • minimum: 0
optional
webhook_targetsinteger (int64), or null
  • minimum: 0
optional
export_jobsinteger (int64), or null
  • minimum: 0
optional
ai_tokens_per_dayinteger (int64), or null
  • minimum: 0
optionalWhat the workspace may spend on the AI models the installation offers in a day of its own time zone, counted in the tokens every provider reports.

Responses

  • 200 The workspace's quotas after the write, resolved
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X PATCH "$HUBTASK_URL/admin/tenants/{tenantId}/quotas" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/merge-patch+json" \  -d '{  "api_requests_per_minute": 0,  "items": 0,  "media_bytes": 0,  "automation_runs_per_hour": 0,  "webhook_targets": 0,  "export_jobs": 0,  "ai_tokens_per_day": 0}'