Version 1.0.0
Droomwork IAM
Registration, sessions, API keys and the token grant.
https://sandbox.droomwork.ioDroomwork-Api-Key: dw_test_… or Authorization: Bearer dw_test_…Every request you send carries a credential. This is where you get one.
What you should know before you start
You have two kinds of credential, for different jobs. An API key is long lived; send it from your server on every request. An access token is what you get by trading a client identifier and secret: it lasts fifteen minutes and carries only the scopes you asked for. Start with a key. Use tokens when you want to hand a narrower credential to part of your system; the client identifier and secret behind them are issued by Droomwork on request, and there is no refresh token, so you ask for another when it runs out.
Your credential says which organisation, with one exception. It comes from your credential on every endpoint, and you can't act for one you don't hold a credential for. The exception is a signed-in call working on a product your company owns, where organisation names which of them you mean; GET /v1/me lists the ones you may name, and anything else answers not_found.
Scopes are granular and we check them. run:read can't approve a payroll run: looking at money and moving it are different scopes. There are three actions for each of the eight modules, plus ledger:read, and delivery:read and delivery:write for your webhook endpoints. Ask for what you need and no more.
You see a secret once. An API key is returned when you create it and never again. No endpoint reads one back.
Sandbox keys are real keys. They authenticate for real against real records. What makes one a sandbox key is the realm it reaches, and livemode tells you which.
Getting started
Register. One call gives you an organisation, an account, a session and a first sandbox key. Send the key as Droomwork-Api-Key on any sandbox request. When you want short-lived credentials, ask Droomwork for a client identifier and secret and exchange them at the token grant.
Every operation below has two samples: Direct HTTP, with nothing but your language's own client, and Client library, with ours. Same request, same answer; the switch above each sample picks one and remembers it. Which should I use?
/v1/registrations#Register an organisation
iam.registrations.create
This registers your company and the first person in it. It hands back both halves of an integration: a key your server authenticates with, and a session that signs that person into your screens with no second sign-in.
Send organisation_name, name, email and password. Copy api_key.key from the 201 first, then send it as Droomwork-Api-Key, session.token as Droomwork-Session.
Don't retry a timed-out call. The second answers conflict and the secret is gone. Recover with POST /v1/sessions, then POST /v1/account/api_keys.
You send no credential here, and nothing has to exist first. If Droomwork created your account and sent you a password, call POST /v1/account/password instead.
Send a session and the organisation you create becomes a product of the one you signed in as, which is how a company keeps each product's records and keys apart. A product needs its own contact address and cannot register a product itself.
Body
organisation_name
string
requiredThe name of the organisation you're registering. It's what organisation.name shows wherever the organisation appears, so give the name you trade under.
name
string
requiredThe person registering.
email
string · email
requiredThe address the person registering will sign in with. One address belongs to one person: an address that already has an account is refused with conflict.
password
string
requiredLong beats complicated. Refused with a reason you can show somebody, never a rule they have to guess.
Returns
The organisation, the account, a session and the first key.
object
always "registration"
requiredAlways registration. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredFalse for a record in the sandbox realm, true for one in the live realm. This answers which data a record belongs to. mocked answers where its numbers came from, and the two are separate questions. A sandbox payslip computed by the real engine is livemode false and mocked false. A deferred capability in the live realm is livemode true and mocked true.
mocked
boolean
requiredTrue when this response came from a mock, false when the engine computed the numbers.
It stays true for a capability that is still on its documented mock, even in the live realm. It answers a different question from livemode: livemode says which realm you are in, this says where the numbers came from.
organisation
RegisteredOrganisation
requiredThe organisation a registration created, and the company that owns it.
3 fields of RegisteredOrganisation
id
string
requiredThe new organisation's identifier; it starts with org_ and never changes.
name
string
requiredThe organisation_name you sent.
owned_by
string · nullable
requiredThe company that owns this organisation, or null when it owns itself. It is null unless you sent a session, and it is the organisation that session belongs to when you did.
account
AccountDetail
required2 fields of AccountDetail
email
string · email
requiredThe address this account signs in with. It's the email you send to POST /v1/sessions.
name
string
requiredThe name of the person this account belongs to, as it was given when the account was created.
session
object
requiredYou're signed in from this moment: a token to send as Droomwork-Session, and when it stops being accepted. No need to sign in again with what you just typed.
2 fields
token
string
requiredSend it as the Droomwork-Session header on the endpoints a signed-in person uses, such as GET /v1/me. It acts as this account, so keep it to yourself.
expires_at
string · date-time
requiredWhen this session stops being accepted, as an RFC 3339 timestamp in UTC. Sign in again at POST /v1/sessions after that.
api_key
object
required10 fields
id
string
requiredThe key's identifier; it starts with api_key_ and never changes. Returned by POST /v1/api_keys, POST /v1/account/api_keys and POST /v1/registrations, listed by GET /v1/api_keys and GET /v1/me, and sent as {id} to revoke it.
object
always "api_key"
requiredAlways api_key. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredFalse for a record in the sandbox realm, true for one in the live realm. This answers which data a record belongs to. mocked answers where its numbers came from, and the two are separate questions. A sandbox payslip computed by the real engine is livemode false and mocked false. A deferred capability in the live realm is livemode true and mocked true.
mocked
boolean
requiredTrue when this response came from a mock, false when the engine computed the numbers.
It stays true for a capability that is still on its documented mock, even in the live realm. It answers a different question from livemode: livemode says which realm you are in, this says where the numbers came from.
name
string
requiredWhat this key is for, as named when it was created. Use it to tell your keys apart in a list.
realm
string
requiredWhich realm this key reaches: test is the sandbox, where nothing moves, and live is real money and real filings. Sandbox keys are test and carry livemode false; live keys aren't self-serve.
testlivescopes
array of Scope
requiredWhat this key may do, as module:action scopes; a request needing one not listed here is refused. You can't issue the wildcard, so it never appears here.
revoked_at
string · date-time · nullable
optionalAlways null on a key you have just been given, which is every response that carries this schema. A key stops authenticating when you revoke it at POST /v1/api_keys/{id}/revoke or POST /v1/account/api_keys/{id}/revoke, and GET /v1/api_keys and GET /v1/account/api_keys are where it then shows the timestamp.
created_at
string · date-time
requiredWhen the record was created, as an RFC 3339 timestamp in UTC.
key
string
requiredShown here and never again. Send it as the Droomwork-Api-Key header.
next
string
requiredWhat to do with the key you were just given.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/registrations" \
-H "Droomwork-Session: $DROOMWORK_SESSION" \
-H "Content-Type: application/json" \
-d '{"organisation_name":"Adaeze Foods Ltd","name":"Ada Obi","email":"ada@adaezefoods.example","password":"correct horse battery staple"}'import { Configuration, IAMApi } from '@droomwork/sdk';
const api = new IAMApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.iamRegistrationsCreate({
iamRegistrationRequest: {"organisationName":"Adaeze Foods Ltd","name":"Ada Obi","email":"ada@adaezefoods.example","password":"correct horse battery staple"},
});const response = await fetch('https://sandbox.droomwork.io/v1/registrations', {
method: 'POST',
headers: {
'Droomwork-Session': process.env.DROOMWORK_SESSION,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"organisation_name": "Adaeze Foods Ltd",
"name": "Ada Obi",
"email": "ada@adaezefoods.example",
"password": "correct horse battery staple"
}),
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.IAMApi(client)
result = api.iam_registrations_create(body={"organisation_name": "Adaeze Foods Ltd", "name": "Ada Obi", "email": "ada@adaezefoods.example", "password": "correct horse battery staple"})import os
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/registrations',
headers={
'Droomwork-Session': os.environ['DROOMWORK_SESSION'],
},
json={"organisation_name": "Adaeze Foods Ltd", "name": "Ada Obi", "email": "ada@adaezefoods.example", "password": "correct horse battery staple"},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\IAMApi(new GuzzleHttp\Client(), $config);
$result = $api->iamRegistrationsCreate(json_decode('{"organisation_name":"Adaeze Foods Ltd","name":"Ada Obi","email":"ada@adaezefoods.example","password":"correct horse battery staple"}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/registrations');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Session: ' . getenv('DROOMWORK_SESSION'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{"organisation_name":"Adaeze Foods Ltd","name":"Ada Obi","email":"ada@adaezefoods.example","password":"correct horse battery staple"}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.IamApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
IamApi api = new IamApi(client);
var result = api.iamRegistrationsCreate(body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/registrations"))
.header("Droomwork-Session", System.getenv("DROOMWORK_SESSION"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"organisation_name": "Adaeze Foods Ltd",
"name": "Ada Obi",
"email": "ada@adaezefoods.example",
"password": "correct horse battery staple"
}
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new IAMApi(config);
var result = api.IamRegistrationsCreate(body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/registrations");
request.Headers.Add("Droomwork-Session", Environment.GetEnvironmentVariable("DROOMWORK_SESSION"));
request.Content = new StringContent("""
{
"organisation_name": "Adaeze Foods Ltd",
"name": "Ada Obi",
"email": "ada@adaezefoods.example",
"password": "correct horse battery staple"
}
""", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.IAMAPI.IamRegistrationsCreate(ctx).IamRegistrationRequest(body).Execute()body := strings.NewReader(`{
"organisation_name": "Adaeze Foods Ltd",
"name": "Ada Obi",
"email": "ada@adaezefoods.example",
"password": "correct horse battery staple"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/registrations", body)
req.Header.Set("Droomwork-Session", os.Getenv("DROOMWORK_SESSION"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "registration",
"livemode": false,
"mocked": false,
"organisation": {
"id": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"name": "Ada Obi",
"owned_by": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
},
"account": {
"email": "ada@adaezefoods.example",
"name": "Ada Obi"
},
"session": {
"token": "example",
"expires_at": "2026-09-01T09:00:00Z"
},
"api_key": {
"id": "api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "api_key",
"livemode": false,
"mocked": false,
"name": "Ada Obi",
"realm": "test",
"scopes": [
"run:read"
],
"created_at": "2026-09-01T09:00:00Z",
"revoked_at": null,
"key": "dw_test_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
},
"next": "example"
}
/v1/sessions#Sign in and get a session
iam.sessions.create
It signs one person in. It's what lets your front end act for that person: you take a password and hold a token, so no password sits in a browser and your API key stays on your server.
Send the account's email and password. Keep token for the Droomwork-Session header and expires_at for when to sign them in again; the organisation's own id comes back too, starting org_.
A password_reset_required refusal isn't a wrong password. The account still holds the password we sent, so send the person to POST /v1/account/password.
You send no credential here. Use it when you hold no session: after expires_at passes, after POST /v1/sessions/revoke, or on a new device. POST /v1/registrations and POST /v1/account/password each hand you a session, so you don't need it there. After this, GET /v1/me and POST /v1/account/api_keys.
Body
email
string · email
requiredThe address the account signs in with. An address that was never registered is refused the same way as a wrong password.
password
string
requiredThe account's own password. If it's still the one Droomwork sent, you get password_reset_required: replace it at POST /v1/account/password instead.
Returns
One session, with the signed-in person's name and their organisation, so your first screen needs no second call.
object
always "session"
requiredAlways session. Tells you which kind of record you are looking at, so one handler can read any response.
token
string
requiredSend it as the Droomwork-Session header on the endpoints a signed-in person uses, such as GET /v1/me. It isn't an API key, and nothing outside those endpoints accepts it.
expires_at
string · date-time
requiredWhen this session stops being accepted, as an RFC 3339 timestamp in UTC. Sign in again after that.
account
AccountDetail
required2 fields of AccountDetail
email
string · email
requiredThe address this account signs in with. It's the email you send to POST /v1/sessions.
name
string
requiredThe name of the person this account belongs to, as it was given when the account was created.
organisation
OrganisationDetail
required2 fields of OrganisationDetail
id
string
requiredYour organisation's identifier; it starts with org_ and never changes. It reads the same wherever organisation appears, from POST /v1/registrations to GET /v1/me. Your key names your organisation on every request, so you never send this with a key. The one place you may send it is a signed-in call that works on one of several organisations you own, such as GET /v1/account/api_keys, and there you can only name one GET /v1/me already lists.
name
string
requiredThe organisation's name, as it was given when the organisation was registered.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/sessions" \
-H "Content-Type: application/json" \
-d '{"email":"ada@adaezefoods.example","password":"correct horse battery staple"}'import { Configuration, IAMApi } from '@droomwork/sdk';
const api = new IAMApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.iamSessionsCreate({
iamSignInRequest: {"email":"ada@adaezefoods.example","password":"correct horse battery staple"},
});const response = await fetch('https://sandbox.droomwork.io/v1/sessions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"email": "ada@adaezefoods.example",
"password": "correct horse battery staple"
}),
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.IAMApi(client)
result = api.iam_sessions_create(body={"email": "ada@adaezefoods.example", "password": "correct horse battery staple"})import os
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/sessions',
headers={
},
json={"email": "ada@adaezefoods.example", "password": "correct horse battery staple"},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\IAMApi(new GuzzleHttp\Client(), $config);
$result = $api->iamSessionsCreate(json_decode('{"email":"ada@adaezefoods.example","password":"correct horse battery staple"}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sessions');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{"email":"ada@adaezefoods.example","password":"correct horse battery staple"}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.IamApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
IamApi api = new IamApi(client);
var result = api.iamSessionsCreate(body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sessions"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"email": "ada@adaezefoods.example",
"password": "correct horse battery staple"
}
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new IAMApi(config);
var result = api.IamSessionsCreate(body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/sessions");
request.Content = new StringContent("""
{
"email": "ada@adaezefoods.example",
"password": "correct horse battery staple"
}
""", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.IAMAPI.IamSessionsCreate(ctx).IamSignInRequest(body).Execute()body := strings.NewReader(`{
"email": "ada@adaezefoods.example",
"password": "correct horse battery staple"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/sessions", body)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "session",
"token": "example",
"expires_at": "2026-09-01T09:00:00Z",
"account": {
"email": "ada@adaezefoods.example",
"name": "Ada Obi"
},
"organisation": {
"id": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"name": "Ada Obi"
}
}
/v1/sessions/revoke#Sign a person out
iam.sessions.revoke
This ends one sign-in session. It gives your app a Sign out button that ends the session right away: with no call here the token works until expires_at passes, and someone at a shared computer stays signed in.
Send no request body. The session token goes in the Droomwork-Session header, not an API key. Drop the token you held and show your sign-in screen.
This ends only the session you called with. Only POST /v1/account/password ends every session an account holds, and it replaces the password as well. Any API key you issued keeps working.
Call POST /v1/registrations, POST /v1/account/password or POST /v1/sessions first; any of the three gives you the session. After this, signing in again means POST /v1/sessions.
Returns
The session is ended.
object
always "session"
requiredAlways session. Tells you which kind of record you are looking at, so one handler can read any response.
revoked
always true
requiredAlways true when this answers; a session that could not be ended is refused instead.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/sessions/revoke" \
-H "Droomwork-Session: $DROOMWORK_SESSION"import { Configuration, IAMApi } from '@droomwork/sdk';
const api = new IAMApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.iamSessionsRevoke({});const response = await fetch('https://sandbox.droomwork.io/v1/sessions/revoke', {
method: 'POST',
headers: {
'Droomwork-Session': process.env.DROOMWORK_SESSION,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.IAMApi(client)
result = api.iam_sessions_revoke()import os
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/sessions/revoke',
headers={
'Droomwork-Session': os.environ['DROOMWORK_SESSION'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\IAMApi(new GuzzleHttp\Client(), $config);
$result = $api->iamSessionsRevoke();<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sessions/revoke');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Session: ' . getenv('DROOMWORK_SESSION'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.IamApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
IamApi api = new IamApi(client);
var result = api.iamSessionsRevoke();var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sessions/revoke"))
.header("Droomwork-Session", System.getenv("DROOMWORK_SESSION"))
.method("POST", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new IAMApi(config);
var result = api.IamSessionsRevoke();using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/sessions/revoke");
request.Headers.Add("Droomwork-Session", Environment.GetEnvironmentVariable("DROOMWORK_SESSION"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.IAMAPI.IamSessionsRevoke(ctx).Execute()req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/sessions/revoke", nil)
req.Header.Set("Droomwork-Session", os.Getenv("DROOMWORK_SESSION"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "session",
"revoked": true
}
/v1/oauth/token#Trade client credentials for a short-lived token
iam.oauth.token
This gets you a credential to hand to something that should not hold your API key: a build script, a reporting service, a job you don't fully trust. What you hand over expires by itself, so you never pass your API key around and you never have to revoke what you handed out.
Send grant_type as client_credentials with your client_id and client_secret, and access_token comes back. Ask for scopes the client lacks and you get forbidden.
You send no credential here, and the client in the body is the one thing that must already exist. After this you can call POST /v1/api_keys and GET /v1/api_keys, and the rest of IAM a token reaches.
Body
grant_type
always "client_credentials"
requiredAlways client_credentials, the one grant this endpoint supports. Anything else, or leaving it out, is refused with invalid_request.
client_id
string
requiredThe identifier of a client Droomwork issued you on request, sent with its client_secret; no endpoint creates or lists clients, and the sandbox holds none until one is issued. An unknown identifier and a wrong secret are refused identically, so the response can't tell you which clients exist.
client_secret
string
requiredThe secret issued with client_id. Keep it on your server; a wrong secret is refused the same way as an unknown client.
scopes
array of Scope
optionalA subset of what the client holds. Omitted means all of them.
Returns
The token.
object
always "token"
requiredAlways token. Tells you which kind of record you are looking at, so one handler can read any response.
access_token
string
requiredThe bearer token. Send it as Authorization: Bearer <token> on every request until expires_in runs out, then ask for another with the same client identifier and secret; there is no refresh token.
token_type
always "Bearer"
requiredAlways Bearer. Send access_token in the Authorization header with that prefix.
expires_in
integer
requiredSeconds. Fifteen minutes.
scopes
array of Scope
requiredWhat this token may do, as module:action scopes. Exactly what you asked for, or everything the client holds if you asked for none.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/oauth/token" \
-H "Content-Type: application/json" \
-d '{"grant_type":"client_credentials","client_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","client_secret":"example","scopes":["run:read"]}'import { Configuration, IAMApi } from '@droomwork/sdk';
const api = new IAMApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.iamOauthToken({
iamTokenRequest: {"grantType":"client_credentials","clientId":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","clientSecret":"example","scopes":["run:read"]},
});const response = await fetch('https://sandbox.droomwork.io/v1/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"grant_type": "client_credentials",
"client_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"client_secret": "example",
"scopes": [
"run:read"
]
}),
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.IAMApi(client)
result = api.iam_oauth_token(body={"grant_type": "client_credentials", "client_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "client_secret": "example", "scopes": ["run:read"]})import os
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/oauth/token',
headers={
},
json={"grant_type": "client_credentials", "client_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "client_secret": "example", "scopes": ["run:read"]},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\IAMApi(new GuzzleHttp\Client(), $config);
$result = $api->iamOauthToken(json_decode('{"grant_type":"client_credentials","client_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","client_secret":"example","scopes":["run:read"]}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/oauth/token');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{"grant_type":"client_credentials","client_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","client_secret":"example","scopes":["run:read"]}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.IamApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
IamApi api = new IamApi(client);
var result = api.iamOauthToken(body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/oauth/token"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"grant_type": "client_credentials",
"client_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"client_secret": "example",
"scopes": [
"run:read"
]
}
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new IAMApi(config);
var result = api.IamOauthToken(body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/oauth/token");
request.Content = new StringContent("""
{
"grant_type": "client_credentials",
"client_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"client_secret": "example",
"scopes": [
"run:read"
]
}
""", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.IAMAPI.IamOauthToken(ctx).IamTokenRequest(body).Execute()body := strings.NewReader(`{
"grant_type": "client_credentials",
"client_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"client_secret": "example",
"scopes": [
"run:read"
]
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/oauth/token", body)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "token",
"access_token": "example",
"token_type": "Bearer",
"expires_in": 1,
"scopes": [
"run:read"
]
}
/v1/api_keys#See every key your organisation holds
iam.api_keys.list
This lists every API key your organisation holds: what each is called, what it may do in scopes, and whether it has been stopped. It's the only list of your keys a server can read on its own, with no person signed in.
You get one record for each key in data, newest first, and no secret among them: a key's secret comes back once, from POST /v1/api_keys. Keep each id for POST /v1/api_keys/{id}/revoke.
Check revoked_at on every row: a stopped key still looks like a working one until you do.
Call POST /v1/registrations, POST /v1/oauth/token or POST /v1/account/api_keys first; any of them gives you the credential this call needs. Nothing else waits on this call.
Returns
The key records, each with a created_at, so you can find the ones older than your rotation window.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of ApiKey
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
9 fields of ApiKey
id
string
requiredThe key's identifier; it starts with api_key_ and never changes. Returned by POST /v1/api_keys, POST /v1/account/api_keys and POST /v1/registrations, listed by GET /v1/api_keys and GET /v1/me, and sent as {id} to revoke it.
object
always "api_key"
requiredAlways api_key. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredFalse for a record in the sandbox realm, true for one in the live realm. This answers which data a record belongs to. mocked answers where its numbers came from, and the two are separate questions. A sandbox payslip computed by the real engine is livemode false and mocked false. A deferred capability in the live realm is livemode true and mocked true.
mocked
boolean
requiredTrue when this response came from a mock, false when the engine computed the numbers.
It stays true for a capability that is still on its documented mock, even in the live realm. It answers a different question from livemode: livemode says which realm you are in, this says where the numbers came from.
name
string
requiredWhat this key is for, as named when it was created. Use it to tell your keys apart in a list.
realm
string
requiredWhich realm this key reaches: test is the sandbox, where nothing moves, and live is real money and real filings. Sandbox keys are test and carry livemode false; live keys aren't self-serve.
testlivescopes
array of Scope
requiredWhat this key may do, as module:action scopes; a request needing one not listed here is refused. You can't issue the wildcard, so it never appears here.
revoked_at
string · date-time · nullable
optionalWhen this key stopped authenticating, as an RFC 3339 timestamp in UTC, or null while it still works. A revoked key stays listed with this set, so its history stays with it.
created_at
string · date-time
requiredWhen the record was created, as an RFC 3339 timestamp in UTC.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
organisation
string
optionalWhich organisation these keys belong to. GET /v1/account/api_keys sets it, so a page you fetched for one of your organisations can be told from a page you fetched for another without remembering which request produced which. GET /v1/api_keys leaves it out: that call answers for the organisation on your key and there is only ever one.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/api_keys" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, IAMApi } from '@droomwork/sdk';
const api = new IAMApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.iamApiKeysList({});const response = await fetch('https://sandbox.droomwork.io/v1/api_keys', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.IAMApi(client)
result = api.iam_api_keys_list()import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/api_keys',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\IAMApi(new GuzzleHttp\Client(), $config);
$result = $api->iamApiKeysList();<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/api_keys');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.IamApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
IamApi api = new IamApi(client);
var result = api.iamApiKeysList();var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/api_keys"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new IAMApi(config);
var result = api.IamApiKeysList();using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/api_keys");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.IAMAPI.IamApiKeysList(ctx).Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/api_keys", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "api_key",
"livemode": false,
"mocked": false,
"name": "Ada Obi",
"realm": "test",
"scopes": [
"run:read"
],
"created_at": "2026-09-01T09:00:00Z",
"revoked_at": "2026-09-01T09:00:00Z"
}
],
"has_more": true,
"organisation": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
/v1/api_keys#Give part of your system its own key
iam.api_keys.create
This gives one part of your system its own API key, holding only the powers you decide. When a key leaks you revoke that one, and the rest keep working.
Send name and scopes, and copy key from the 201. The id beside it is what you send to revoke this key later.
Leave scopes out or empty and the key holds run:read only. org_id or organisation_id in the body gets forbidden.
Call POST /v1/registrations, POST /v1/oauth/token or POST /v1/account/api_keys first; any of them gives you the credential this call needs. After this you can call POST /v1/api_keys/{id}/revoke with the id that came back, and the rest of IAM your credential reaches.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body optional
name
string
optionalWhat this key is for, so a list of them can be read later.
scopes
array of Scope
optionalThe scopes the key should carry, each module:action; name only what it needs. A value that isn't a scope refuses the whole request, and you can't issue the wildcard.
organisation
string
optionalWhich of your organisations the key is for, when you hold more than one. Leave it out and the key is for the one you signed in as. Only POST /v1/account/api_keys reads this, because only a session knows a person; POST /v1/api_keys takes its organisation from your key and refuses to be pointed anywhere else. Name an organisation you don't own and you get not_found, the same answer as an organisation that doesn't exist. GET /v1/me lists the ones you can name.
Returns
The key record, created_at included, so you can store when it was issued without listing your keys.
id
string
requiredThe key's identifier; it starts with api_key_ and never changes. Returned by POST /v1/api_keys, POST /v1/account/api_keys and POST /v1/registrations, listed by GET /v1/api_keys and GET /v1/me, and sent as {id} to revoke it.
object
always "api_key"
requiredAlways api_key. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredFalse for a record in the sandbox realm, true for one in the live realm. This answers which data a record belongs to. mocked answers where its numbers came from, and the two are separate questions. A sandbox payslip computed by the real engine is livemode false and mocked false. A deferred capability in the live realm is livemode true and mocked true.
mocked
boolean
requiredTrue when this response came from a mock, false when the engine computed the numbers.
It stays true for a capability that is still on its documented mock, even in the live realm. It answers a different question from livemode: livemode says which realm you are in, this says where the numbers came from.
name
string
requiredWhat this key is for, as named when it was created. Use it to tell your keys apart in a list.
realm
string
requiredWhich realm this key reaches: test is the sandbox, where nothing moves, and live is real money and real filings. Sandbox keys are test and carry livemode false; live keys aren't self-serve.
testlivescopes
array of Scope
requiredWhat this key may do, as module:action scopes; a request needing one not listed here is refused. You can't issue the wildcard, so it never appears here.
revoked_at
string · date-time · nullable
optionalAlways null on a key you have just been given, which is every response that carries this schema. A key stops authenticating when you revoke it at POST /v1/api_keys/{id}/revoke or POST /v1/account/api_keys/{id}/revoke, and GET /v1/api_keys and GET /v1/account/api_keys are where it then shows the timestamp.
created_at
string · date-time
requiredWhen the record was created, as an RFC 3339 timestamp in UTC.
key
string
requiredShown here and never again. Send it as the Droomwork-Api-Key header.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/api_keys" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"name":"CI","scopes":["run:read"],"organisation":"org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}'import { Configuration, IAMApi } from '@droomwork/sdk';
const api = new IAMApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.iamApiKeysCreate({
idempotencyKey: crypto.randomUUID(),
iamApiKeyCreateRequest: {"name":"CI","scopes":["run:read"],"organisation":"org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},
});const response = await fetch('https://sandbox.droomwork.io/v1/api_keys', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"name": "CI",
"scopes": [
"run:read"
],
"organisation": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}),
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.IAMApi(client)
result = api.iam_api_keys_create(body={"name": "CI", "scopes": ["run:read"], "organisation": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"})import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/api_keys',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"name": "CI", "scopes": ["run:read"], "organisation": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\IAMApi(new GuzzleHttp\Client(), $config);
$result = $api->iamApiKeysCreate($idempotencyKey, json_decode('{"name":"CI","scopes":["run:read"],"organisation":"org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/api_keys');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => '{"name":"CI","scopes":["run:read"],"organisation":"org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.IamApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
IamApi api = new IamApi(client);
var result = api.iamApiKeysCreate(idempotencyKey, body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/api_keys"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"name": "CI",
"scopes": [
"run:read"
],
"organisation": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new IAMApi(config);
var result = api.IamApiKeysCreate(idempotencyKey, body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/api_keys");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"name": "CI",
"scopes": [
"run:read"
],
"organisation": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
""", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.IAMAPI.IamApiKeysCreate(ctx).IdempotencyKey(key).IamApiKeyCreateRequest(body).Execute()body := strings.NewReader(`{
"name": "CI",
"scopes": [
"run:read"
],
"organisation": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/api_keys", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "api_key",
"livemode": false,
"mocked": false,
"name": "Ada Obi",
"realm": "test",
"scopes": [
"run:read"
],
"created_at": "2026-09-01T09:00:00Z",
"revoked_at": null,
"key": "dw_test_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
/v1/api_keys/{id}/revoke#Stop a key from working
iam.api_keys.revoke
This stops one API key working, straight away. An API key lasts until you end it, and your own code can end it with no person signed in.
Send the key's id in the path, not the secret you stored. Send no request body. The one field to keep is revoked_at.
Revoking the same key twice answers not_found, so retry a timed-out call with its original Idempotency-Key.
With a session, POST /v1/account/api_keys/{id}/revoke does the same and takes no Idempotency-Key.
Call POST /v1/registrations, POST /v1/api_keys or POST /v1/account/api_keys first; any of the three issues a key and hands you the id you send here. Nothing else waits on this call.
Path parameters
id
string
requiredThe key's id, which starts with api_key_, as returned by POST /v1/api_keys or POST /v1/account/api_keys when you made it and listed by GET /v1/api_keys and GET /v1/me. A key belonging to another organisation answers not_found.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Returns
The key's whole record, not just a confirmation, so your own keys list can replace that row.
id
string
requiredThe key's identifier; it starts with api_key_ and never changes. Returned by POST /v1/api_keys, POST /v1/account/api_keys and POST /v1/registrations, listed by GET /v1/api_keys and GET /v1/me, and sent as {id} to revoke it.
object
always "api_key"
requiredAlways api_key. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredFalse for a record in the sandbox realm, true for one in the live realm. This answers which data a record belongs to. mocked answers where its numbers came from, and the two are separate questions. A sandbox payslip computed by the real engine is livemode false and mocked false. A deferred capability in the live realm is livemode true and mocked true.
mocked
boolean
requiredTrue when this response came from a mock, false when the engine computed the numbers.
It stays true for a capability that is still on its documented mock, even in the live realm. It answers a different question from livemode: livemode says which realm you are in, this says where the numbers came from.
name
string
requiredWhat this key is for, as named when it was created. Use it to tell your keys apart in a list.
realm
string
requiredWhich realm this key reaches: test is the sandbox, where nothing moves, and live is real money and real filings. Sandbox keys are test and carry livemode false; live keys aren't self-serve.
testlivescopes
array of Scope
requiredWhat this key may do, as module:action scopes; a request needing one not listed here is refused. You can't issue the wildcard, so it never appears here.
revoked_at
string · date-time · nullable
optionalWhen this key stopped authenticating, as an RFC 3339 timestamp in UTC, or null while it still works. A revoked key stays listed with this set, so its history stays with it.
created_at
string · date-time
requiredWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/api_keys/api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/revoke" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"import { Configuration, IAMApi } from '@droomwork/sdk';
const api = new IAMApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.iamApiKeysRevoke({ id: 'api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/api_keys/api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/revoke', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Idempotency-Key': crypto.randomUUID(),
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.IAMApi(client)
result = api.iam_api_keys_revoke(id='api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/api_keys/api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/revoke',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\IAMApi(new GuzzleHttp\Client(), $config);
$result = $api->iamApiKeysRevoke(id: 'api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/api_keys/api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/revoke');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.IamApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
IamApi api = new IamApi(client);
var result = api.iamApiKeysRevoke("api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/api_keys/api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/revoke"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new IAMApi(config);
var result = api.IamApiKeysRevoke(id: "api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/api_keys/api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/revoke");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.IAMAPI.IamApiKeysRevoke(ctx, "api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/api_keys/api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/revoke", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "api_key",
"livemode": false,
"mocked": false,
"name": "Ada Obi",
"realm": "test",
"scopes": [
"run:read"
],
"created_at": "2026-09-01T09:00:00Z",
"revoked_at": "2026-09-01T09:00:00Z"
}
/v1/support_access#When Droomwork looked at your data
iam.support_access.list
You see every time a Droomwork staff member read your organisation's data, newest first. It lets you line up every look against your own audit trail by at, and show what each one was for.
You get one row for each look, and each row's id starts with sta_. No row names who looked; keep that id to quote back to us.
Raise limit to see further back. Every read is written before it's answered, so an empty list means nobody looked.
Call POST /v1/registrations, POST /v1/oauth/token or POST /v1/account/api_keys first; any of them gives you the credential this call needs. Nothing else waits on this call.
Query parameters
limit
integer
optionalHow many records to return, from 1 to 200, newest first. Leave it out to get 50.
Returns
When Droomwork looked, and what for.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of SupportAccess
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
5 fields of SupportAccess
id
string
requiredThis look's identifier; it starts with sta_ and is returned only by GET /v1/support_access. The record never names who looked, so if you need to take something further, quote this id to us and we can identify the person internally.
object
always "support_access"
requiredAlways support_access. Tells you which kind of record you are looking at, so one handler can read any response.
at
string · date-time
requiredWhen the read happened, as an RFC 3339 timestamp in UTC. Line it up with the entry marked actor_type: staff in your own audit trail.
action
string
requiredWhat was read, as the method and the path. The same shape your audit trail uses, so you can line the two up.
purpose
one of
optionalNull for a look recorded before purposes were kept.
SupportAccessPurposeorhas_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional)
curl -X GET "https://sandbox.droomwork.io/v1/support_access?limit=25" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, IAMApi } from '@droomwork/sdk';
const api = new IAMApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional)
const result = await api.iamSupportAccessList({ limit: 25 });// query parameters: limit (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/support_access?limit=25', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.IAMApi(client)
# query parameters: limit (optional)
result = api.iam_support_access_list(limit=25)import os
import requests
# query parameters: limit (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/support_access?limit=25',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\IAMApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional)
$result = $api->iamSupportAccessList(limit: 25);<?php
// query parameters: limit (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/support_access?limit=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.IamApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
IamApi api = new IamApi(client);
// query parameters: limit (optional)
var result = api.iamSupportAccessList(25);// query parameters: limit (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/support_access?limit=25"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new IAMApi(config);
// query parameters: limit (optional)
var result = api.IamSupportAccessList(limit: 25);// query parameters: limit (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/support_access?limit=25");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: limit (optional)
result, _, err := client.IAMAPI.IamSupportAccessList(ctx).Limit(25).Execute()// query parameters: limit (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/support_access?limit=25", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "sta_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "support_access",
"at": "2026-09-01T09:00:00Z",
"action": "GET /v1/payroll/runs",
"purpose": "support_ticket"
}
],
"has_more": true
}
/v1/me#Who this credential belongs to
iam.me.retrieve
This answers who a session token belongs to: the account that signed in, the organisation it belongs to, that organisation's API keys, and every organisation this sign-in can work on. So your account screen draws from this one response.
Send the session token in the Droomwork-Session header. An API key, or an expired or revoked session, gets unauthenticated. Each api_keys[].id is the {id} for revoking that key.
Read organisations if you own more than one. Those id values are the only ones a signed-in call will accept as organisation, so name anything else and you get not_found.
api_keys holds the keys of the organisation you signed in as, not one person's. For a product's keys, call GET /v1/account/api_keys and name it.
Call POST /v1/registrations, POST /v1/account/password or POST /v1/sessions first for the session.
Returns
The account, and a created_at on each entry in organisations, so you can show a product's registration date.
object
always "account"
requiredAlways account. Tells you which kind of record you are looking at, so one handler can read any response.
account
AccountDetail
required2 fields of AccountDetail
email
string · email
requiredThe address this account signs in with. It's the email you send to POST /v1/sessions.
name
string
requiredThe name of the person this account belongs to, as it was given when the account was created.
organisation
OrganisationDetail
required2 fields of OrganisationDetail
id
string
requiredYour organisation's identifier; it starts with org_ and never changes. It reads the same wherever organisation appears, from POST /v1/registrations to GET /v1/me. Your key names your organisation on every request, so you never send this with a key. The one place you may send it is a signed-in call that works on one of several organisations you own, such as GET /v1/account/api_keys, and there you can only name one GET /v1/me already lists.
name
string
requiredThe organisation's name, as it was given when the organisation was registered.
api_keys
array of ApiKey
requiredEvery API key the organisation holds, without their secrets; revoked keys stay listed with revoked_at set. No endpoint returns a secret again. These are the keys of the organisation in organisation only. For a product's keys, call GET /v1/account/api_keys and name it.
9 fields of ApiKey
id
string
requiredThe key's identifier; it starts with api_key_ and never changes. Returned by POST /v1/api_keys, POST /v1/account/api_keys and POST /v1/registrations, listed by GET /v1/api_keys and GET /v1/me, and sent as {id} to revoke it.
object
always "api_key"
requiredAlways api_key. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredFalse for a record in the sandbox realm, true for one in the live realm. This answers which data a record belongs to. mocked answers where its numbers came from, and the two are separate questions. A sandbox payslip computed by the real engine is livemode false and mocked false. A deferred capability in the live realm is livemode true and mocked true.
mocked
boolean
requiredTrue when this response came from a mock, false when the engine computed the numbers.
It stays true for a capability that is still on its documented mock, even in the live realm. It answers a different question from livemode: livemode says which realm you are in, this says where the numbers came from.
name
string
requiredWhat this key is for, as named when it was created. Use it to tell your keys apart in a list.
realm
string
requiredWhich realm this key reaches: test is the sandbox, where nothing moves, and live is real money and real filings. Sandbox keys are test and carry livemode false; live keys aren't self-serve.
testlivescopes
array of Scope
requiredWhat this key may do, as module:action scopes; a request needing one not listed here is refused. You can't issue the wildcard, so it never appears here.
revoked_at
string · date-time · nullable
optionalWhen this key stopped authenticating, as an RFC 3339 timestamp in UTC, or null while it still works. A revoked key stays listed with this set, so its history stays with it.
created_at
string · date-time
requiredWhen the record was created, as an RFC 3339 timestamp in UTC.
organisations
array of ReachableOrganisation
requiredEvery organisation this sign-in can work on: the company in organisation first, then each product it owns, oldest first. Always at least one entry, so you have one list to read through and no special case for the company itself. Signing in to a product gives you one entry, that product, because a product never reaches the company above it or a product beside it. Pass any id here as organisation on a signed-in call.
6 fields of ReachableOrganisation
id
string
requiredThe organisation's identifier. Send it as organisation on a signed-in call to work on this one instead of the company you signed in as.
object
always "organisation"
requiredAlways organisation. Tells you which kind of record you are looking at, so one handler can read any response.
name
string
requiredThe name this organisation was registered with.
realm
string
requiredAlways test while the sandbox is what we serve. A product is always in the same realm as the company that owns it, so this is never a mixture.
owned_by
string · nullable
requiredThe company that owns this organisation, or null when it owns itself. The entry you signed in as is the one with null, and every other entry names it here.
created_at
string · date-time
requiredWhen this organisation was registered, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/me" \
-H "Droomwork-Session: $DROOMWORK_SESSION"import { Configuration, IAMApi } from '@droomwork/sdk';
const api = new IAMApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.iamMeRetrieve({});const response = await fetch('https://sandbox.droomwork.io/v1/me', {
method: 'GET',
headers: {
'Droomwork-Session': process.env.DROOMWORK_SESSION,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.IAMApi(client)
result = api.iam_me_retrieve()import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/me',
headers={
'Droomwork-Session': os.environ['DROOMWORK_SESSION'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\IAMApi(new GuzzleHttp\Client(), $config);
$result = $api->iamMeRetrieve();<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/me');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Session: ' . getenv('DROOMWORK_SESSION'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.IamApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
IamApi api = new IamApi(client);
var result = api.iamMeRetrieve();var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/me"))
.header("Droomwork-Session", System.getenv("DROOMWORK_SESSION"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new IAMApi(config);
var result = api.IamMeRetrieve();using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/me");
request.Headers.Add("Droomwork-Session", Environment.GetEnvironmentVariable("DROOMWORK_SESSION"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.IAMAPI.IamMeRetrieve(ctx).Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/me", nil)
req.Header.Set("Droomwork-Session", os.Getenv("DROOMWORK_SESSION"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "account",
"account": {
"email": "ada@adaezefoods.example",
"name": "Ada Obi"
},
"organisation": {
"id": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"name": "Ada Obi"
},
"api_keys": [
{
"id": "api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "api_key",
"livemode": false,
"mocked": false,
"name": "Ada Obi",
"realm": "test",
"scopes": [
"run:read"
],
"created_at": "2026-09-01T09:00:00Z",
"revoked_at": "2026-09-01T09:00:00Z"
}
],
"organisations": [
{
"id": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "organisation",
"name": "Ada Obi",
"realm": "example",
"owned_by": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"created_at": "2026-09-01T09:00:00Z"
}
]
}
/v1/account/password#Replace the password you were sent
iam.account.password.replace
It swaps the password we sent for one only you know, and it signs you in too. So your setup code can finish the start-up alone: set the real password, then take the account's first API key, with no sign-in call between.
Send email, current_password and new_password. Keep token as Droomwork-Session. Reusing the sent password is invalid_request, an expired one unauthenticated. All sessions end.
You send no credential here. You do need an account we created for you and the password we sent with it, which won't sign you in. After this you can call POST /v1/account/api_keys and GET /v1/me. GET /v1/support_access isn't one of them, so make a key first.
Body
email
string · email
requiredThe address of the account whose password you're replacing: the one the starting password was sent for.
current_password
string
requiredThe password Droomwork sent you.
new_password
string
requiredOne only you know. Twelve characters at least.
Returns
The password is yours, and this is the session for it.
object
always "session"
requiredAlways session. Tells you which kind of record you are looking at, so one handler can read any response.
token
string
requiredSend it as the Droomwork-Session header on the endpoints a signed-in person uses, such as GET /v1/me. It isn't an API key, and nothing outside those endpoints accepts it.
expires_at
string · date-time
requiredWhen this session stops being accepted, as an RFC 3339 timestamp in UTC. Sign in again after that.
account
AccountDetail
required2 fields of AccountDetail
email
string · email
requiredThe address this account signs in with. It's the email you send to POST /v1/sessions.
name
string
requiredThe name of the person this account belongs to, as it was given when the account was created.
organisation
OrganisationDetail
required2 fields of OrganisationDetail
id
string
requiredYour organisation's identifier; it starts with org_ and never changes. It reads the same wherever organisation appears, from POST /v1/registrations to GET /v1/me. Your key names your organisation on every request, so you never send this with a key. The one place you may send it is a signed-in call that works on one of several organisations you own, such as GET /v1/account/api_keys, and there you can only name one GET /v1/me already lists.
name
string
requiredThe organisation's name, as it was given when the organisation was registered.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/account/password" \
-H "Content-Type: application/json" \
-d '{"email":"ada@adaezefoods.example","current_password":"example","new_password":"example correct"}'import { Configuration, IAMApi } from '@droomwork/sdk';
const api = new IAMApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.iamAccountPasswordReplace({
iamAccountPasswordRequest: {"email":"ada@adaezefoods.example","currentPassword":"example","newPassword":"example correct"},
});const response = await fetch('https://sandbox.droomwork.io/v1/account/password', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"email": "ada@adaezefoods.example",
"current_password": "example",
"new_password": "example correct"
}),
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.IAMApi(client)
result = api.iam_account_password_replace(body={"email": "ada@adaezefoods.example", "current_password": "example", "new_password": "example correct"})import os
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/account/password',
headers={
},
json={"email": "ada@adaezefoods.example", "current_password": "example", "new_password": "example correct"},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\IAMApi(new GuzzleHttp\Client(), $config);
$result = $api->iamAccountPasswordReplace(json_decode('{"email":"ada@adaezefoods.example","current_password":"example","new_password":"example correct"}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/account/password');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{"email":"ada@adaezefoods.example","current_password":"example","new_password":"example correct"}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.IamApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
IamApi api = new IamApi(client);
var result = api.iamAccountPasswordReplace(body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/account/password"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"email": "ada@adaezefoods.example",
"current_password": "example",
"new_password": "example correct"
}
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new IAMApi(config);
var result = api.IamAccountPasswordReplace(body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/account/password");
request.Content = new StringContent("""
{
"email": "ada@adaezefoods.example",
"current_password": "example",
"new_password": "example correct"
}
""", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.IAMAPI.IamAccountPasswordReplace(ctx).IamAccountPasswordRequest(body).Execute()body := strings.NewReader(`{
"email": "ada@adaezefoods.example",
"current_password": "example",
"new_password": "example correct"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/account/password", body)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "session",
"token": "example",
"expires_at": "2026-09-01T09:00:00Z",
"account": {
"email": "ada@adaezefoods.example",
"name": "Ada Obi"
},
"organisation": {
"id": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"name": "Ada Obi"
}
}
/v1/account/api_keys#Your keys, without holding one
iam.account.api_keys.list
This lists the API keys of one of your organisations, from a signed in person's session. So the keys page in your own product can show what exists, which scopes each key carries and which have been revoked, without anybody holding a key to ask with.
Send your session token as Droomwork-Session, not an API key. Add organisation to list the keys of a product you own; leave it out for the organisation you signed in as.
GET /v1/api_keys lists the same records for whoever holds a key of that organisation. This one needs a person instead, which is the only way in for a product whose key you have lost.
GET /v1/me lists the organisations you can name here.
Query parameters
organisation
string
optionalWhich of your organisations to list. Leave it out for the one you signed in as. Name an organisation you don't own and you get not_found, the same answer as one that doesn't exist, because an identifier you cannot reach is not an identifier you should be able to confirm.
Returns
The organisation's key records, each carrying a created_at, so you can show when a key was issued.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of ApiKey
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
9 fields of ApiKey
id
string
requiredThe key's identifier; it starts with api_key_ and never changes. Returned by POST /v1/api_keys, POST /v1/account/api_keys and POST /v1/registrations, listed by GET /v1/api_keys and GET /v1/me, and sent as {id} to revoke it.
object
always "api_key"
requiredAlways api_key. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredFalse for a record in the sandbox realm, true for one in the live realm. This answers which data a record belongs to. mocked answers where its numbers came from, and the two are separate questions. A sandbox payslip computed by the real engine is livemode false and mocked false. A deferred capability in the live realm is livemode true and mocked true.
mocked
boolean
requiredTrue when this response came from a mock, false when the engine computed the numbers.
It stays true for a capability that is still on its documented mock, even in the live realm. It answers a different question from livemode: livemode says which realm you are in, this says where the numbers came from.
name
string
requiredWhat this key is for, as named when it was created. Use it to tell your keys apart in a list.
realm
string
requiredWhich realm this key reaches: test is the sandbox, where nothing moves, and live is real money and real filings. Sandbox keys are test and carry livemode false; live keys aren't self-serve.
testlivescopes
array of Scope
requiredWhat this key may do, as module:action scopes; a request needing one not listed here is refused. You can't issue the wildcard, so it never appears here.
revoked_at
string · date-time · nullable
optionalWhen this key stopped authenticating, as an RFC 3339 timestamp in UTC, or null while it still works. A revoked key stays listed with this set, so its history stays with it.
created_at
string · date-time
requiredWhen the record was created, as an RFC 3339 timestamp in UTC.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
organisation
string
optionalWhich organisation these keys belong to. GET /v1/account/api_keys sets it, so a page you fetched for one of your organisations can be told from a page you fetched for another without remembering which request produced which. GET /v1/api_keys leaves it out: that call answers for the organisation on your key and there is only ever one.
Other responses
Errors it can return
# query parameters: organisation (optional)
curl -X GET "https://sandbox.droomwork.io/v1/account/api_keys?organisation=org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
-H "Droomwork-Session: $DROOMWORK_SESSION"import { Configuration, IAMApi } from '@droomwork/sdk';
const api = new IAMApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: organisation (optional)
const result = await api.iamAccountApiKeysList({ organisation: 'org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });// query parameters: organisation (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/account/api_keys?organisation=org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
method: 'GET',
headers: {
'Droomwork-Session': process.env.DROOMWORK_SESSION,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.IAMApi(client)
# query parameters: organisation (optional)
result = api.iam_account_api_keys_list(organisation='org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
# query parameters: organisation (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/account/api_keys?organisation=org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
headers={
'Droomwork-Session': os.environ['DROOMWORK_SESSION'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\IAMApi(new GuzzleHttp\Client(), $config);
# query parameters: organisation (optional)
$result = $api->iamAccountApiKeysList(organisation: 'org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
// query parameters: organisation (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/account/api_keys?organisation=org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Session: ' . getenv('DROOMWORK_SESSION'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.IamApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
IamApi api = new IamApi(client);
// query parameters: organisation (optional)
var result = api.iamAccountApiKeysList("org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");// query parameters: organisation (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/account/api_keys?organisation=org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"))
.header("Droomwork-Session", System.getenv("DROOMWORK_SESSION"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new IAMApi(config);
// query parameters: organisation (optional)
var result = api.IamAccountApiKeysList(organisation: "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");// query parameters: organisation (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/account/api_keys?organisation=org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
request.Headers.Add("Droomwork-Session", Environment.GetEnvironmentVariable("DROOMWORK_SESSION"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: organisation (optional)
result, _, err := client.IAMAPI.IamAccountApiKeysList(ctx).Organisation("org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()// query parameters: organisation (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/account/api_keys?organisation=org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", nil)
req.Header.Set("Droomwork-Session", os.Getenv("DROOMWORK_SESSION"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "api_key",
"livemode": false,
"mocked": false,
"name": "Ada Obi",
"realm": "test",
"scopes": [
"run:read"
],
"created_at": "2026-09-01T09:00:00Z",
"revoked_at": "2026-09-01T09:00:00Z"
}
],
"has_more": true,
"organisation": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
/v1/account/api_keys#Make an API key while signed in
iam.account.api_keys.create
Make an API key from a signed in person's session. No other call issues a key from a session for an organisation you already hold, so you can build a keys page in your own product: each server or job gets its own key, named and scoped, and revoked there when it leaks.
Use name to tell keys apart. Leave scopes out, or empty, and the key carries every scope we issue. POST /v1/api_keys defaults to run:read, so set scopes on both. Copy key from the 201.
A key acts as the organisation it was issued for and no other, so two of your organisations take two keys.
Stop one at POST /v1/account/api_keys/{id}/revoke.
Body optional
name
string
optionalWhat this key is for, so a list of them can be read later.
scopes
array of Scope
optionalThe scopes the key should carry, each module:action; name only what it needs. A value that isn't a scope refuses the whole request, and you can't issue the wildcard.
organisation
string
optionalWhich of your organisations the key is for, when you hold more than one. Leave it out and the key is for the one you signed in as. Only POST /v1/account/api_keys reads this, because only a session knows a person; POST /v1/api_keys takes its organisation from your key and refuses to be pointed anywhere else. Name an organisation you don't own and you get not_found, the same answer as an organisation that doesn't exist. GET /v1/me lists the ones you can name.
Returns
The key record, with a created_at you never sent, so you can store when this key was issued.
id
string
requiredThe key's identifier; it starts with api_key_ and never changes. Returned by POST /v1/api_keys, POST /v1/account/api_keys and POST /v1/registrations, listed by GET /v1/api_keys and GET /v1/me, and sent as {id} to revoke it.
object
always "api_key"
requiredAlways api_key. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredFalse for a record in the sandbox realm, true for one in the live realm. This answers which data a record belongs to. mocked answers where its numbers came from, and the two are separate questions. A sandbox payslip computed by the real engine is livemode false and mocked false. A deferred capability in the live realm is livemode true and mocked true.
mocked
boolean
requiredTrue when this response came from a mock, false when the engine computed the numbers.
It stays true for a capability that is still on its documented mock, even in the live realm. It answers a different question from livemode: livemode says which realm you are in, this says where the numbers came from.
name
string
requiredWhat this key is for, as named when it was created. Use it to tell your keys apart in a list.
realm
string
requiredWhich realm this key reaches: test is the sandbox, where nothing moves, and live is real money and real filings. Sandbox keys are test and carry livemode false; live keys aren't self-serve.
testlivescopes
array of Scope
requiredWhat this key may do, as module:action scopes; a request needing one not listed here is refused. You can't issue the wildcard, so it never appears here.
revoked_at
string · date-time · nullable
optionalAlways null on a key you have just been given, which is every response that carries this schema. A key stops authenticating when you revoke it at POST /v1/api_keys/{id}/revoke or POST /v1/account/api_keys/{id}/revoke, and GET /v1/api_keys and GET /v1/account/api_keys are where it then shows the timestamp.
created_at
string · date-time
requiredWhen the record was created, as an RFC 3339 timestamp in UTC.
key
string
requiredShown here and never again. Send it as the Droomwork-Api-Key header.
organisation
string
requiredWhich organisation this key acts as, which is not always the one you signed in to. POST /v1/account/api_keys takes an organisation naming a product your company owns, and the key it answers with acts as that product and no other, so read this to confirm which one you just minted for. POST /v1/api_keys leaves it out: that call issues for the organisation on your credential and there is only ever one.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/account/api_keys" \
-H "Droomwork-Session: $DROOMWORK_SESSION" \
-H "Content-Type: application/json" \
-d '{"name":"CI","scopes":["run:read"],"organisation":"org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}'import { Configuration, IAMApi } from '@droomwork/sdk';
const api = new IAMApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.iamAccountApiKeysCreate({
iamApiKeyCreateRequest: {"name":"CI","scopes":["run:read"],"organisation":"org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},
});const response = await fetch('https://sandbox.droomwork.io/v1/account/api_keys', {
method: 'POST',
headers: {
'Droomwork-Session': process.env.DROOMWORK_SESSION,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "CI",
"scopes": [
"run:read"
],
"organisation": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}),
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.IAMApi(client)
result = api.iam_account_api_keys_create(body={"name": "CI", "scopes": ["run:read"], "organisation": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"})import os
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/account/api_keys',
headers={
'Droomwork-Session': os.environ['DROOMWORK_SESSION'],
},
json={"name": "CI", "scopes": ["run:read"], "organisation": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\IAMApi(new GuzzleHttp\Client(), $config);
$result = $api->iamAccountApiKeysCreate(json_decode('{"name":"CI","scopes":["run:read"],"organisation":"org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/account/api_keys');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Session: ' . getenv('DROOMWORK_SESSION'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{"name":"CI","scopes":["run:read"],"organisation":"org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.IamApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
IamApi api = new IamApi(client);
var result = api.iamAccountApiKeysCreate(body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/account/api_keys"))
.header("Droomwork-Session", System.getenv("DROOMWORK_SESSION"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"name": "CI",
"scopes": [
"run:read"
],
"organisation": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new IAMApi(config);
var result = api.IamAccountApiKeysCreate(body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/account/api_keys");
request.Headers.Add("Droomwork-Session", Environment.GetEnvironmentVariable("DROOMWORK_SESSION"));
request.Content = new StringContent("""
{
"name": "CI",
"scopes": [
"run:read"
],
"organisation": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
""", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.IAMAPI.IamAccountApiKeysCreate(ctx).IamApiKeyCreateRequest(body).Execute()body := strings.NewReader(`{
"name": "CI",
"scopes": [
"run:read"
],
"organisation": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/account/api_keys", body)
req.Header.Set("Droomwork-Session", os.Getenv("DROOMWORK_SESSION"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "api_key",
"livemode": false,
"mocked": false,
"name": "Ada Obi",
"realm": "test",
"scopes": [
"run:read"
],
"created_at": "2026-09-01T09:00:00Z",
"revoked_at": null,
"key": "dw_test_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"organisation": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
/v1/account/api_keys/{id}/revoke#Stop a key from working, while signed in
iam.account.api_keys.revoke
This stops one of your API keys working, straight away. So you can put a Revoke button on your settings screen, turn off a colleague's key, or replace one by making the new key first and stopping the old one after.
Send the key's id in the path, the one listed at GET /v1/account/api_keys, and your session token as Droomwork-Session, not an API key.
If the key belongs to a product you own, and not to the organisation you signed in as, send that product as organisation. The key has to belong to the organisation you name, so a product's key sent without one answers not_found.
Read revoked_at in the answer: the moment the key stopped. Revoking isn't deleting.
Path parameters
id
string
requiredThe key's id, which starts with api_key_, as returned by POST /v1/api_keys or POST /v1/account/api_keys when you made it and listed by GET /v1/api_keys and GET /v1/me. A key belonging to another organisation answers not_found.
Body optional
organisation
string
optionalWhich of your organisations the key belongs to. Leave it out for the one you signed in as. GET /v1/me lists the ones you can name.
Returns
The key, with the moment it stopped working.
id
string
requiredThe key's identifier; it starts with api_key_ and never changes. Returned by POST /v1/api_keys, POST /v1/account/api_keys and POST /v1/registrations, listed by GET /v1/api_keys and GET /v1/me, and sent as {id} to revoke it.
object
always "api_key"
requiredAlways api_key. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredFalse for a record in the sandbox realm, true for one in the live realm. This answers which data a record belongs to. mocked answers where its numbers came from, and the two are separate questions. A sandbox payslip computed by the real engine is livemode false and mocked false. A deferred capability in the live realm is livemode true and mocked true.
mocked
boolean
requiredTrue when this response came from a mock, false when the engine computed the numbers.
It stays true for a capability that is still on its documented mock, even in the live realm. It answers a different question from livemode: livemode says which realm you are in, this says where the numbers came from.
name
string
requiredWhat this key is for, as named when it was created. Use it to tell your keys apart in a list.
realm
string
requiredWhich realm this key reaches: test is the sandbox, where nothing moves, and live is real money and real filings. Sandbox keys are test and carry livemode false; live keys aren't self-serve.
testlivescopes
array of Scope
requiredWhat this key may do, as module:action scopes; a request needing one not listed here is refused. You can't issue the wildcard, so it never appears here.
revoked_at
string · date-time · nullable
optionalWhen this key stopped authenticating, as an RFC 3339 timestamp in UTC, or null while it still works. A revoked key stays listed with this set, so its history stays with it.
created_at
string · date-time
requiredWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/account/api_keys/api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/revoke" \
-H "Droomwork-Session: $DROOMWORK_SESSION" \
-H "Content-Type: application/json" \
-d '{"organisation":"org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}'import { Configuration, IAMApi } from '@droomwork/sdk';
const api = new IAMApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.iamAccountApiKeysRevoke({ id: 'api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/account/api_keys/api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/revoke', {
method: 'POST',
headers: {
'Droomwork-Session': process.env.DROOMWORK_SESSION,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"organisation": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}),
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.IAMApi(client)
result = api.iam_account_api_keys_revoke(id='api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/account/api_keys/api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/revoke',
headers={
'Droomwork-Session': os.environ['DROOMWORK_SESSION'],
},
json={"organisation": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\IAMApi(new GuzzleHttp\Client(), $config);
$result = $api->iamAccountApiKeysRevoke(id: 'api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/account/api_keys/api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/revoke');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Session: ' . getenv('DROOMWORK_SESSION'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{"organisation":"org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.IamApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
IamApi api = new IamApi(client);
var result = api.iamAccountApiKeysRevoke("api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/account/api_keys/api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/revoke"))
.header("Droomwork-Session", System.getenv("DROOMWORK_SESSION"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"organisation": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new IAMApi(config);
var result = api.IamAccountApiKeysRevoke(id: "api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/account/api_keys/api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/revoke");
request.Headers.Add("Droomwork-Session", Environment.GetEnvironmentVariable("DROOMWORK_SESSION"));
request.Content = new StringContent("""
{
"organisation": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
""", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.IAMAPI.IamAccountApiKeysRevoke(ctx, "api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()body := strings.NewReader(`{
"organisation": "org_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/account/api_keys/api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/revoke", body)
req.Header.Set("Droomwork-Session", os.Getenv("DROOMWORK_SESSION"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "api_key_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "api_key",
"livemode": false,
"mocked": false,
"name": "Ada Obi",
"realm": "test",
"scopes": [
"run:read"
],
"created_at": "2026-09-01T09:00:00Z",
"revoked_at": "2026-09-01T09:00:00Z"
}