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.
/api/sa/resellers/{id}/api-keysList 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
| Parameter | Type | Required | Details |
|---|---|---|---|
cursor | uuid | No |
No request body is required.
Response
| Field | Type | Required | Details |
|---|---|---|---|
items | object[] | No | Max 25 items. |
next_cursor | string or null | No | |
available_scopes | reseller.customers.read | reseller.plans.read | reseller.usage.read[] | No | Min 1 items. Max 3 items. |
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>'/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
| Field | Type | Required | Details |
|---|---|---|---|
key | object | Yes | |
secret | null | Yes | |
replayed | true | Yes |
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>'/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
| Field | Type | Required | Details |
|---|---|---|---|
id | uuid | Yes | |
reseller_id | uuid | Yes | |
name | string | Yes | Key name Max 100 characters. |
prefix | string | Yes | Nonsecret dk_res_ prefix |
scopes | reseller.customers.read | reseller.plans.read | reseller.usage.read[] | Yes | Min 1 items. Max 3 items. |
expires_at | date-time | Yes | ISO instant with explicit UTC offset. |
revoked_at | string or null | Yes | |
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
created_by | uuid | Yes | |
created_at | date-time | Yes | ISO instant with explicit UTC offset. |
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>'/api/sa/resellers/{id}/api-keysIssue 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
| Field | Type | Required | Details |
|---|---|---|---|
name | string | Yes | Key name Max 100 characters. |
scopes | reseller.customers.read | reseller.plans.read | reseller.usage.read[] | Yes | Min 1 items. Max 3 items. |
expires_at | date-time | Yes | ISO instant with explicit UTC offset. |
request_id | uuid | Yes | |
reason | string | Yes | Required reason recorded in immutable platform audit Max 1000 characters. |
Response
| Field | Type | Required | Details |
|---|---|---|---|
key | object | Yes | |
secret | string or null | Yes | dk_res_ followed by 64 lowercase hexadecimal characters. Present on first successful creation only. |
replayed | boolean | Yes | Identical saved request |
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>"
}'/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
| Field | Type | Required | Details |
|---|---|---|---|
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
reason | string | Yes | Required reason recorded in immutable platform audit Max 1000 characters. |
Response
| Field | Type | Required | Details |
|---|---|---|---|
id | uuid | Yes | |
reseller_id | uuid | Yes | |
name | string | Yes | Key name Max 100 characters. |
prefix | string | Yes | Nonsecret dk_res_ prefix |
scopes | reseller.customers.read | reseller.plans.read | reseller.usage.read[] | Yes | Min 1 items. Max 3 items. |
expires_at | date-time | Yes | ISO instant with explicit UTC offset. |
revoked_at | string or null | Yes | |
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
created_by | uuid | Yes | |
created_at | date-time | Yes | ISO instant with explicit UTC offset. |
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>"
}'/api/sa/resellers/{id}/membersList 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
| Parameter | Type | Required | Details |
|---|---|---|---|
cursor | uuid | No |
No request body is required.
Response
| Field | Type | Required | Details |
|---|---|---|---|
items | object[] | Yes | Max 25 items. |
next_cursor | string or null | Yes |
curl --request GET \
--url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001/members' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/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
| Field | Type | Required | Details |
|---|---|---|---|
id | uuid | Yes | |
account_id | uuid | Yes | |
name | string | Yes | Staff name |
email | Yes | Max 200 characters. | |
role | owner | admin | support | billing | Yes | Organization membership role, separate from platform and customer roles |
disabled | boolean | Yes | Membership disabled |
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
mfa_enabled | boolean | Yes | Authenticator enrolled |
account_disabled | boolean | No | Global staff account disabled; directory reads only |
created_at | date-time | No | ISO instant with explicit UTC offset. |
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>'/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
| Field | Type | Required | Details |
|---|---|---|---|
id | uuid | Yes | |
account_id | uuid | Yes | |
name | string | Yes | Staff name |
email | Yes | Max 200 characters. | |
role | owner | admin | support | billing | Yes | Organization membership role, separate from platform and customer roles |
disabled | boolean | Yes | Membership disabled |
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
mfa_enabled | boolean | Yes | Authenticator enrolled |
account_disabled | boolean | No | Global staff account disabled; directory reads only |
created_at | date-time | No | ISO instant with explicit UTC offset. |
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>'/api/sa/resellers/{id}/membersCreate 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
| Field | Type | Required | Details |
|---|---|---|---|
id | uuid | Yes | |
account_id | uuid | Yes | |
name | string | Yes | Staff name |
email | Yes | Max 200 characters. | |
role | owner | admin | support | billing | Yes | Organization membership role, separate from platform and customer roles |
disabled | boolean | Yes | Membership disabled |
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
mfa_enabled | boolean | Yes | Authenticator enrolled |
account_disabled | boolean | No | Global staff account disabled; directory reads only |
created_at | date-time | No | ISO instant with explicit UTC offset. |
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>"
}'/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
| Field | Type | Required | Details |
|---|---|---|---|
role | owner | admin | support | billing | Yes | Organization membership role, separate from platform and customer roles |
disabled | boolean | Yes | Disable organization membership without deletion |
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
reason | string | Yes | Required reason recorded in immutable platform audit Max 1000 characters. |
Response
| Field | Type | Required | Details |
|---|---|---|---|
id | uuid | Yes | |
account_id | uuid | Yes | |
name | string | Yes | Staff name |
email | Yes | Max 200 characters. | |
role | owner | admin | support | billing | Yes | Organization membership role, separate from platform and customer roles |
disabled | boolean | Yes | Membership disabled |
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
mfa_enabled | boolean | Yes | Authenticator enrolled |
account_disabled | boolean | No | Global staff account disabled; directory reads only |
created_at | date-time | No | ISO instant with explicit UTC offset. |
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>"
}'/api/sa/resellersList 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
| Parameter | Type | Required | Details |
|---|---|---|---|
cursor | uuid | No | |
search | string | No | Literal name/key search Max 100 characters. |
status | all | pending | active | suspended | No |
No request body is required.
Response
| Field | Type | Required | Details |
|---|---|---|---|
items | object[] | Yes | Max 25 items. |
next_cursor | string or null | Yes |
curl --request GET \
--url 'https://app.drykraft.com/api/sa/resellers' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/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.
curl --request GET \
--url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/api/sa/resellersCreate 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
| Field | Type | Required | Details |
|---|---|---|---|
key | string | Yes | Pattern: ^[a-z][a-z0-9-]{2,49}$. |
name | string | Yes | Organization name Max 100 characters. |
customer_access_policy | retain_access | suspend_access | Yes | Explicit customer behavior while reseller is pending/suspended or its contract is outside its date window. Never deletes customer data. |
contract_reference | string | Yes | Commercial contract reference; not a credential Max 200 characters. |
billing_mode | wholesale | managed_rebilling | Yes | Contract metadata only. Provider collection, wallets, settlement and managed rebilling are not configured by this API. |
contract_starts_at | date-time | Yes | |
contract_ends_at | date-time | Yes | Must follow contract_starts_at |
grant | ResellerGrantDefinition | Yes | |
reason | string | Yes | Required 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.
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>"
}'/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
| Field | Type | Required | Details |
|---|---|---|---|
name | string | Yes | Organization name Max 100 characters. |
customer_access_policy | retain_access | suspend_access | Yes | Explicit customer behavior while reseller is pending/suspended or its contract is outside its date window. Never deletes customer data. |
contract_reference | string | Yes | Commercial contract reference; not a credential Max 200 characters. |
billing_mode | wholesale | managed_rebilling | Yes | Contract metadata only. Provider collection, wallets, settlement and managed rebilling are not configured by this API. |
contract_starts_at | date-time | Yes | |
contract_ends_at | date-time | Yes | Must follow contract_starts_at |
status | pending | active | suspended | Yes | |
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
reason | string | Yes | Required 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.
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>"
}'/api/sa/resellers/{id}/grantsRead 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
| Parameter | Type | Required | Details |
|---|---|---|---|
before | integer | No | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
No request body is required.
Response
object.
curl --request GET \
--url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001/grants' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/api/sa/resellers/{id}/grantsPublish 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
| Field | Type | Required | Details |
|---|---|---|---|
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
definition | ResellerGrantDefinition | Yes | |
reason | string | Yes | Required 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.
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>"
}'/api/sa/resellers/{id}/plansList 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
| Parameter | Type | Required | Details |
|---|---|---|---|
cursor | uuid | No |
No request body is required.
Response
| Field | Type | Required | Details |
|---|---|---|---|
items | object[] | Yes | Max 25 items. |
next_cursor | string or null | Yes |
curl --request GET \
--url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001/plans' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/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
| Parameter | Type | Required | Details |
|---|---|---|---|
before | integer | No | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
No request body is required.
Response
object.
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>'/api/sa/resellers/{id}/plansCreate 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
| Field | Type | Required | Details |
|---|---|---|---|
key | string | Yes | Pattern: ^[a-z][a-z0-9-]{2,49}$. |
definition | ResellerRetailDefinition | Yes | |
reason | string | Yes | Required reason recorded in immutable platform audit Max 1000 characters. |
Response
object.
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>"
}'/api/sa/resellers/{id}/plans/{planId}/versionsPublish 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
| Field | Type | Required | Details |
|---|---|---|---|
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
definition | ResellerRetailDefinition | Yes | |
reason | string | Yes | Required reason recorded in immutable platform audit Max 1000 characters. |
Response
object.
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>"
}'/api/sa/resellers/{id}/customersList 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
| Parameter | Type | Required | Details |
|---|---|---|---|
cursor | uuid | No |
No request body is required.
Response
| Field | Type | Required | Details |
|---|---|---|---|
items | object[] | Yes | Max 25 items. |
next_cursor | string or null | Yes |
curl --request GET \
--url 'https://app.drykraft.com/api/sa/resellers/00000000-0000-4000-8000-000000000001/customers' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/api/sa/workspaces/{id}/resellerRead 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.
curl --request GET \
--url 'https://app.drykraft.com/api/sa/workspaces/00000000-0000-4000-8000-000000000001/reseller' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/api/sa/workspaces/{id}/resellerAssign 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
| Field | Type | Required | Details |
|---|---|---|---|
version | integer | Yes | Current assignment version;0 only when never assigned Min 0. |
reseller_id | string or null | Yes | |
retail_plan_id | string or null | Yes | |
retail_plan_version | integer or null | Yes | Min 1. |
reason | string | Yes | Required 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.
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>"
}'/api/sa/plansList 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
| Parameter | Type | Required | Details |
|---|---|---|---|
search | string | No | Literal name/email search, default blank Max 100 characters. |
cursor | uuid | No |
No request body is required.
Response
| Field | Type | Required | Details |
|---|---|---|---|
items | object[] | No | Max 25 items. |
next_cursor | string or null | No | |
meters | string[] | No | |
features | string[] | No |
curl --request GET \
--url 'https://app.drykraft.com/api/sa/plans' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/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
| Parameter | Type | Required | Details |
|---|---|---|---|
before | integer | No | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
No request body is required.
Response
| Field | Type | Required | Details |
|---|---|---|---|
id | uuid | No | |
key | string | No | Plan key |
current_version | integer | No | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
versions | object[] | No | Max 25 items. |
next_before | integer or null | No |
curl --request GET \
--url 'https://app.drykraft.com/api/sa/plans/00000000-0000-4000-8000-000000000001' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/api/sa/plansCreate 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
| Field | Type | Required | Details |
|---|---|---|---|
key | string | Yes | Pattern: ^[a-z][a-z0-9-]{2,49}$. |
definition | PlatformPlanDefinition | Yes | |
reason | string | Yes | Required reason recorded in immutable platform audit Max 1000 characters. |
Response
object.
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>"
}'/api/sa/plans/{id}/versionsPublish 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
| Field | Type | Required | Details |
|---|---|---|---|
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
definition | PlatformPlanDefinition | Yes | |
reason | string | Yes | Required reason recorded in immutable platform audit Max 1000 characters. |
Response
object.
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>"
}'/api/sa/workspaces/{id}/entitlementsRead 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
curl --request GET \
--url 'https://app.drykraft.com/api/sa/workspaces/00000000-0000-4000-8000-000000000001/entitlements' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/api/sa/workspaces/{id}/entitlementsAssign 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
| Field | Type | Required | Details |
|---|---|---|---|
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
plan_id | uuid | Yes | |
plan_version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
overrides | object | Yes | |
agreement | object or null | Yes | |
reason | string | Yes | Required reason recorded in immutable platform audit Max 1000 characters. |
Response
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>"
}'/api/sa/workspaces/{id}/trialExtend 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
| Field | Type | Required | Details |
|---|---|---|---|
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
ends_at | date-time | Yes | |
reason | string | Yes | Required reason recorded in immutable platform audit Max 1000 characters. |
Response
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>"
}'/api/sa/loginStart 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
| Field | Type | Required | Details |
|---|---|---|---|
email | Yes | Max 200 characters. | |
password | string | Yes | Current platform password Max 128 characters. |
Response
| Field | Type | Required | Details |
|---|---|---|---|
challenge | string | No | Pattern: ^[a-f0-9]{64}$. |
mfa_required | boolean | No | Verification required |
enrollment_required | boolean | No | Enrollment required |
expires_in | 300 | No |
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>"
}'/api/sa/mfa/setupRead 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
| Field | Type | Required | Details |
|---|---|---|---|
challenge | string | Yes | Pattern: ^[a-f0-9]{64}$. |
Response
| Field | Type | Required | Details |
|---|---|---|---|
secret | string | No | Private base32 setup key |
qr | string | No | Private data:image/png QR |
expires_at | string | No | Challenge expiry |
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>"
}'/api/sa/mfaComplete 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
| Field | Type | Required | Details |
|---|---|---|---|
challenge | string | Yes | Pattern: ^[a-f0-9]{64}$. |
code | string | Yes | Authenticator or recovery code Max 40 characters. |
Response
| Field | Type | Required | Details |
|---|---|---|---|
ok | true | No | |
recovery_codes | string[] | No | Min 10 items. Max 10 items. |
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>"
}'/api/sa/logoutEnd 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.
curl --request POST \
--url 'https://app.drykraft.com/api/sa/logout' \
--header 'Origin: https://app.drykraft.com'/api/sa/sessionRead 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
| Field | Type | Required | Details |
|---|---|---|---|
account | object | No | |
expires_at | string | No | Fixed session expiry |
idle_timeout_seconds | 900 | No |
curl --request GET \
--url 'https://app.drykraft.com/api/sa/session' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/api/sa/passwordChange 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
| Field | Type | Required | Details |
|---|---|---|---|
current_password | string | Yes | Current password Max 128 characters. |
new_password | string | Yes | Private password Max 128 characters. |
code | string | Yes | Authenticator or recovery proof Max 40 characters. |
Response
Use OK.
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>"
}'/api/sa/overviewRead 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
| Field | Type | Required | Details |
|---|---|---|---|
workspaces | integer | No | Total workspaces |
suspended_workspaces | integer | No | Suspended workspaces |
users | integer | No | Registered customer users |
active_subscriptions | integer | No | Current active subscriptions |
trial_workspaces | integer | No | Unexpired trial workspaces without active subscription |
curl --request GET \
--url 'https://app.drykraft.com/api/sa/overview' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/api/sa/workspacesList 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
| Parameter | Type | Required | Details |
|---|---|---|---|
search | string | No | Literal name/email search, default blank Max 100 characters. |
cursor | uuid | No | |
status | all | active | suspended | No | Default all |
No request body is required.
Response
| Field | Type | Required | Details |
|---|---|---|---|
items | object[] | Yes | Max 25 items. |
next_cursor | string or null | Yes |
curl --request GET \
--url 'https://app.drykraft.com/api/sa/workspaces' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/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.
curl --request GET \
--url 'https://app.drykraft.com/api/sa/workspaces/00000000-0000-4000-8000-000000000001' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/api/sa/workspaces/{id}/stateSuspend 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
| Field | Type | Required | Details |
|---|---|---|---|
suspended | boolean | Yes | True suspends; false restores |
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
reason | string | Yes | Required 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.
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>"
}'/api/sa/workspacesProvision 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
| Field | Type | Required | Details |
|---|---|---|---|
request_id | uuid | Yes | |
name | string | Yes | Workspace name Max 100 characters. |
slug | string | Yes | Unoccupied and not reserved Pattern: ^[a-z][a-z0-9-]{2,39}$. |
owner_email | Yes | Max 200 characters. | |
currency | string | Yes | Supported currency code, e.g. USD, INR, EUR or KWD. Default USD. |
currencies | string[] | Yes | Min 1 items. Max 180 items. |
timezone | string | Yes | Valid named IANA timezone Max 80 characters. |
reason | string | Yes | Required reason recorded in immutable platform audit Max 1000 characters. |
Response
| Field | Type | Required | Details |
|---|---|---|---|
id | uuid | No | |
invitation_id | uuid | No | |
owner_email | No | Max 200 characters. | |
expires_at | string | No | Invitation expiry |
owner_joined | boolean | No | Invitation accepted |
invitation_token | string or null | No | Private owner invitation token; valid pending invitation only |
reused | boolean | No | Saved operation replay |
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>"
}'/api/sa/workspaces/{id}/owner-invitationRead 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
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>'/api/sa/workspaces/{id}/owner-invitation/linkReveal 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
| Field | Type | Required | Details |
|---|---|---|---|
invitation_id | uuid | Yes | |
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
reason | string | Yes | Required reason recorded in immutable platform audit Max 1000 characters. |
Response
| Field | Type | Required | Details |
|---|---|---|---|
invitation_token | string | No | Pattern: ^[a-f0-9]{64}$. |
version | integer | No | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
expires_at | string | No | Timestamp |
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>"
}'/api/sa/workspaces/{id}/owner-invitation/renewRenew 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
| Field | Type | Required | Details |
|---|---|---|---|
request_id | uuid | Yes | |
invitation_id | uuid | Yes | |
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
reason | string | Yes | Required reason recorded in immutable platform audit Max 1000 characters. |
Response
| Field | Type | Required | Details |
|---|---|---|---|
invitation_id | uuid | No | |
version | integer | No | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
expires_at | string | No | Timestamp |
revoked_at | string or null | No | |
reused | boolean | No | An identical operation replay; no further mutation |
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>"
}'/api/sa/workspaces/{id}/owner-invitation/revokeRevoke 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
| Field | Type | Required | Details |
|---|---|---|---|
request_id | uuid | Yes | |
invitation_id | uuid | Yes | |
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
reason | string | Yes | Required reason recorded in immutable platform audit Max 1000 characters. |
Response
| Field | Type | Required | Details |
|---|---|---|---|
invitation_id | uuid | No | |
version | integer | No | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
expires_at | string | No | Timestamp |
revoked_at | string or null | No | |
reused | boolean | No | An identical operation replay; no further mutation |
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>"
}'/api/sa/workspaces/{id}/owner-invitation/email-previewReview 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
| Field | Type | Required | Details |
|---|---|---|---|
invitation_id | uuid | Yes | |
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
reason | string | Yes | Required reason recorded in immutable platform audit Max 1000 characters. |
Response
| Field | Type | Required | Details |
|---|---|---|---|
to | No | Max 200 characters. | |
from | object | No | |
replyTo | No | Max 200 characters. | |
subject | string | No | Rendered subject |
text | string | No | Private rendered body |
template_version | integer | No | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
transport_version | integer | No | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
preview_hash | string | No | Pattern: ^[a-f0-9]{64}$. |
preview | boolean | No | Private test environment |
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>"
}'/api/sa/workspaces/{id}/owner-invitation/emailQueue 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
| Field | Type | Required | Details |
|---|---|---|---|
request_id | uuid | Yes | |
invitation_id | uuid | Yes | |
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
reason | string | Yes | Required reason recorded in immutable platform audit Max 1000 characters. |
template_version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
transport_version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
preview_hash | string | Yes | Pattern: ^[a-f0-9]{64}$. |
Response
| Field | Type | Required | Details |
|---|---|---|---|
invitation_id | uuid | No | |
version | integer | No | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
mail_id | uuid | No | |
state | queued | No | |
reused | boolean | No | Saved operation replay |
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>"
}'/api/sa/email/transportRead 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
| Field | Type | Required | Details |
|---|---|---|---|
version | integer | No | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
metadata | object | No | |
enabled | boolean | No | Enabled for delivery |
verified_at | string or null | No | |
updated_at | string | No | Timestamp |
has_credentials | boolean | No | Encrypted connection present |
preview | boolean | No | Private test preview transport; never available in production |
curl --request GET \
--url 'https://app.drykraft.com/api/sa/email/transport' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/api/sa/email/transportConfigure 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
| Field | Type | Required | Details |
|---|---|---|---|
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
reason | string | Yes | Required reason recorded in immutable platform audit Max 1000 characters. |
enabled | boolean | Yes | Enable after verification |
email | Yes | Max 200 characters. | |
from_name | string | Yes | Single-line sender name Max 100 characters. Pattern: ^[^\r\n]+$. |
host | string | Yes | Public SMTP hostname Max 253 characters. Pattern: ^[a-zA-Z0-9.-]+$. |
port | 465 | 587 | Yes | |
user | string | Yes | Account username Max 200 characters. |
password | string | No | SMTP password; omitted retains saved value Max 1000 characters. |
Response
| Field | Type | Required | Details |
|---|---|---|---|
version | integer | No | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
metadata | object | No | |
enabled | boolean | No | Enabled for delivery |
verified_at | string or null | No | |
updated_at | string | No | Timestamp |
has_credentials | boolean | No | Encrypted connection present |
preview | boolean | No | Private test preview transport; never available in production |
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>"
}'/api/sa/email/templates/owner_invitationRead 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
| Field | Type | Required | Details |
|---|---|---|---|
key | owner_invitation | No | |
version | integer | No | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
subject | string | No | Single-line plain-text subject Max 200 characters. Pattern: ^[^\r\n]+$. |
body | string | No | Plain text with required {{invite_url}}. Allowed placeholders: workspace_name, product_name, invite_url, expires_at, owner_email, support_email. Max 10000 characters. |
updated_at | string | No | Timestamp |
variables | string[] | No |
curl --request GET \
--url 'https://app.drykraft.com/api/sa/email/templates/owner_invitation' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/api/sa/email/templates/owner_invitationEdit 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
| Field | Type | Required | Details |
|---|---|---|---|
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
reason | string | Yes | Required reason recorded in immutable platform audit Max 1000 characters. |
subject | string | Yes | Single-line plain-text subject Max 200 characters. Pattern: ^[^\r\n]+$. |
body | string | Yes | Plain text with required {{invite_url}}. Allowed placeholders: workspace_name, product_name, invite_url, expires_at, owner_email, support_email. Max 10000 characters. |
Response
| Field | Type | Required | Details |
|---|---|---|---|
key | owner_invitation | No | |
version | integer | No | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
subject | string | No | Single-line plain-text subject Max 200 characters. Pattern: ^[^\r\n]+$. |
body | string | No | Plain text with required {{invite_url}}. Allowed placeholders: workspace_name, product_name, invite_url, expires_at, owner_email, support_email. Max 10000 characters. |
updated_at | string | No | Timestamp |
variables | string[] | No |
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}}>"
}'/api/sa/email/templates/owner_invitation/previewPreview 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
| Field | Type | Required | Details |
|---|---|---|---|
subject | string | Yes | Single-line plain-text subject Max 200 characters. Pattern: ^[^\r\n]+$. |
body | string | Yes | Plain text with required {{invite_url}}. Allowed placeholders: workspace_name, product_name, invite_url, expires_at, owner_email, support_email. Max 10000 characters. |
Response
| Field | Type | Required | Details |
|---|---|---|---|
subject | string | No | Rendered sample subject |
text | string | No | Rendered sample plain-text body |
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}}>"
}'/api/sa/email/deliveriesRead 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
| Parameter | Type | Required | Details |
|---|---|---|---|
cursor | uuid | No | |
workspace_id | uuid | No |
No request body is required.
Response
| Field | Type | Required | Details |
|---|---|---|---|
items | object[] | No | Max 25 items. |
next_cursor | string or null | No | |
preview | boolean | No | Private test environment |
curl --request GET \
--url 'https://app.drykraft.com/api/sa/email/deliveries' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/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
| Field | Type | Required | Details |
|---|---|---|---|
id | uuid | No | |
invitation_id | uuid | No | |
version | integer | No | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
owner_email | No | Max 200 characters. | |
expires_at | string | No | Timestamp |
owner_joined | boolean | No | Owner joined |
invitation_token | string or null | No | Private valid pending invitation token |
reused | true | No |
curl --request GET \
--url 'https://app.drykraft.com/api/sa/workspaces/provisioning/00000000-0000-4000-8000-000000000001' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/api/sa/usersList 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
| Parameter | Type | Required | Details |
|---|---|---|---|
search | string | No | Literal name/email search, default blank Max 100 characters. |
cursor | uuid | No |
No request body is required.
Response
| Field | Type | Required | Details |
|---|---|---|---|
items | object[] | Yes | Max 25 items. |
next_cursor | string or null | Yes |
curl --request GET \
--url 'https://app.drykraft.com/api/sa/users' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/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
| Field | Type | Required | Details |
|---|---|---|---|
id | uuid | No | |
name | string | No | Name |
email | No | Max 200 characters. | |
created_at | string | No | Timestamp |
disabled | boolean | No | Global customer sign-in disabled |
version | integer | No | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
reason | string | No | Last status reason |
active_sessions | integer | No | Unexpired sessions |
mfa_enabled | boolean | No | Customer authenticator enabled |
workspace_count | integer | No | Current workspace memberships |
curl --request GET \
--url 'https://app.drykraft.com/api/sa/users/00000000-0000-4000-8000-000000000001' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/api/sa/users/{id}/workspacesList 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
| Parameter | Type | Required | Details |
|---|---|---|---|
cursor | uuid | No |
No request body is required.
Response
| Field | Type | Required | Details |
|---|---|---|---|
items | object[] | Yes | Max 25 items. |
next_cursor | string or null | Yes |
curl --request GET \
--url 'https://app.drykraft.com/api/sa/users/00000000-0000-4000-8000-000000000001/workspaces' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/api/sa/users/{id}/stateDisable 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
| Field | Type | Required | Details |
|---|---|---|---|
disabled | boolean | Yes | True disables sign-in; false restores |
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
reason | string | Yes | Required reason recorded in immutable platform audit Max 1000 characters. |
Response
| Field | Type | Required | Details |
|---|---|---|---|
id | uuid | No | |
name | string | No | Name |
email | No | Max 200 characters. | |
created_at | string | No | Timestamp |
disabled | boolean | No | Global customer sign-in disabled |
version | integer | No | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
reason | string | No | Last status reason |
active_sessions | integer | No | Unexpired sessions |
mfa_enabled | boolean | No | Customer authenticator enabled |
workspace_count | integer | No | Current workspace memberships |
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>"
}'/api/sa/billingList 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
| Parameter | Type | Required | Details |
|---|---|---|---|
search | string | No | Literal name/email search, default blank Max 100 characters. |
cursor | uuid | No |
No request body is required.
Response
| Field | Type | Required | Details |
|---|---|---|---|
items | object[] | Yes | Max 25 items. |
next_cursor | string or null | Yes |
curl --request GET \
--url 'https://app.drykraft.com/api/sa/billing' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/api/sa/accountsList 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
| Parameter | Type | Required | Details |
|---|---|---|---|
search | string | No | Literal name/email search, default blank Max 100 characters. |
cursor | uuid | No |
No request body is required.
Response
| Field | Type | Required | Details |
|---|---|---|---|
items | object[] | Yes | Max 25 items. |
next_cursor | string or null | Yes |
curl --request GET \
--url 'https://app.drykraft.com/api/sa/accounts' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/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
| Field | Type | Required | Details |
|---|---|---|---|
id | uuid | Yes | |
name | string | Yes | Name |
email | Yes | Max 200 characters. | |
role | owner | admin | support | billing | viewer | Yes | Separate platform role; workspace roles do not grant platform access |
disabled | boolean | Yes | Account disabled |
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
mfa_enabled | boolean | Yes | Authenticator enrolled |
created_at | string | No | Timestamp |
password_changed_at | string or null | No |
curl --request GET \
--url 'https://app.drykraft.com/api/sa/accounts/00000000-0000-4000-8000-000000000001' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/api/sa/accountsCreate 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
| Field | Type | Required | Details |
|---|---|---|---|
name | string | Yes | Name Max 100 characters. |
email | Yes | Max 200 characters. | |
password | string | Yes | Private password Max 128 characters. |
role | owner | admin | support | billing | viewer | Yes | Separate platform role; workspace roles do not grant platform access |
reason | string | Yes | Required reason recorded in immutable platform audit Max 1000 characters. |
Response
| Field | Type | Required | Details |
|---|---|---|---|
id | uuid | Yes | |
name | string | Yes | Name |
email | Yes | Max 200 characters. | |
role | owner | admin | support | billing | viewer | Yes | Separate platform role; workspace roles do not grant platform access |
disabled | boolean | Yes | Account disabled |
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
mfa_enabled | boolean | Yes | Authenticator enrolled |
created_at | string | No | Timestamp |
password_changed_at | string or null | No |
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>"
}'/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
| Field | Type | Required | Details |
|---|---|---|---|
name | string | Yes | Name Max 100 characters. |
role | owner | admin | support | billing | viewer | Yes | Separate platform role; workspace roles do not grant platform access |
disabled | boolean | Yes | Account disabled |
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
reason | string | Yes | Required reason recorded in immutable platform audit Max 1000 characters. |
Response
| Field | Type | Required | Details |
|---|---|---|---|
id | uuid | Yes | |
name | string | Yes | Name |
email | Yes | Max 200 characters. | |
role | owner | admin | support | billing | viewer | Yes | Separate platform role; workspace roles do not grant platform access |
disabled | boolean | Yes | Account disabled |
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
mfa_enabled | boolean | Yes | Authenticator enrolled |
created_at | string | No | Timestamp |
password_changed_at | string or null | No |
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>"
}'/api/sa/settingsRead 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
| Field | Type | Required | Details |
|---|---|---|---|
version | integer | No | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
data | object | No | |
updated_at | string | No | Timestamp |
curl --request GET \
--url 'https://app.drykraft.com/api/sa/settings' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/api/sa/settingsSave 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
| Field | Type | Required | Details |
|---|---|---|---|
version | integer | Yes | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
reason | string | Yes | Required reason recorded in immutable platform audit Max 1000 characters. |
data | object | Yes |
Response
| Field | Type | Required | Details |
|---|---|---|---|
version | integer | No | Positive optimistic version returned by the latest read. Refresh on 409; do not overwrite stale changes. Min 1. |
data | object | No | |
updated_at | string | No | Timestamp |
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>"
}
}'/api/sa/auditRead 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
| Parameter | Type | Required | Details |
|---|---|---|---|
cursor | uuid | No | |
resource_id | uuid | No | |
action | string | No | Exact action Max 100 characters. |
No request body is required.
Response
| Field | Type | Required | Details |
|---|---|---|---|
items | object[] | Yes | Max 25 items. |
next_cursor | string or null | Yes |
curl --request GET \
--url 'https://app.drykraft.com/api/sa/audit' \
--cookie 'drykraft_sa_session=<YOUR_SESSION_TOKEN>'/api/auth/platform-invitations/previewPreview 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
| Field | Type | Required | Details |
|---|---|---|---|
token | string | Yes | Pattern: ^[a-f0-9]{64}$. |
Response
| Field | Type | Required | Details |
|---|---|---|---|
email | No | Max 200 characters. | |
role | owner | No | |
expires_at | string | No | Timestamp |
accepted | boolean | No | Already accepted |
workspace | object | No |
curl --request POST \
--url 'https://app.drykraft.com/api/auth/platform-invitations/preview' \
--header 'Content-Type: application/json' \
--data '{
"token": "<value>"
}'/api/auth/platform-invitations/acceptAccept 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
| Field | Type | Required | Details |
|---|---|---|---|
token | string | Yes | Pattern: ^[a-f0-9]{64}$. |
password | string | Yes | Max 128 characters. |
mode | login | signup | Yes | Existing account or new recipient |
name | string | No | Required for new account Max 100 characters. |
code | string | No | Required fresh authenticator or unused recovery code for MFA accounts Max 40 characters. |
Response
Use OK.
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"
}'