Browse documentation

Platform administration

Current release · Updated October 4, 2026

DryKraft Platform administration reference: methods, permissions, request fields, response shapes, examples and current limits. Read authentication and error handling first.

GET/api/sa/resellers/{id}/api-keys

List reseller API keys

Access: Platform owner, administrator or read-only account · Success: 200

Safe key metadata only; no raw token or token digest. Includes expired/revoked keys. Customer membership, support and billing platform roles cannot inspect these keys.

Query parameters

ParameterTypeRequiredDetails
cursoruuidNo

No request body is required.

Response

FieldTypeRequiredDetails
itemsobject[]No Max 25 items.
next_cursorstring or nullNo
available_scopesreseller.customers.read | reseller.plans.read | reseller.usage.read[]No Min 1 items. Max 3 items.
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001/api-keys' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
GET/api/sa/resellers/{id}/api-keys/creations/{requestId}

Recover own saved reseller key setup

Access: Platform owner or administrator · Success: 200

Current platform owner/admin and original creator only, within the named reseller. Returns current safe metadata even after suspension, expiry or revocation. secret is always null; the generated token is never recoverable. Unknown or other-actor/parent requests return404. No query parameters.

No request body is required.

Response

FieldTypeRequiredDetails
keyobjectYes
secretnullYes
replayedtrueYes
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001/api-keys/creations/00000000-0000-4000-8000-000000000001' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
GET/api/sa/resellers/{id}/api-keys/{keyId}

Read reseller API key metadata

Access: Platform owner, administrator or read-only account · Success: 200

Exact key within the named reseller only, including expired or revoked keys and unavailable contracts. Safe metadata only; no token, digest, request hash or secret. Unknown or other-parent key IDs return404. No query parameters.

No request body is required.

Response

FieldTypeRequiredDetails
iduuidYes
reseller_iduuidYes
namestringYesKey name Max 100 characters.
prefixstringYesNonsecret dk_res_ prefix
scopesreseller.customers.read | reseller.plans.read | reseller.usage.read[]Yes Min 1 items. Max 3 items.
expires_atdate-timeYesISO instant with explicit UTC offset.
revoked_atstring or nullYes
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
created_byuuidYes
created_atdate-timeYesISO instant with explicit UTC offset.
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001/api-keys/00000000-0000-4000-8000-000000000001' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
POST/api/sa/resellers/{id}/api-keys

Issue reseller API key

Access: Platform owner or administrator · Success: 201

An approved reseller with a current contract is required for a new key. Token is shown once and stored only as a digest. Scope and expiry are immutable; expiry must be future and within 90 days. Same actor/request_id/input retry returns200, existing metadata and secret=null, including after suspension/revocation. Changed input conflicts. Never creates a customer service identity.

JSON request

FieldTypeRequiredDetails
namestringYesKey name Max 100 characters.
scopesreseller.customers.read | reseller.plans.read | reseller.usage.read[]Yes Min 1 items. Max 3 items.
expires_atdate-timeYesISO instant with explicit UTC offset.
request_iduuidYes
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

FieldTypeRequiredDetails
keyobjectYes
secretstring or nullYesdk_res_ followed by 64 lowercase hexadecimal characters. Present on first successful creation only.
replayedbooleanYesIdentical saved request
Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001/api-keys' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "<Key name>",
  "scopes": [
    "reseller.customers.read"
  ],
  "expires_at": "<ISO instant with explicit UTC offset>",
  "request_id": "00000000-0000-4000-8000-000000000001",
  "reason": "<Required reason recorded in immutable platform audit>"
}'
PUT/api/sa/resellers/{id}/api-keys/{keyId}

Revoke reseller API key

Access: Platform owner or administrator · Success: 200

Current optimistic version and reason are required. Revocation retains immutable metadata and completed request audit. It works for inactive/expired contracts too. Concurrent requests already holding the key read lock may finish before revocation commits; subsequent requests fail. No unrevocation or scope editing.

JSON request

FieldTypeRequiredDetails
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

FieldTypeRequiredDetails
iduuidYes
reseller_iduuidYes
namestringYesKey name Max 100 characters.
prefixstringYesNonsecret dk_res_ prefix
scopesreseller.customers.read | reseller.plans.read | reseller.usage.read[]Yes Min 1 items. Max 3 items.
expires_atdate-timeYesISO instant with explicit UTC offset.
revoked_atstring or nullYes
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
created_byuuidYes
created_atdate-timeYesISO instant with explicit UTC offset.
Request shape (illustrative)
curl --request PUT \
  --url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001/api-keys/00000000-0000-4000-8000-000000000001' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "version": 1,
  "reason": "<Required reason recorded in immutable platform audit>"
}'
GET/api/sa/resellers/{id}/members

List reseller staff team

Access: Platform owner, administrator or read-only account · Success: 200

Safe staff identity/membership metadata only, 25 rows per UUID cursor. No password hashes, MFA factor, recovery codes, other organization memberships or customer records.

Query parameters

ParameterTypeRequiredDetails
cursoruuidNo

No request body is required.

Response

FieldTypeRequiredDetails
itemsobject[]Yes Max 25 items.
next_cursorstring or nullYes
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001/members' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
GET/api/sa/resellers/{id}/members/{memberId}

Read one reseller staff member

Access: Platform owner, administrator or read-only account · Success: 200

Exact organization membership lookup for the detail page, independent of directory pagination. Wrong parent or missing member returns404. Safe identity/MFA readiness only; no credential hashes, factors or other memberships.

No request body is required.

Response

FieldTypeRequiredDetails
iduuidYes
account_iduuidYes
namestringYesStaff name
emailemailYes Max 200 characters.
roleowner | admin | support | billingYesOrganization membership role, separate from platform and customer roles
disabledbooleanYesMembership disabled
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
mfa_enabledbooleanYesAuthenticator enrolled
account_disabledbooleanNoGlobal staff account disabled; directory reads only
created_atdate-timeNoISO instant with explicit UTC offset.
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001/members/00000000-0000-4000-8000-000000000001' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
GET/api/sa/resellers/{id}/members/creations/{requestId}

Recover own saved reseller member creation

Access: Platform owner or administrator · Success: 200

Only the original currently authorized platform owner/admin may recover their membership creation receipt for this exact organization, including pending organizations. Other actors, parents and missing receipts return404. No password or authenticator data is returned.

No request body is required.

Response

FieldTypeRequiredDetails
iduuidYes
account_iduuidYes
namestringYesStaff name
emailemailYes Max 200 characters.
roleowner | admin | support | billingYesOrganization membership role, separate from platform and customer roles
disabledbooleanYesMembership disabled
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
mfa_enabledbooleanYesAuthenticator enrolled
account_disabledbooleanNoGlobal staff account disabled; directory reads only
created_atdate-timeNoISO instant with explicit UTC offset.
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001/members/creations/00000000-0000-4000-8000-000000000001' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
POST/api/sa/resellers/{id}/members

Create reseller team membership

Access: Platform owner or administrator · Success: 201

Reasoned creation with an explicit new/existing identity mode. Same actor/request_id/normalized input returns200 and the saved result, never changes credentials on retry. Changed request input conflicts. Pending organizations can receive their first owner; this does not activate their contract. Required MFA enrollment belongs to the new human staff member.

JSON request

object or object. Explicitly create a separate staff identity or attach an existing reseller identity without changing its credentials. The first enabled team member must be an owner. This creates no customer membership and sends no invitation email.

Response

FieldTypeRequiredDetails
iduuidYes
account_iduuidYes
namestringYesStaff name
emailemailYes Max 200 characters.
roleowner | admin | support | billingYesOrganization membership role, separate from platform and customer roles
disabledbooleanYesMembership disabled
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
mfa_enabledbooleanYesAuthenticator enrolled
account_disabledbooleanNoGlobal staff account disabled; directory reads only
created_atdate-timeNoISO instant with explicit UTC offset.
Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001/members' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "email": "person@example.com",
  "role": "owner",
  "request_id": "00000000-0000-4000-8000-000000000001",
  "reason": "<Required reason recorded in immutable platform audit>",
  "mode": "new_account",
  "name": "<Name>",
  "initial_password": "<Private password>"
}'
PUT/api/sa/resellers/{id}/members/{memberId}

Change reseller team role or access

Access: Platform owner or administrator · Success: 200

Current membership version and reason required. Keep at least one enabled organization owner. A change revokes this account’s reseller sessions and pending sign-ins across all its organizations, retaining memberships and audit. Unchanged values preserve version/access. It changes no customer membership.

JSON request

FieldTypeRequiredDetails
roleowner | admin | support | billingYesOrganization membership role, separate from platform and customer roles
disabledbooleanYesDisable organization membership without deletion
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

FieldTypeRequiredDetails
iduuidYes
account_iduuidYes
namestringYesStaff name
emailemailYes Max 200 characters.
roleowner | admin | support | billingYesOrganization membership role, separate from platform and customer roles
disabledbooleanYesMembership disabled
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
mfa_enabledbooleanYesAuthenticator enrolled
account_disabledbooleanNoGlobal staff account disabled; directory reads only
created_atdate-timeNoISO instant with explicit UTC offset.
Request shape (illustrative)
curl --request PUT \
  --url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001/members/00000000-0000-4000-8000-000000000001' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "role": "owner",
  "disabled": false,
  "version": 1,
  "reason": "<Required reason recorded in immutable platform audit>"
}'
GET/api/sa/resellers

List reseller organizations

Access: Platform owner, administrator, billing or read-only account · Success: 200

Returns up to25 reseller commercial metadata rows ordered by UUID. Literal name/key search, status filter and cursor pagination. No reseller identity, customer record access or provider collection is granted by an organization relationship.

Query parameters

ParameterTypeRequiredDetails
cursoruuidNo
searchstringNoLiteral name/key search Max 100 characters.
statusall | pending | active | suspendedNo

No request body is required.

Response

FieldTypeRequiredDetails
itemsobject[]Yes Max 25 items.
next_cursorstring or nullYes
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/resellers' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
GET/api/sa/resellers/{id}

Read reseller grants and aggregate resources

Access: Platform owner, administrator, billing or read-only account · Success: 200

Returns saved organization contract/status/policy, its current wholesale grant, customer count and eight aggregate live-resource gauges. Counts expose no underlying business records. collection_configured=false distinguishes catalog metadata from payment collection.

No request body is required.

Response

object. Organization metadata, status, optimistic version, current grant_version, current immutable grant, customer_count, eight aggregate live-resource usage gauges and collection_configured=false. No customer records, secrets or provider collection are exposed.

Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
POST/api/sa/resellers

Create a pending reseller

Access: Platform owner or administrator · Success: 201

Creates a pending organization and immutable wholesale grant version1 with platform audit. Explicit contract dates and customer access policy required. Wholesale limits/features may only reduce the selected platform plan version. Approval is a separate reasoned update; this does not create login credentials, provision customers or collect payments.

JSON request

FieldTypeRequiredDetails
keystringYes Pattern: ^[a-z][a-z0-9-]{2,49}$.
namestringYesOrganization name Max 100 characters.
customer_access_policyretain_access | suspend_accessYesExplicit customer behavior while reseller is pending/suspended or its contract is outside its date window. Never deletes customer data.
contract_referencestringYesCommercial contract reference; not a credential Max 200 characters.
billing_modewholesale | managed_rebillingYesContract metadata only. Provider collection, wallets, settlement and managed rebilling are not configured by this API.
contract_starts_atdate-timeYes
contract_ends_atdate-timeYesMust follow contract_starts_at
grantResellerGrantDefinitionYes
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

object. Organization metadata, status, optimistic version, current grant_version, current immutable grant, customer_count, eight aggregate live-resource usage gauges and collection_configured=false. No customer records, secrets or provider collection are exposed.

Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/resellers' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "key": "<value>",
  "name": "<Organization name>",
  "customer_access_policy": "retain_access",
  "contract_reference": "<Commercial contract reference; not a credential>",
  "billing_mode": "wholesale",
  "contract_starts_at": "<value>",
  "contract_ends_at": "<Must follow contract_starts_at>",
  "grant": {
    "platform_plan_id": "00000000-0000-4000-8000-000000000001",
    "platform_plan_version": 1,
    "max_workspaces": "<Maximum live resources; null means no ceiling at this level>",
    "trial_days_max": 0,
    "limits": {
      "users": "<Maximum live resources; null means no ceiling at this level>",
      "contacts": "<Maximum live resources; null means no ceiling at this level>",
      "custom_records": "<Maximum live resources; null means no ceiling at this level>",
      "pipelines": "<Maximum live resources; null means no ceiling at this level>",
      "workflows": "<Maximum live resources; null means no ceiling at this level>",
      "active_workflows": "<Maximum live resources; null means no ceiling at this level>",
      "integrations": "<Maximum live resources; null means no ceiling at this level>",
      "calendars": "<Maximum live resources; null means no ceiling at this level>"
    },
    "aggregate_limits": {
      "users": "<Maximum live resources; null means no ceiling at this level>",
      "contacts": "<Maximum live resources; null means no ceiling at this level>",
      "custom_records": "<Maximum live resources; null means no ceiling at this level>",
      "pipelines": "<Maximum live resources; null means no ceiling at this level>",
      "workflows": "<Maximum live resources; null means no ceiling at this level>",
      "active_workflows": "<Maximum live resources; null means no ceiling at this level>",
      "integrations": "<Maximum live resources; null means no ceiling at this level>",
      "calendars": "<Maximum live resources; null means no ceiling at this level>"
    },
    "features": {
      "crm": false,
      "sales": false,
      "finance": false,
      "automation": false,
      "outreach": false,
      "calendar": false
    }
  },
  "reason": "<Required reason recorded in immutable platform audit>"
}'
PUT/api/sa/resellers/{id}

Approve or update reseller lifecycle policy

Access: Platform owner or administrator · Success: 200

Requires the current organization version and reason. Serializes against customer transfers and guarded resource growth. Approval, suspension, policy and contract edits preserve customer data. retain_access preserves customer operation; suspend_access also suspends linked customer access while status/contract is not active. Emits attributed reseller organization lifecycle events through the existing shared outbox. Contract mode is metadata; no automatic provider charge.

JSON request

FieldTypeRequiredDetails
namestringYesOrganization name Max 100 characters.
customer_access_policyretain_access | suspend_accessYesExplicit customer behavior while reseller is pending/suspended or its contract is outside its date window. Never deletes customer data.
contract_referencestringYesCommercial contract reference; not a credential Max 200 characters.
billing_modewholesale | managed_rebillingYesContract metadata only. Provider collection, wallets, settlement and managed rebilling are not configured by this API.
contract_starts_atdate-timeYes
contract_ends_atdate-timeYesMust follow contract_starts_at
statuspending | active | suspendedYes
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

object. Organization metadata, status, optimistic version, current grant_version, current immutable grant, customer_count, eight aggregate live-resource usage gauges and collection_configured=false. No customer records, secrets or provider collection are exposed.

Request shape (illustrative)
curl --request PUT \
  --url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "<Organization name>",
  "customer_access_policy": "retain_access",
  "contract_reference": "<Commercial contract reference; not a credential>",
  "billing_mode": "wholesale",
  "contract_starts_at": "<value>",
  "contract_ends_at": "<Must follow contract_starts_at>",
  "status": "pending",
  "version": 1,
  "reason": "<Required reason recorded in immutable platform audit>"
}'
GET/api/sa/resellers/{id}/grants

Read immutable wholesale grant history

Access: Platform owner, administrator, billing or read-only account · Success: 200

Returns25 immutable grant versions newest first, current_version and next_before. before selects older history. Existing retail versions remain pinned; current wholesale reductions clamp effective workspace allowances without removing existing resources.

Query parameters

ParameterTypeRequiredDetails
beforeintegerNoPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.

No request body is required.

Response

object.

Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001/grants' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
POST/api/sa/resellers/{id}/grants

Publish a wholesale grant

Access: Platform owner, administrator or billing account · Success: 201

Requires current grant version and reason. Publishes an immutable validated reduction of the selected platform plan, updating the reseller current grant pointer. Resource growth is serialized against publication; shared hard ceilings and maximum customer count apply to subsequent growth. Existing records are retained. Emits reseller.customer.plan_changed for linked customers with reseller/tenant/schema metadata.

JSON request

FieldTypeRequiredDetails
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
definitionResellerGrantDefinitionYes
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

object. Organization metadata, status, optimistic version, current grant_version, current immutable grant, customer_count, eight aggregate live-resource usage gauges and collection_configured=false. No customer records, secrets or provider collection are exposed.

Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001/grants' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "version": 1,
  "definition": {
    "platform_plan_id": "00000000-0000-4000-8000-000000000001",
    "platform_plan_version": 1,
    "max_workspaces": "<Maximum live resources; null means no ceiling at this level>",
    "trial_days_max": 0,
    "limits": {
      "users": "<Maximum live resources; null means no ceiling at this level>",
      "contacts": "<Maximum live resources; null means no ceiling at this level>",
      "custom_records": "<Maximum live resources; null means no ceiling at this level>",
      "pipelines": "<Maximum live resources; null means no ceiling at this level>",
      "workflows": "<Maximum live resources; null means no ceiling at this level>",
      "active_workflows": "<Maximum live resources; null means no ceiling at this level>",
      "integrations": "<Maximum live resources; null means no ceiling at this level>",
      "calendars": "<Maximum live resources; null means no ceiling at this level>"
    },
    "aggregate_limits": {
      "users": "<Maximum live resources; null means no ceiling at this level>",
      "contacts": "<Maximum live resources; null means no ceiling at this level>",
      "custom_records": "<Maximum live resources; null means no ceiling at this level>",
      "pipelines": "<Maximum live resources; null means no ceiling at this level>",
      "workflows": "<Maximum live resources; null means no ceiling at this level>",
      "active_workflows": "<Maximum live resources; null means no ceiling at this level>",
      "integrations": "<Maximum live resources; null means no ceiling at this level>",
      "calendars": "<Maximum live resources; null means no ceiling at this level>"
    },
    "features": {
      "crm": false,
      "sales": false,
      "finance": false,
      "automation": false,
      "outreach": false,
      "calendar": false
    }
  },
  "reason": "<Required reason recorded in immutable platform audit>"
}'
GET/api/sa/resellers/{id}/plans

List reseller retail packages

Access: Platform owner, administrator, billing or read-only account · Success: 200

Returns25 current retail plan headers with name/currency/monthly/annual/setup catalog prices and trial days, ordered by UUID. These prices do not change DryKraft provider subscriptions or customer billing records.

Query parameters

ParameterTypeRequiredDetails
cursoruuidNo

No request body is required.

Response

FieldTypeRequiredDetails
itemsobject[]Yes Max 25 items.
next_cursorstring or nullYes
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001/plans' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
GET/api/sa/resellers/{id}/plans/{planId}

Read retail package versions

Access: Platform owner, administrator, billing or read-only account · Success: 200

Returns the reseller-owned plan header,25 immutable versions newest first and next_before. Each version records its publication wholesale grant. Customer assignment stays pinned when a new version is published.

Query parameters

ParameterTypeRequiredDetails
beforeintegerNoPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.

No request body is required.

Response

object.

Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001/plans/00000000-0000-4000-8000-000000000001' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
POST/api/sa/resellers/{id}/plans

Create a bounded retail package

Access: Platform owner, administrator or billing account · Success: 201

Approved reseller with current contract required. Creates immutable retail version1 with audit. Eight limits and six capabilities may only reduce current wholesale allowances; trial_days may not exceed trial_days_max. Supported currency and monthly/annual/setup minor-unit catalog prices are required. No provider product, payment or invoice is created.

JSON request

FieldTypeRequiredDetails
keystringYes Pattern: ^[a-z][a-z0-9-]{2,49}$.
definitionResellerRetailDefinitionYes
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

object.

Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001/plans' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "key": "<value>",
  "definition": {
    "name": "<Retail version name>",
    "currency": "<Supported ISO currency>",
    "monthly_price_minor": 0,
    "annual_price_minor": 0,
    "setup_price_minor": 0,
    "trial_days": 0,
    "limits": {
      "users": "<Maximum live resources; null means no ceiling at this level>",
      "contacts": "<Maximum live resources; null means no ceiling at this level>",
      "custom_records": "<Maximum live resources; null means no ceiling at this level>",
      "pipelines": "<Maximum live resources; null means no ceiling at this level>",
      "workflows": "<Maximum live resources; null means no ceiling at this level>",
      "active_workflows": "<Maximum live resources; null means no ceiling at this level>",
      "integrations": "<Maximum live resources; null means no ceiling at this level>",
      "calendars": "<Maximum live resources; null means no ceiling at this level>"
    },
    "features": {
      "crm": false,
      "sales": false,
      "finance": false,
      "automation": false,
      "outreach": false,
      "calendar": false
    }
  },
  "reason": "<Required reason recorded in immutable platform audit>"
}'
POST/api/sa/resellers/{id}/plans/{planId}/versions

Publish another retail package version

Access: Platform owner, administrator or billing account · Success: 201

Requires the current retail version, approved reseller/current contract and reason. Concurrent publication conflicts instead of overwriting. Immutable earlier prices remain pinned to customer assignments. Limits/features/trial are validated against the current wholesale grant; no automatic customer reassignment or provider charge occurs.

JSON request

FieldTypeRequiredDetails
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
definitionResellerRetailDefinitionYes
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

object.

Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001/plans/00000000-0000-4000-8000-000000000001/versions' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "version": 1,
  "definition": {
    "name": "<Retail version name>",
    "currency": "<Supported ISO currency>",
    "monthly_price_minor": 0,
    "annual_price_minor": 0,
    "setup_price_minor": 0,
    "trial_days": 0,
    "limits": {
      "users": "<Maximum live resources; null means no ceiling at this level>",
      "contacts": "<Maximum live resources; null means no ceiling at this level>",
      "custom_records": "<Maximum live resources; null means no ceiling at this level>",
      "pipelines": "<Maximum live resources; null means no ceiling at this level>",
      "workflows": "<Maximum live resources; null means no ceiling at this level>",
      "active_workflows": "<Maximum live resources; null means no ceiling at this level>",
      "integrations": "<Maximum live resources; null means no ceiling at this level>",
      "calendars": "<Maximum live resources; null means no ceiling at this level>"
    },
    "features": {
      "crm": false,
      "sales": false,
      "finance": false,
      "automation": false,
      "outreach": false,
      "calendar": false
    }
  },
  "reason": "<Required reason recorded in immutable platform audit>"
}'
GET/api/sa/resellers/{id}/customers

List linked customer workspaces

Access: Platform owner, administrator, billing or read-only account · Success: 200

Returns25 linked workspace IDs/names/slugs, assignment versions and pinned retail version metadata ordered by workspace UUID. No customer business records, user secrets or support access are returned.

Query parameters

ParameterTypeRequiredDetails
cursoruuidNo

No request body is required.

Response

FieldTypeRequiredDetails
itemsobject[]Yes Max 25 items.
next_cursorstring or nullYes
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001/customers' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
GET/api/sa/workspaces/{id}/reseller

Read workspace commercial parent

Access: Platform owner, administrator, billing or read-only account · Success: 200

Returns current reseller/retail assignment metadata and version0 for never-assigned direct workspaces. Detaching retains its advanced version to prevent stale transfer replay. Organization ownership gives no workspace membership or CRM access.

No request body is required.

Response

object. tenant_id, reseller_id/retail_plan_id/retail_plan_version (nullable together), optimistic version, updater/time and optional reseller_name. A direct workspace never assigned returns version0; detached workspaces retain their advanced version.

Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/workspaces/00000000-0000-4000-8000-000000000001/reseller' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
PUT/api/sa/workspaces/{id}/reseller

Assign or transfer a customer commercial parent

Access: Platform owner or administrator · Success: 200

Requires current assignment version and reason. Select a reseller plus its exact retail version, or clear all three for direct management. Parent locks are sorted before the workspace lock; current contract, plan ownership, wholesale ceilings, customer count and aggregate resource capacity are checked atomically. Transfers preserve records/memberships/provider billing. Emits attributed reseller.customer.transferred or reseller.customer.plan_changed. This endpoint does not perform owner provisioning, snapshot application or approval-based support access.

JSON request

FieldTypeRequiredDetails
versionintegerYesCurrent assignment version;0 only when never assigned Min 0.
reseller_idstring or nullYes
retail_plan_idstring or nullYes
retail_plan_versioninteger or nullYes Min 1.
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

object. tenant_id, reseller_id/retail_plan_id/retail_plan_version (nullable together), optimistic version, updater/time and optional reseller_name. A direct workspace never assigned returns version0; detached workspaces retain their advanced version.

Request shape (illustrative)
curl --request PUT \
  --url 'https://app.drykraft.com/api/sa/workspaces/00000000-0000-4000-8000-000000000001/reseller' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "version": 0,
  "reseller_id": "00000000-0000-4000-8000-000000000001",
  "retail_plan_id": "00000000-0000-4000-8000-000000000001",
  "retail_plan_version": "<value>",
  "reason": "<Required reason recorded in immutable platform audit>"
}'
GET/api/sa/plans

List versioned plans

Access: Platform owner, administrator, billing or read-only account · Success: 200

Returns up to25 plans ordered by UUID, with current version name, kind and price metadata. Literal key/name search, cursor pagination. Includes enforced gauge and capability keys. Catalog publication never changes already assigned versions or charges the provider.

Query parameters

ParameterTypeRequiredDetails
searchstringNoLiteral name/email search, default blank Max 100 characters.
cursoruuidNo

No request body is required.

Response

FieldTypeRequiredDetails
itemsobject[]No Max 25 items.
next_cursorstring or nullNo
metersstring[]No
featuresstring[]No
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/plans' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
GET/api/sa/plans/{id}

Read plan version history

Access: Platform owner, administrator, billing or read-only account · Success: 200

Returns plan header, up to25 immutable versions newest first and next_before. Use before for older history. Existing workspace assignments retain their pinned version until an explicit reassignment.

Query parameters

ParameterTypeRequiredDetails
beforeintegerNoPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.

No request body is required.

Response

FieldTypeRequiredDetails
iduuidNo
keystringNoPlan key
current_versionintegerNoPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
versionsobject[]No Max 25 items.
next_beforeinteger or nullNo
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/plans/00000000-0000-4000-8000-000000000001' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
POST/api/sa/plans

Create a plan

Access: Platform owner, administrator or billing account · Success: 201

Creates version1 with a required reason and immutable audit. All eight supported live gauges and six capabilities are required. Unimplemented consumed meters are rejected. Workspace price metadata stays USD1/user/month or USD12/user/year; enterprise has no fabricated price or payment state.

JSON request

FieldTypeRequiredDetails
keystringYes Pattern: ^[a-z][a-z0-9-]{2,49}$.
definitionPlatformPlanDefinitionYes
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

object.

Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/plans' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "key": "<value>",
  "definition": {
    "name": "<Plan name>",
    "kind": "workspace",
    "limits": {
      "users": "<Maximum live resources; null means no ceiling at this level>",
      "contacts": "<Maximum live resources; null means no ceiling at this level>",
      "custom_records": "<Maximum live resources; null means no ceiling at this level>",
      "pipelines": "<Maximum live resources; null means no ceiling at this level>",
      "workflows": "<Maximum live resources; null means no ceiling at this level>",
      "active_workflows": "<Maximum live resources; null means no ceiling at this level>",
      "integrations": "<Maximum live resources; null means no ceiling at this level>",
      "calendars": "<Maximum live resources; null means no ceiling at this level>"
    },
    "features": {
      "crm": false,
      "sales": false,
      "finance": false,
      "automation": false,
      "outreach": false,
      "calendar": false
    }
  },
  "reason": "<Required reason recorded in immutable platform audit>"
}'
POST/api/sa/plans/{id}/versions

Publish another plan version

Access: Platform owner, administrator or billing account · Success: 201

Requires the current header version and reason. Concurrent publication yields one new version and a409 for the stale publisher. Earlier definitions cannot be edited/deleted; workspace assignments are not automatically advanced. This does not update a Dodo product or subscription.

JSON request

FieldTypeRequiredDetails
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
definitionPlatformPlanDefinitionYes
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

object.

Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/plans/00000000-0000-4000-8000-000000000001/versions' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "version": 1,
  "definition": {
    "name": "<Plan name>",
    "kind": "workspace",
    "limits": {
      "users": "<Maximum live resources; null means no ceiling at this level>",
      "contacts": "<Maximum live resources; null means no ceiling at this level>",
      "custom_records": "<Maximum live resources; null means no ceiling at this level>",
      "pipelines": "<Maximum live resources; null means no ceiling at this level>",
      "workflows": "<Maximum live resources; null means no ceiling at this level>",
      "active_workflows": "<Maximum live resources; null means no ceiling at this level>",
      "integrations": "<Maximum live resources; null means no ceiling at this level>",
      "calendars": "<Maximum live resources; null means no ceiling at this level>"
    },
    "features": {
      "crm": false,
      "sales": false,
      "finance": false,
      "automation": false,
      "outreach": false,
      "calendar": false
    }
  },
  "reason": "<Required reason recorded in immutable platform audit>"
}'
GET/api/sa/workspaces/{id}/entitlements

Read effective workspace entitlements

Access: Platform owner, administrator, billing or read-only account · Success: 200

Returns pinned definition, reducing overrides, agreement/trial metadata and live-resource counts only. Contacts count unmerged People; custom_records count records outside the seven built-in objects. Integrations count saved workflow credentials plus enabled outreach/calendar connections/customer webhooks. Calendars count enabled calendars; users count memberships.

No request body is required.

Response

Use WorkspaceEntitlements.

Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/workspaces/00000000-0000-4000-8000-000000000001/entitlements' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
PUT/api/sa/workspaces/{id}/entitlements

Assign a plan or enterprise agreement

Access: Platform owner, administrator or billing account · Success: 200

Requires current entitlement version and reason. Locks against resource growth and trial/plan edits. Overrides can only reduce plan ceilings/capabilities; a null override cannot remove a finite ceiling. Existing resources remain after a lower ceiling; further growth is blocked. Only enterprise versions accept bounded agreements. Current agreement dates and seats can grant write access, without fabricating provider subscription/payment state. Emits billing.entitlements.updated.

JSON request

FieldTypeRequiredDetails
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
plan_iduuidYes
plan_versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
overridesobjectYes
agreementobject or nullYes
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

Use WorkspaceEntitlements.

Request shape (illustrative)
curl --request PUT \
  --url 'https://app.drykraft.com/api/sa/workspaces/00000000-0000-4000-8000-000000000001/entitlements' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "version": 1,
  "plan_id": "00000000-0000-4000-8000-000000000001",
  "plan_version": 1,
  "overrides": {
    "limits": {},
    "features": {}
  },
  "agreement": {
    "reference": "<Agreement reference>",
    "starts_at": "<value>",
    "ends_at": "<After start and within the next366 days; expiry removes agreement access>",
    "seats": 1
  },
  "reason": "<Required reason recorded in immutable platform audit>"
}'
POST/api/sa/workspaces/{id}/trial

Extend workspace trial

Access: Platform owner, administrator or billing account · Success: 200

Extends the saved trial end to a later timestamp within 90 days of now. Current entitlement version and reason required; no shortening or silent retry overwrite. Advances entitlement version and emits billing.trial.extended with immutable audit. It does not charge or change a provider subscription.

JSON request

FieldTypeRequiredDetails
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
ends_atdate-timeYes
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

Use WorkspaceEntitlements.

Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/workspaces/00000000-0000-4000-8000-000000000001/trial' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "version": 1,
  "ends_at": "<value>",
  "reason": "<Required reason recorded in immutable platform audit>"
}'
POST/api/sa/login

Start platform sign-in

Access: Public · Success: 200

Central app host only. Separate account password verification returns a five-minute challenge, never a session. Mandatory authenticator enrollment is requested for new accounts. Shared rate protection counts failed attempts.

JSON request

FieldTypeRequiredDetails
emailemailYes Max 200 characters.
passwordstringYesCurrent platform password Max 128 characters.

Response

FieldTypeRequiredDetails
challengestringNo Pattern: ^[a-f0-9]{64}$.
mfa_requiredbooleanNoVerification required
enrollment_requiredbooleanNoEnrollment required
expires_in300No
Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/login' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "email": "person@example.com",
  "password": "<Current platform password>"
}'
POST/api/sa/mfa/setup

Read pending platform authenticator setup

Access: Public · Success: 200

Requires an unexpired password-verified platform challenge for an account without MFA. Returns the private setup key and QR once per saved challenge state; repeated calls reuse that state. Never share or log this response.

JSON request

FieldTypeRequiredDetails
challengestringYes Pattern: ^[a-f0-9]{64}$.

Response

FieldTypeRequiredDetails
secretstringNoPrivate base32 setup key
qrstringNoPrivate data:image/png QR
expires_atstringNoChallenge expiry
Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/mfa/setup' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "challenge": "<value>"
}'
POST/api/sa/mfa

Complete platform sign-in

Access: Public · Success: 200

Consumes the challenge and a fresh six-digit authenticator or unused recovery code. Enrollment returns ten private one-use recovery codes. Sets drykraft_sa_session: host-only, Secure in production, HttpOnly, SameSite=Strict, path=/api/sa, four-hour maximum and15-minute idle expiry. Up to10 attempts per challenge.

JSON request

FieldTypeRequiredDetails
challengestringYes Pattern: ^[a-f0-9]{64}$.
codestringYesAuthenticator or recovery code Max 40 characters.

Response

FieldTypeRequiredDetails
oktrueNo
recovery_codesstring[]No Min 10 items. Max 10 items.
Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/mfa' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "challenge": "<value>",
  "code": "<Authenticator or recovery code>"
}'
POST/api/sa/logout

End platform session

Access: Public · Success: 200

Revokes the platform cookie session and clears its cookie. Safe when already signed out. Customer workspace sessions are separate.

No request body is required.

Response

Use OK.

Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/logout' \
  --header 'Origin: https://app.drykraft.com'
GET/api/sa/session

Read platform session

Access: Platform account · Success: 200

Rechecks current account role, status, authentication version and session expiry. Successful platform requests renew idle activity, without extending the four-hour maximum.

No request body is required.

Response

FieldTypeRequiredDetails
accountobjectNo
expires_atstringNoFixed session expiry
idle_timeout_seconds900No
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/session' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
POST/api/sa/password

Change platform password

Access: Platform account · Success: 200

Requires current password plus a fresh authenticator or unused recovery proof. New password differs and has12-128 characters. Revokes other sessions/challenges and replaces the current session; this does not change a customer account.

JSON request

FieldTypeRequiredDetails
current_passwordstringYesCurrent password Max 128 characters.
new_passwordstringYesPrivate password Max 128 characters.
codestringYesAuthenticator or recovery proof Max 40 characters.

Response

Use OK.

Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/password' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "current_password": "<Current password>",
  "new_password": "<Private password>",
  "code": "<Authenticator or recovery proof>"
}'
GET/api/sa/overview

Read platform totals

Access: Platform account · Success: 200

Returns safe workspace/user/subscription counts. Trial counts exclude active subscriptions. These are management totals, not aggregate customer business records.

No request body is required.

Response

FieldTypeRequiredDetails
workspacesintegerNoTotal workspaces
suspended_workspacesintegerNoSuspended workspaces
usersintegerNoRegistered customer users
active_subscriptionsintegerNoCurrent active subscriptions
trial_workspacesintegerNoUnexpired trial workspaces without active subscription
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/overview' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
GET/api/sa/workspaces

List platform workspaces

Access: Platform account · Success: 200

Searches literal name/address text and returns25 newest workspaces per cursor page, member counts and saved trial/billing status. Status filter defaults to all.

Query parameters

ParameterTypeRequiredDetails
searchstringNoLiteral name/email search, default blank Max 100 characters.
cursoruuidNo
statusall | active | suspendedNoDefault all

No request body is required.

Response

FieldTypeRequiredDetails
itemsobject[]Yes Max 25 items.
next_cursorstring or nullYes
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/workspaces' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
GET/api/sa/workspaces/{id}

Read workspace management details

Access: Platform account · Success: 200

Management detail shows saved access status, up to25 members and safe billing metadata. The view is recorded in platform audit. No business records or credentials are exposed.

No request body is required.

Response

object. id, name, slug, created_at, suspended (platform control), access_suspended (effective platform/reseller policy), version, suspension_reason, updated_at and member count. Detail includes up to25 members_preview(id/name/email/role) and billing(status/seats/trial_started_at/trial_ends_at/next_billing_date/cancel_at_next_billing_date/synced_at). No customer records or provider credentials.

Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/workspaces/00000000-0000-4000-8000-000000000001' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
POST/api/sa/workspaces/{id}/state

Suspend or restore workspace

Access: Platform owner or administrator · Success: 200

Current version and reason required. Suspension preserves stored data and blocks shared workspace access/billing checks. Restoration still requires current memberships and billing access. Matching current state has no new event; stale version returns409. Does not charge or refund.

JSON request

FieldTypeRequiredDetails
suspendedbooleanYesTrue suspends; false restores
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

object. id, name, slug, created_at, suspended (platform control), access_suspended (effective platform/reseller policy), version, suspension_reason, updated_at and member count. Detail includes up to25 members_preview(id/name/email/role) and billing(status/seats/trial_started_at/trial_ends_at/next_billing_date/cancel_at_next_billing_date/synced_at). No customer records or provider credentials.

Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/workspaces/00000000-0000-4000-8000-000000000001/state' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "suspended": false,
  "version": 1,
  "reason": "<Required reason recorded in immutable platform audit>"
}'
POST/api/sa/workspaces

Provision workspace and owner invitation

Access: Platform owner or administrator · Success: 201

Atomically initializes standard objects, sales pipeline, dashboard, settings and seven-day owner invitation; starts15-day trial. No user or membership is created. Keep request_id and all validated input identical on retries: same actor receives the saved result with200; changed input/actor conflicts. Invitation token is private and is null after acceptance/revocation/expiry. No email is sent.

JSON request

FieldTypeRequiredDetails
request_iduuidYes
namestringYesWorkspace name Max 100 characters.
slugstringYesUnoccupied and not reserved Pattern: ^[a-z][a-z0-9-]{2,39}$.
owner_emailemailYes Max 200 characters.
currencystringYesSupported currency code, e.g. USD, INR, EUR or KWD. Default USD.
currenciesstring[]Yes Min 1 items. Max 180 items.
timezonestringYesValid named IANA timezone Max 80 characters.
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

FieldTypeRequiredDetails
iduuidNo
invitation_iduuidNo
owner_emailemailNo Max 200 characters.
expires_atstringNoInvitation expiry
owner_joinedbooleanNoInvitation accepted
invitation_tokenstring or nullNoPrivate owner invitation token; valid pending invitation only
reusedbooleanNoSaved operation replay
Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/workspaces' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "request_id": "00000000-0000-4000-8000-000000000001",
  "name": "<Workspace name>",
  "slug": "<Unoccupied and not reserved>",
  "owner_email": "person@example.com",
  "currency": "<Supported currency code>",
  "currencies": [
    "<Supported currency code>"
  ],
  "timezone": "<Valid named IANA timezone>",
  "reason": "<Required reason recorded in immutable platform audit>"
}'
GET/api/sa/workspaces/{id}/owner-invitation

Read owner invitation status

Access: Platform account · Success: 200

Returns latest owner invitation metadata or null. Does not return an invitation token, create access, dispatch email or refresh expiry.

No request body is required.

Response

object or null. id, email, version, issued_by, expires_at, accepted_at, revoked_at, created_at, can_access_link and can_renew; null if no platform owner invitation

Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/workspaces/00000000-0000-4000-8000-000000000001/owner-invitation' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
POST/api/sa/workspaces/{id}/owner-invitation/link

Reveal current private owner invitation

Access: Platform owner or administrator · Success: 200

Requires invitation ID/current version/reason. Only the issuing administrator can view a valid pending invitation for an unsuspended workspace without an owner. Other administrators must renew under their own identity. This audited response contains a private bearer token; never log it or place it in query parameters.

JSON request

FieldTypeRequiredDetails
invitation_iduuidYes
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

FieldTypeRequiredDetails
invitation_tokenstringNo Pattern: ^[a-f0-9]{64}$.
versionintegerNoPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
expires_atstringNoTimestamp
Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/workspaces/00000000-0000-4000-8000-000000000001/owner-invitation/link' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "invitation_id": "00000000-0000-4000-8000-000000000001",
  "version": 1,
  "reason": "<Required reason recorded in immutable platform audit>"
}'
POST/api/sa/workspaces/{id}/owner-invitation/renew

Renew an owner invitation

Access: Platform owner or administrator · Success: 200

Atomically replaces the token, sets seven-day expiry and current issuer, advances its version and cancels queued old-version mail. Accepted invitations or workspaces with an owner/suspension cannot renew. Old links become unusable. Identical request_id/body/actor replay returns saved metadata; changed details conflict. No token is persisted in the replay receipt and no email is sent.

JSON request

FieldTypeRequiredDetails
request_iduuidYes
invitation_iduuidYes
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

FieldTypeRequiredDetails
invitation_iduuidNo
versionintegerNoPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
expires_atstringNoTimestamp
revoked_atstring or nullNo
reusedbooleanNoAn identical operation replay; no further mutation
Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/workspaces/00000000-0000-4000-8000-000000000001/owner-invitation/renew' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "request_id": "00000000-0000-4000-8000-000000000001",
  "invitation_id": "00000000-0000-4000-8000-000000000001",
  "version": 1,
  "reason": "<Required reason recorded in immutable platform audit>"
}'
POST/api/sa/workspaces/{id}/owner-invitation/revoke

Revoke an owner invitation

Access: Platform owner or administrator · Success: 200

Revokes a pending invitation with current version and required reason, and cancels queued mail. Accepted invitations require separate ownership management. An in-flight message may arrive but cannot grant access through the revoked link. Identical operation replay adds no change or audit entry; changed details conflict.

JSON request

FieldTypeRequiredDetails
request_iduuidYes
invitation_iduuidYes
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

FieldTypeRequiredDetails
invitation_iduuidNo
versionintegerNoPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
expires_atstringNoTimestamp
revoked_atstring or nullNo
reusedbooleanNoAn identical operation replay; no further mutation
Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/workspaces/00000000-0000-4000-8000-000000000001/owner-invitation/revoke' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "request_id": "00000000-0000-4000-8000-000000000001",
  "invitation_id": "00000000-0000-4000-8000-000000000001",
  "version": 1,
  "reason": "<Required reason recorded in immutable platform audit>"
}'
POST/api/sa/workspaces/{id}/owner-invitation/email-preview

Review current private invitation email

Access: Platform owner or administrator · Success: 200

Current issuer only, with invitation ID/version/reason. Returns the actual recipient, sender, plain-text content and reviewed template/transport versions. Contains a private invitation link. Audited without storing its body/token; does not queue or send. Include both reviewed versions and preview_hash when queuing; changed content/template/connection conflicts.

JSON request

FieldTypeRequiredDetails
invitation_iduuidYes
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

FieldTypeRequiredDetails
toemailNo Max 200 characters.
fromobjectNo
replyToemailNo Max 200 characters.
subjectstringNoRendered subject
textstringNoPrivate rendered body
template_versionintegerNoPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
transport_versionintegerNoPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
preview_hashstringNo Pattern: ^[a-f0-9]{64}$.
previewbooleanNoPrivate test environment
Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/workspaces/00000000-0000-4000-8000-000000000001/owner-invitation/email-preview' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "invitation_id": "00000000-0000-4000-8000-000000000001",
  "version": 1,
  "reason": "<Required reason recorded in immutable platform audit>"
}'
POST/api/sa/workspaces/{id}/owner-invitation/email

Queue reviewed owner invitation email

Access: Platform owner or administrator · Success: 202

Requires a valid current-issuer invitation and verified enabled platform SMTP. Pins template/transport/invitation versions and encrypts the composed message. Dedicated mail worker checks access again. Identical retry returns200 and same mail_id; fresh queue returns202. One recent delivery per invitation version per ten minutes, at most three per workspace per hour. Reviewed template_version, transport_version and preview_hash are required; changed composed content after review returns409. Changing SMTP cancels queued previous-transport messages; unknown SMTP acceptance is never automatically retried.

JSON request

FieldTypeRequiredDetails
request_iduuidYes
invitation_iduuidYes
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.
template_versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
transport_versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
preview_hashstringYes Pattern: ^[a-f0-9]{64}$.

Response

FieldTypeRequiredDetails
invitation_iduuidNo
versionintegerNoPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
mail_iduuidNo
statequeuedNo
reusedbooleanNoSaved operation replay
Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/workspaces/00000000-0000-4000-8000-000000000001/owner-invitation/email' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "request_id": "00000000-0000-4000-8000-000000000001",
  "invitation_id": "00000000-0000-4000-8000-000000000001",
  "version": 1,
  "reason": "<Required reason recorded in immutable platform audit>",
  "template_version": 1,
  "transport_version": 1,
  "preview_hash": "<value>"
}'
GET/api/sa/email/transport

Read platform email connection

Access: Platform owner or administrator · Success: 200

Returns SMTP connection metadata, optimistic version, enabled/verified state and credential presence only. Passwords and ciphertext are never returned. Only a platform owner can save the transport.

No request body is required.

Response

FieldTypeRequiredDetails
versionintegerNoPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
metadataobjectNo
enabledbooleanNoEnabled for delivery
verified_atstring or nullNo
updated_atstringNoTimestamp
has_credentialsbooleanNoEncrypted connection present
previewbooleanNoPrivate test preview transport; never available in production
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/email/transport' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
PUT/api/sa/email/transport

Configure and verify platform SMTP

Access: Platform owner · Success: 200

Saves an encrypted SMTP connection with current version and required reason. Enabling verifies authentication without sending mail; public host with validated TLS on465/587 required. Omitted password reuses the saved secret; first connection requires it. Existing queued messages are not moved onto a different transport version. Test-only preview environments are explicitly reported.

JSON request

FieldTypeRequiredDetails
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.
enabledbooleanYesEnable after verification
emailemailYes Max 200 characters.
from_namestringYesSingle-line sender name Max 100 characters. Pattern: ^[^\r\n]+$.
hoststringYesPublic SMTP hostname Max 253 characters. Pattern: ^[a-zA-Z0-9.-]+$.
port465 | 587Yes
userstringYesAccount username Max 200 characters.
passwordstringNoSMTP password; omitted retains saved value Max 1000 characters.

Response

FieldTypeRequiredDetails
versionintegerNoPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
metadataobjectNo
enabledbooleanNoEnabled for delivery
verified_atstring or nullNo
updated_atstringNoTimestamp
has_credentialsbooleanNoEncrypted connection present
previewbooleanNoPrivate test preview transport; never available in production
Request shape (illustrative)
curl --request PUT \
  --url 'https://app.drykraft.com/api/sa/email/transport' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "version": 1,
  "reason": "<Required reason recorded in immutable platform audit>",
  "enabled": false,
  "email": "person@example.com",
  "from_name": "<Single-line sender name>",
  "host": "<Public SMTP hostname>",
  "port": 465,
  "user": "<Account username>"
}'
GET/api/sa/email/templates/owner_invitation

Read owner invitation email template

Access: Platform account · Success: 200

Returns current plain-text subject/body, version and supported placeholders. Customer workspace templates are separate. Reading a template does not dispatch any email.

No request body is required.

Response

FieldTypeRequiredDetails
keyowner_invitationNo
versionintegerNoPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
subjectstringNoSingle-line plain-text subject Max 200 characters. Pattern: ^[^\r\n]+$.
bodystringNoPlain text with required {{invite_url}}. Allowed placeholders: workspace_name, product_name, invite_url, expires_at, owner_email, support_email. Max 10000 characters.
updated_atstringNoTimestamp
variablesstring[]No
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/email/templates/owner_invitation' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
PUT/api/sa/email/templates/owner_invitation

Edit owner invitation email template

Access: Platform owner or administrator · Success: 200

Current version and reason required. Subject must be single-line. Body requires {{invite_url}}; unknown/incomplete placeholders are rejected. Saves an immutable version for queued-message snapshots. Changes do not rewrite existing messages. HTML and remote assets are not rendered.

JSON request

FieldTypeRequiredDetails
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.
subjectstringYesSingle-line plain-text subject Max 200 characters. Pattern: ^[^\r\n]+$.
bodystringYesPlain text with required {{invite_url}}. Allowed placeholders: workspace_name, product_name, invite_url, expires_at, owner_email, support_email. Max 10000 characters.

Response

FieldTypeRequiredDetails
keyowner_invitationNo
versionintegerNoPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
subjectstringNoSingle-line plain-text subject Max 200 characters. Pattern: ^[^\r\n]+$.
bodystringNoPlain text with required {{invite_url}}. Allowed placeholders: workspace_name, product_name, invite_url, expires_at, owner_email, support_email. Max 10000 characters.
updated_atstringNoTimestamp
variablesstring[]No
Request shape (illustrative)
curl --request PUT \
  --url 'https://app.drykraft.com/api/sa/email/templates/owner_invitation' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "version": 1,
  "reason": "<Required reason recorded in immutable platform audit>",
  "subject": "<Single-line plain-text subject>",
  "body": "<Plain text with required {{invite_url}}>"
}'
POST/api/sa/email/templates/owner_invitation/preview

Preview invitation content without sending

Access: Platform account · Success: 200

Validates submitted subject/body and replaces supported placeholders with fictional sample data. Does not save, create an invitation, reveal real tokens or dispatch email.

JSON request

FieldTypeRequiredDetails
subjectstringYesSingle-line plain-text subject Max 200 characters. Pattern: ^[^\r\n]+$.
bodystringYesPlain text with required {{invite_url}}. Allowed placeholders: workspace_name, product_name, invite_url, expires_at, owner_email, support_email. Max 10000 characters.

Response

FieldTypeRequiredDetails
subjectstringNoRendered sample subject
textstringNoRendered sample plain-text body
Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/email/templates/owner_invitation/preview' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "subject": "<Single-line plain-text subject>",
  "body": "<Plain text with required {{invite_url}}>"
}'
GET/api/sa/email/deliveries

Read platform invitation delivery history

Access: Platform account · Success: 200

Returns25 newest deliveries per cursor, optionally restricted to one workspace. Safe recipient/status/template version/timestamps/error code only; no message body, token or provider credentials. SMTP acceptance is not delivery or read proof. Unknown jobs require operator inspection and are not retried automatically.

Query parameters

ParameterTypeRequiredDetails
cursoruuidNo
workspace_iduuidNo

No request body is required.

Response

FieldTypeRequiredDetails
itemsobject[]No Max 25 items.
next_cursorstring or nullNo
previewbooleanNoPrivate test environment
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/email/deliveries' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
GET/api/sa/workspaces/provisioning/{requestId}

Recover saved provisioning result

Access: Platform owner or administrator · Success: 200

Only the original still-authorized staff account can recover its operation result after a timeout or reload. Other actors/missing operations return404. Valid pending invitation token is private; accepted/expired/revoked invitations or invitations renewed by another administrator return null token. A valid replacement token is recoverable only while this actor remains its issuer. This read is audited and does not create or send anything.

No request body is required.

Response

FieldTypeRequiredDetails
iduuidNo
invitation_iduuidNo
versionintegerNoPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
owner_emailemailNo Max 200 characters.
expires_atstringNoTimestamp
owner_joinedbooleanNoOwner joined
invitation_tokenstring or nullNoPrivate valid pending invitation token
reusedtrueNo
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/workspaces/provisioning/00000000-0000-4000-8000-000000000001' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
GET/api/sa/users

List customer accounts

Access: Platform account · Success: 200

Searches literal customer name/email text and returns25 newest users per cursor page with workspace membership counts. Customer password hashes and MFA secrets are excluded.

Query parameters

ParameterTypeRequiredDetails
searchstringNoLiteral name/email search, default blank Max 100 characters.
cursoruuidNo

No request body is required.

Response

FieldTypeRequiredDetails
itemsobject[]Yes Max 25 items.
next_cursorstring or nullYes
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/users' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
GET/api/sa/users/{id}

Read customer account management

Access: Platform account · Success: 200

Shows global customer account status, safe MFA status, active session count and memberships count. This view is audited. Does not expose passwords, MFA keys or recovery codes.

No request body is required.

Response

FieldTypeRequiredDetails
iduuidNo
namestringNoName
emailemailNo Max 200 characters.
created_atstringNoTimestamp
disabledbooleanNoGlobal customer sign-in disabled
versionintegerNoPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
reasonstringNoLast status reason
active_sessionsintegerNoUnexpired sessions
mfa_enabledbooleanNoCustomer authenticator enabled
workspace_countintegerNoCurrent workspace memberships
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/users/00000000-0000-4000-8000-000000000001' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
GET/api/sa/users/{id}/workspaces

List customer workspace memberships

Access: Platform account · Success: 200

Returns up to25 current memberships ordered by workspace UUID with workspace ID/name/address, role and suspended status. Use next_cursor unchanged.

Query parameters

ParameterTypeRequiredDetails
cursoruuidNo

No request body is required.

Response

FieldTypeRequiredDetails
itemsobject[]Yes Max 25 items.
next_cursorstring or nullYes
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/users/00000000-0000-4000-8000-000000000001/workspaces' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
POST/api/sa/users/{id}/state

Disable or restore customer sign-in

Access: Platform owner or administrator · Success: 200

Current version and reason required. Locks the customer account against session issuance, removes all sessions/sign-in challenges and advances authentication version. Retains memberships, password/MFA credentials and business history. Restoration requires fresh sign-in. Stale version returns409 and repeating current state adds no event.

JSON request

FieldTypeRequiredDetails
disabledbooleanYesTrue disables sign-in; false restores
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

FieldTypeRequiredDetails
iduuidNo
namestringNoName
emailemailNo Max 200 characters.
created_atstringNoTimestamp
disabledbooleanNoGlobal customer sign-in disabled
versionintegerNoPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
reasonstringNoLast status reason
active_sessionsintegerNoUnexpired sessions
mfa_enabledbooleanNoCustomer authenticator enabled
workspace_countintegerNoCurrent workspace memberships
Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/users/00000000-0000-4000-8000-000000000001/state' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "disabled": false,
  "version": 1,
  "reason": "<Required reason recorded in immutable platform audit>"
}'
GET/api/sa/billing

List saved workspace subscriptions

Access: Platform owner, administrator, billing or read-only account · Success: 200

Returns25 newest workspaces with saved provider state, purchased seats, trial/renewal dates and last sync timestamp. This read does not query or mutate provider payments.

Query parameters

ParameterTypeRequiredDetails
searchstringNoLiteral name/email search, default blank Max 100 characters.
cursoruuidNo

No request body is required.

Response

FieldTypeRequiredDetails
itemsobject[]Yes Max 25 items.
next_cursorstring or nullYes
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/billing' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
GET/api/sa/accounts

List platform accounts

Access: Platform owner · Success: 200

Owner-only directory,25 newest accounts per cursor page. Safe role/status/enrollment metadata only; customer users are separate.

Query parameters

ParameterTypeRequiredDetails
searchstringNoLiteral name/email search, default blank Max 100 characters.
cursoruuidNo

No request body is required.

Response

FieldTypeRequiredDetails
itemsobject[]Yes Max 25 items.
next_cursorstring or nullYes
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/accounts' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
GET/api/sa/accounts/{id}

Read platform account

Access: Platform owner · Success: 200

Returns safe identity, role, version, disabled status and enrollment/password-change timestamps for a separate platform account.

No request body is required.

Response

FieldTypeRequiredDetails
iduuidYes
namestringYesName
emailemailYes Max 200 characters.
roleowner | admin | support | billing | viewerYesSeparate platform role; workspace roles do not grant platform access
disabledbooleanYesAccount disabled
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
mfa_enabledbooleanYesAuthenticator enrolled
created_atstringNoTimestamp
password_changed_atstring or nullNo
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/accounts/00000000-0000-4000-8000-000000000001' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
POST/api/sa/accounts

Create platform account

Access: Platform owner · Success: 201

Creates a separate staff account with a12-128 character initial password and required access reason. Recipient must enroll an authenticator before console access. No customer workspace permissions or email delivery are added.

JSON request

FieldTypeRequiredDetails
namestringYesName Max 100 characters.
emailemailYes Max 200 characters.
passwordstringYesPrivate password Max 128 characters.
roleowner | admin | support | billing | viewerYesSeparate platform role; workspace roles do not grant platform access
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

FieldTypeRequiredDetails
iduuidYes
namestringYesName
emailemailYes Max 200 characters.
roleowner | admin | support | billing | viewerYesSeparate platform role; workspace roles do not grant platform access
disabledbooleanYesAccount disabled
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
mfa_enabledbooleanYesAuthenticator enrolled
created_atstringNoTimestamp
password_changed_atstring or nullNo
Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/sa/accounts' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "<Name>",
  "email": "person@example.com",
  "password": "<Private password>",
  "role": "owner",
  "reason": "<Required reason recorded in immutable platform audit>"
}'
PUT/api/sa/accounts/{id}

Update platform role or status

Access: Platform owner · Success: 200

Current version and reason required. Serialized governance keeps at least one active owner. Role/status changes revoke the target’s sessions/challenges; fresh actor authority is checked inside the transaction.

JSON request

FieldTypeRequiredDetails
namestringYesName Max 100 characters.
roleowner | admin | support | billing | viewerYesSeparate platform role; workspace roles do not grant platform access
disabledbooleanYesAccount disabled
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.

Response

FieldTypeRequiredDetails
iduuidYes
namestringYesName
emailemailYes Max 200 characters.
roleowner | admin | support | billing | viewerYesSeparate platform role; workspace roles do not grant platform access
disabledbooleanYesAccount disabled
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
mfa_enabledbooleanYesAuthenticator enrolled
created_atstringNoTimestamp
password_changed_atstring or nullNo
Request shape (illustrative)
curl --request PUT \
  --url 'https://app.drykraft.com/api/sa/accounts/00000000-0000-4000-8000-000000000001' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "<Name>",
  "role": "owner",
  "disabled": false,
  "version": 1,
  "reason": "<Required reason recorded in immutable platform audit>"
}'
GET/api/sa/settings

Read platform settings

Access: Platform owner or administrator · Success: 200

Reads current signup availability, public support email/maintenance notice and version. Only owners can save. Provider credential values are not included.

No request body is required.

Response

FieldTypeRequiredDetails
versionintegerNoPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
dataobjectNo
updated_atstringNoTimestamp
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/settings' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
PUT/api/sa/settings

Save platform settings

Access: Platform owner · Success: 200

Current version and reason required. Closed data contract; unknown fields rejected. Signup switch controls new registration; public metadata contains only support_email and maintenance_message. Immutable audit records the change.

JSON request

FieldTypeRequiredDetails
versionintegerYesPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
reasonstringYesRequired reason recorded in immutable platform audit Max 1000 characters.
dataobjectYes

Response

FieldTypeRequiredDetails
versionintegerNoPositive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1.
dataobjectNo
updated_atstringNoTimestamp
Request shape (illustrative)
curl --request PUT \
  --url 'https://app.drykraft.com/api/sa/settings' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>' \
  --header 'Origin: https://app.drykraft.com' \
  --header 'Content-Type: application/json' \
  --data '{
  "version": 1,
  "reason": "<Required reason recorded in immutable platform audit>",
  "data": {
    "signup_enabled": false,
    "support_email": "person@example.com",
    "maintenance_message": "<Public notice>"
  }
}'
GET/api/sa/audit

Read immutable platform audit

Access: Platform owner or administrator · Success: 200

Returns25 newest platform access/change events per cursor page. Optional exact action/resource filters. Platform runtime can append/read but cannot update/delete audit; no credential values are recorded.

Query parameters

ParameterTypeRequiredDetails
cursoruuidNo
resource_iduuidNo
actionstringNoExact action Max 100 characters.

No request body is required.

Response

FieldTypeRequiredDetails
itemsobject[]Yes Max 25 items.
next_cursorstring or nullYes
Request shape (illustrative)
curl --request GET \
  --url 'https://app.drykraft.com/api/sa/audit' \
  --cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'
POST/api/auth/platform-invitations/preview

Preview platform owner invitation

Access: Public · Success: 200

Email-bound owner invitation. Expiry/revocation, issuing staff authority and workspace hostname are checked. Returns management context without granting membership or issuing a session.

JSON request

FieldTypeRequiredDetails
tokenstringYes Pattern: ^[a-f0-9]{64}$.

Response

FieldTypeRequiredDetails
emailemailNo Max 200 characters.
roleownerNo
expires_atstringNoTimestamp
acceptedbooleanNoAlready accepted
workspaceobjectNo
Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/auth/platform-invitations/preview' \
  --header 'Content-Type: application/json' \
  --data '{
  "token": "<value>"
}'
POST/api/auth/platform-invitations/accept

Accept workspace owner invitation

Access: Public · Success: 200

Verifies invited email account password plus MFA proof when enabled. New recipients supply name and signup mode. Locks issuer authority, workspace and user; checks suspension/available seat. Grants ownership and customer session atomically. Accepted retries require that same account to remain owner; removed membership is never recreated. Disabled accounts cannot accept.

JSON request

FieldTypeRequiredDetails
tokenstringYes Pattern: ^[a-f0-9]{64}$.
passwordstringYes Max 128 characters.
modelogin | signupYesExisting account or new recipient
namestringNoRequired for new account Max 100 characters.
codestringNoRequired fresh authenticator or unused recovery code for MFA accounts Max 40 characters.

Response

Use OK.

Request shape (illustrative)
curl --request POST \
  --url 'https://app.drykraft.com/api/auth/platform-invitations/accept' \
  --header 'Content-Type: application/json' \
  --data '{
  "token": "<value>",
  "password": "<value>",
  "mode": "login"
}'