DDroomwork Developers

Version 1.0.0

Droomwork MATCH GEO-AWARE WORKFORCE ORCHESTRATOR

Allocation of work to available people, and proof that the work happened.

Base URLhttps://sandbox.droomwork.io
AuthenticationDroomwork-Api-Key: dw_test_… or Authorization: Bearer dw_test_…
Events it sends10 webhooks →
Signed in: requests you run on this page use a key made for your account. Your keys →

MATCH gets your work to the people who can do it: open a work order, get a ranked cascade of offers, and receive sealed proof that the work happened.

What you should know before you start

Only eligible workers are ranked. A worker needs a live unrevoked Passport, an executed engagement, every hard constraint met and a current availability state before they appear in a ranking at all. A ranking you read is a list of people who could lawfully do the work. You never have to filter it.

Availability is set by the worker and never inferred. Movement, silence and heartbeats never change it. It changes only when the worker changes it, through the one endpoint they own.

Location is collected only while a worker is available or on assignment. Send a reading outside those windows and it's refused, not stored. The supply state shows the window as closed with no position on it, so you can prove the absence from the data.

No single signal decides anything that matters. Spoofing needs several independent signals, and one on its own suspends nobody. Arrival needs geofence entry corroborated by a start passcode. Completion needs a completion passcode, dwell duration and telemetry continuity. Where the signals disagree the assignment goes to exception for a person to adjudicate, and does not settle.

Every ranking is kept in full. Every candidate considered, every component score, the weight set version and the outcome. You can reproduce a ranking months later, and a worker who was not offered the work is told why in plain language from a fixed set of reasons.

Travel time is measured over the road network. Not straight line distance. A river between two points is a river, not a short hop.

A fairness floor is reported continuously. Eligible workers in the long tail get a defined minimum offer exposure, and you can read the dispersion of offers across the eligible population at any time, not only at the end of a period.

An unfilled order tells you why. Cascade exhaustion and expiry are declared with a specific reason and emitted as a supply gap, so a failure to fill becomes sourcing demand and not silence.

MATCH computes no money and moves none. The proof of service bundle carries evidence and never an amount. Droomwork Pay settles against the sealed bundle.

Removal from a live cascade takes 60 seconds at most. A revocation from ANCHOR or a lapse of legal standing from RAIL removes the worker from every live cascade and cancels any unstarted assignment, within 60 seconds.

Getting started

Open a work order. Read the ranking it produced. Watch the offers cascade. Accept an offer to turn it into an assignment. Confirm arrival. Confirm completion. Read the sealed proof.

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?

POST/v1/allocation/heartbeats#

Send what the worker's device recorded

allocation.heartbeats.create

This is how a worker's device reports in, built for a network that keeps dropping. A phone out of coverage hands over what it held when the link returns, and the receipt accounts for every reading one way or the other.

Send one batch for one worker, named by the sub_ reference you chose at POST /v1/identity/consent_tokens, with one entry per reading the device took. Back comes a receipt that accounts for every reading you sent, counted as accepted or named with its index.

Stamp recorded_at with when the device took the reading, not when you send the batch. Sending availability on a reading does not set it; the worker's own call at PUT /v1/allocation/availability/{worker_id} does.

The call answers 202 whether or not every reading was accepted, so read rejections and not only the status. The receipt hands the same worker_id back for GET /v1/allocation/supply_states/{worker_id}.

Headers

Idempotency-Key string required

A 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

worker_id string required

The worker every reading is for: the sub_ subject reference you chose and sent as subject_ref at POST /v1/identity/consent_tokens when you first recorded them with ANCHOR. One batch carries one worker.

readings array of Heartbeat required

Ordered by the client where possible. The server reconciles regardless.

7 fields of Heartbeat
recorded_at string · date-time required

The client clock time the reading was taken at. Reconciliation orders by this rather than by arrival, so a buffered batch does not create movement that never happened.

position one of optional

Omitted where the collection window was closed. Sending one anyway is refused rather than stored.

GeoPointor
connectivity string required

The quality of the link at the time of the reading: offline for no link at all, then poor, fair or good for a working link from weakest to strongest.

offlinepoorfairgood
availability one of optional

What the device believed the worker's availability was. Read for drift detection and never written to the supply state, which changes only through the endpoint the worker owns.

AvailabilityStateor
battery_percent integer · nullable · minimum 0 · maximum 100 optional

Device condition. A dying battery explains a gap that would otherwise read as absence.

mock_location_detected boolean · nullable optional

One integrity signal of four. On its own it suspends nobody.

device_integrity_attested boolean · nullable optional

Whether the platform integrity attestation passed on the device.

Returns

The receipt for the batch, with every refused reading named by its index in what you sent.

object always "heartbeat_receipt" required

Always heartbeat_receipt. Tells you which kind of record you are looking at, so one handler can read any response.

worker_id string required

The worker the batch was for: the sub_ subject reference you sent as worker_id in the request to POST /v1/allocation/heartbeats, returned unchanged.

accepted_count integer · minimum 0 required

How many of the readings you sent were accepted, from 0 up. A reading is either counted here or named in rejections.

rejected_count integer · minimum 0 required

How many of the readings you sent were refused, from 0 up. Each one is named in rejections with its index and reason, so a refused position is never a silent gap.

reconciled boolean required

True when readings arrived out of order or late and were reordered by their recorded time.

zone one of optional

The zone the worker resolved to after reconciliation.

Zoneor
presence string optional

What was last heard from the worker, which is separate from what they set. A worker can be available and absent at the same time. That combination is ghost supply.

reportingstaleghost
validation ValidationReport required

The result of checking a submission before anything is committed. Names every failing entry rather than stopping at the first, so one submission tells you everything to fix. The same shape wherever the platform ingests, validates and reports, which is every bulk intake in every module.

4 fields of ValidationReport
entry_count integer · minimum 0 required

How many entries the submission held: every row in the file or line in the instruction set, whether it passed or not.

error_count integer · minimum 0 required

How many entries failed with an error. Each is named in entries with its position and why, so one submission tells you everything to fix.

warning_count integer · minimum 0 required

How many entries passed with a warning, each named in entries. A warning does not fail the entry; it points at something to confirm before you go ahead.

entries array of object optional

One item per error or warning found, with the row or line it sits on, its severity, a code and what was wrong. Empty or absent when every entry passed clean.

4 fields
index integer · minimum 1 required

The row in a file, or the line in an instruction set.

severity string required

error means the entry failed and must be fixed. warning means it passed, but points at something you should confirm before you go ahead.

errorwarning
code string required

Why the entry was flagged, as a code you can branch on. detail says the same in words and may change; the code does not.

detail string required

What was wrong with the entry, in plain words. Show it beside the row; it may change, so branch on code.

rejections array of object optional

One entry per refused reading, indexed against the submitted batch.

3 fields
index integer · minimum 1 required

Which reading was refused, as its position in the readings you sent. Counted from 1, so the first reading is 1.

reason string required

Why one reading in a batch was refused. A refused reading is named rather than dropped quietly, because a silently discarded position is a gap nobody can account for later.

outside_collection_windowimplausible_movementrecorded_in_the_futureolder_than_retention_windowduplicate_readingaccuracy_below_threshold
detail string optional

A sentence saying what about this reading was refused, where the reason code alone would not tell you. Not always present.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X POST "https://sandbox.droomwork.io/v1/allocation/heartbeats" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"worker_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","readings":[{"recorded_at":"2026-09-01T09:00:00Z","connectivity":"offline","position":{"latitude":-90,"longitude":-180,"accuracy_metres":0},"availability":"available","battery_percent":0,"mock_location_detected":true,"device_integrity_attested":true}]}'
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationHeartbeatsCreate({
  idempotencyKey: crypto.randomUUID(),
  matchHeartbeatBatchRequest: {"workerId":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","readings":[{"recordedAt":"2026-09-01T09:00:00Z","connectivity":"offline","position":{"latitude":-90,"longitude":-180,"accuracyMetres":0},"availability":"available","batteryPercent":0,"mockLocationDetected":true,"deviceIntegrityAttested":true}]},
});
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/heartbeats', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
    "readings": [
      {
        "recorded_at": "2026-09-01T09:00:00Z",
        "connectivity": "offline",
        "position": {
          "latitude": -90,
          "longitude": -180,
          "accuracy_metres": 0
        },
        "availability": "available",
        "battery_percent": 0,
        "mock_location_detected": true,
        "device_integrity_attested": true
      }
    ]
  }),
});
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.MATCHApi(client)

result = api.allocation_heartbeats_create(body={"worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "readings": [{"recorded_at": "2026-09-01T09:00:00Z", "connectivity": "offline", "position": {"latitude": -90, "longitude": -180, "accuracy_metres": 0}, "availability": "available", "battery_percent": 0, "mock_location_detected": True, "device_integrity_attested": True}]})
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/allocation/heartbeats',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
        'Idempotency-Key': str(uuid.uuid4()),
    },
    json={"worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "readings": [{"recorded_at": "2026-09-01T09:00:00Z", "connectivity": "offline", "position": {"latitude": -90, "longitude": -180, "accuracy_metres": 0}, "availability": "available", "battery_percent": 0, "mock_location_detected": True, "device_integrity_attested": True}]},
)
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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationHeartbeatsCreate($idempotencyKey, json_decode('{"worker_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","readings":[{"recorded_at":"2026-09-01T09:00:00Z","connectivity":"offline","position":{"latitude":-90,"longitude":-180,"accuracy_metres":0},"availability":"available","battery_percent":0,"mock_location_detected":true,"device_integrity_attested":true}]}', true));
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/heartbeats');
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 => '{"worker_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","readings":[{"recorded_at":"2026-09-01T09:00:00Z","connectivity":"offline","position":{"latitude":-90,"longitude":-180,"accuracy_metres":0},"availability":"available","battery_percent":0,"mock_location_detected":true,"device_integrity_attested":true}]}',
]);
$result = json_decode(curl_exec($ch), true);
import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationHeartbeatsCreate(idempotencyKey, body);
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/heartbeats"))
    .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("""
        {
          "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
          "readings": [
            {
              "recorded_at": "2026-09-01T09:00:00Z",
              "connectivity": "offline",
              "position": {
                "latitude": -90,
                "longitude": -180,
                "accuracy_metres": 0
              },
              "availability": "available",
              "battery_percent": 0,
              "mock_location_detected": true,
              "device_integrity_attested": true
            }
          ]
        }
        """))
    .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 MATCHApi(config);

var result = api.AllocationHeartbeatsCreate(idempotencyKey, body);
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/allocation/heartbeats");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
    {
      "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "readings": [
        {
          "recorded_at": "2026-09-01T09:00:00Z",
          "connectivity": "offline",
          "position": {
            "latitude": -90,
            "longitude": -180,
            "accuracy_metres": 0
          },
          "availability": "available",
          "battery_percent": 0,
          "mock_location_detected": true,
          "device_integrity_attested": true
        }
      ]
    }
    """, 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.MATCHAPI.AllocationHeartbeatsCreate(ctx).IdempotencyKey(key).MatchHeartbeatBatchRequest(body).Execute()
body := strings.NewReader(`{
  "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "readings": [
    {
      "recorded_at": "2026-09-01T09:00:00Z",
      "connectivity": "offline",
      "position": {
        "latitude": -90,
        "longitude": -180,
        "accuracy_metres": 0
      },
      "availability": "available",
      "battery_percent": 0,
      "mock_location_detected": true,
      "device_integrity_attested": true
    }
  ]
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/allocation/heartbeats", 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)
Response
{
  "object": "heartbeat_receipt",
  "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "accepted_count": 0,
  "rejected_count": 0,
  "reconciled": true,
  "validation": {
    "entry_count": 0,
    "error_count": 0,
    "warning_count": 0,
    "entries": [
      {
        "index": 1,
        "severity": "error",
        "code": "licence_electrical",
        "detail": "The payee has no verified destination, so this line cannot be paid."
      }
    ]
  },
  "zone": {
    "id": "zone_lagos_mainland_03",
    "name": "Lagos Mainland 03"
  },
  "presence": "reporting",
  "rejections": [
    {
      "index": 1,
      "reason": "outside_collection_window",
      "detail": "The payee has no verified destination, so this line cannot be paid."
    }
  ]
}
GET/v1/allocation/supply_states#

See who can take work now

allocation.supply_states.list

Use this to see who on your workforce could take work right now, one record for each worker. Each record names the operational zone that worker resolved to, so a staffing view in your product can show where people are as well as how many.

You get one supply state for each worker, carrying their availability, their presence, which is what was last heard from them, and the time their last heartbeat arrived. A heartbeat is one reading taken at a moment their device recorded.

When you only want one worker, go straight to GET /v1/allocation/supply_states/{worker_id} with the worker_id from the record; it answers with the same object.

Query parameters

limit integer optional

How many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.

starting_after string optional

The id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.

availability string optional

Return only workers whose set state is available, unavailable or on_assignment, as they set it at PUT /v1/allocation/availability/{worker_id}. Leave it out to get workers in every state.

availableunavailableon_assignment
presence string optional

Return only workers in one presence: reporting while heartbeats arrive, stale once they have lapsed, ghost once silence passes ghost_threshold_seconds. Leave it out to get every worker.

reportingstaleghost
zone string optional

Return only workers who resolved to one operational zone, by its id such as zone_lagos_mainland_03, as it reads in zone.id on a supply state or a heartbeat receipt. Leave it out to get workers in every zone.

Returns

One supply state for each worker, enough to judge who to send a job to.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of SupplyState required

The records on this page, in the order the list promises. Empty when nothing matched.

15 fields of SupplyState
object always "supply_state" required

Always supply_state. Tells you which kind of record you are looking at, so one handler can read any response.

worker_id string required

The worker this state describes: the sub_ subject reference you sent as subject_ref at POST /v1/identity/consent_tokens when you first recorded them with ANCHOR. Pass it as {worker_id} to retrieve one state or to set availability.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

availability string required

Set by the worker and never inferred. Neither movement nor silence changes it.

availableunavailableon_assignment
presence string required

What was last heard from the worker, which is separate from what they set. A worker can be available and absent at the same time. That combination is ghost supply.

reportingstaleghost
rankable boolean required

False for ghost supply and for a worker suspended by an upheld integrity flag. The availability they set is untouched either way.

excluded_because string · nullable optional

Why the worker is not rankable: ghost_supply (silent past the threshold), integrity_flag_upheld (suspended by an upheld flag), no_executed_engagement (nothing executed in RAIL) or passport_not_live. null while rankable is true.

ghost_supplyintegrity_flag_upheldno_executed_engagementpassport_not_livenull
position one of optional

Null whenever the collection window is closed. This is the readable form of the rule that location is held only during availability or assignment.

GeoPointor
position_withheld_because string · nullable optional

Which rule closed the collection window, so a missing position is provable: availability_not_set (never set by the worker), worker_unavailable (they set unavailable) or no_active_assignment. null while the window is open.

availability_not_setworker_unavailableno_active_assignmentnull
collection_window string optional

Whether positions are being collected right now: open only while the worker is available or on_assignment, closed otherwise. While closed, position is null and position_withheld_because names the rule.

openclosed
zone one of optional

The operational zone the worker resolved to on their last accepted reading, with its id and name. null while they have not resolved to one.

Zoneor
last_heartbeat_at string · date-time · nullable optional

The worker's last heartbeat, as an RFC 3339 timestamp in UTC, or null when none has arrived. Silence past ghost_threshold_seconds from here makes an available worker ghost supply.

ghost_threshold_seconds integer optional

How long without a heartbeat before an available worker becomes ghost supply.

availability_set_at string · date-time · nullable optional

When the worker last set their availability. Preserved across a ghost exclusion.

has_more boolean required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

next_cursor string · nullable optional

Where the next page starts, or null when this is the last page. Pass it back unchanged to continue.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# query parameters: limit (optional), starting_after (optional), availability (optional), presence (optional), zone (optional)
curl -X GET "https://sandbox.droomwork.io/v1/allocation/supply_states?limit=25&availability=available&presence=reporting&zone=zone_lagos_mainland_03" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

// query parameters: limit (optional), starting_after (optional), availability (optional), presence (optional), zone (optional)
const result = await api.allocationSupplyStatesList({ limit: 25, availability: 'available', presence: 'reporting', zone: 'zone_lagos_mainland_03' });
// query parameters: limit (optional), starting_after (optional), availability (optional), presence (optional), zone (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/supply_states?limit=25&availability=available&presence=reporting&zone=zone_lagos_mainland_03', {
  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.MATCHApi(client)

# query parameters: limit (optional), starting_after (optional), availability (optional), presence (optional), zone (optional)
result = api.allocation_supply_states_list(limit=25, availability='available', presence='reporting', zone='zone_lagos_mainland_03')
import os

import requests

# query parameters: limit (optional), starting_after (optional), availability (optional), presence (optional), zone (optional)
response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/allocation/supply_states?limit=25&availability=available&presence=reporting&zone=zone_lagos_mainland_03',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

# query parameters: limit (optional), starting_after (optional), availability (optional), presence (optional), zone (optional)
$result = $api->allocationSupplyStatesList(limit: 25, availability: 'available', presence: 'reporting', zone: 'zone_lagos_mainland_03');
<?php
// query parameters: limit (optional), starting_after (optional), availability (optional), presence (optional), zone (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/supply_states?limit=25&availability=available&presence=reporting&zone=zone_lagos_mainland_03');
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.MatchApi;
import com.droomwork.sdk.model.*;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

// query parameters: limit (optional), starting_after (optional), availability (optional), presence (optional), zone (optional)
var result = api.allocationSupplyStatesList(25, null, MatchAvailabilityState.fromValue("available"), MatchPresence.fromValue("reporting"), "zone_lagos_mainland_03");
// query parameters: limit (optional), starting_after (optional), availability (optional), presence (optional), zone (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/supply_states?limit=25&availability=available&presence=reporting&zone=zone_lagos_mainland_03"))
    .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 MATCHApi(config);

// query parameters: limit (optional), starting_after (optional), availability (optional), presence (optional), zone (optional)
var result = api.AllocationSupplyStatesList(limit: 25, availability: MatchAvailabilityState.Available, presence: MatchPresence.Reporting, zone: "zone_lagos_mainland_03");
// query parameters: limit (optional), starting_after (optional), availability (optional), presence (optional), zone (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/allocation/supply_states?limit=25&availability=available&presence=reporting&zone=zone_lagos_mainland_03");
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), starting_after (optional), availability (optional), presence (optional), zone (optional)
result, _, err := client.MATCHAPI.AllocationSupplyStatesList(ctx).Limit(25).Availability(droomwork.MatchAvailabilityState("available")).Presence(droomwork.MatchPresence("reporting")).Zone("zone_lagos_mainland_03").Execute()
// query parameters: limit (optional), starting_after (optional), availability (optional), presence (optional), zone (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/allocation/supply_states?limit=25&availability=available&presence=reporting&zone=zone_lagos_mainland_03", 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)
Response
{
  "object": "list",
  "data": [
    {
      "object": "supply_state",
      "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "livemode": false,
      "mocked": true,
      "availability": "available",
      "presence": "reporting",
      "rankable": true,
      "excluded_because": "ghost_supply",
      "position": {
        "latitude": -90,
        "longitude": -180,
        "accuracy_metres": 0
      },
      "position_withheld_because": "availability_not_set",
      "collection_window": "open",
      "zone": {
        "id": "zone_lagos_mainland_03",
        "name": "Lagos Mainland 03"
      },
      "last_heartbeat_at": "2026-09-01T09:00:00Z",
      "ghost_threshold_seconds": 1,
      "availability_set_at": "2026-09-01T09:00:00Z"
    }
  ],
  "has_more": true,
  "next_cursor": "example"
}
GET/v1/allocation/supply_states/{worker_id}#

Check one worker before you offer work

allocation.supply_states.retrieve

Use this when you have one worker in hand, from an offer or an assignment, and want their current supply state. It is what fills a single worker's detail view, where somebody reads one person's standing without pulling the whole workforce.

Put the worker's reference in the path and you get that one worker's supply state back, the same record the list carries for them, with nothing added and nothing left out.

The reference to pass is the sub_ subject reference you chose when you first recorded the worker with ANCHOR. MATCH issues no worker identifier of its own, so there is nothing to look up before you call this.

Path parameters

worker_id string required

The worker's sub_ subject reference: the subject_ref you chose and sent at POST /v1/identity/consent_tokens when you first recorded them with ANCHOR, as it reads in worker_id on each state at GET /v1/allocation/supply_states.

Returns

The named worker's supply state, as it stands at the moment you asked.

object always "supply_state" required

Always supply_state. Tells you which kind of record you are looking at, so one handler can read any response.

worker_id string required

The worker this state describes: the sub_ subject reference you sent as subject_ref at POST /v1/identity/consent_tokens when you first recorded them with ANCHOR. Pass it as {worker_id} to retrieve one state or to set availability.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

availability string required

Set by the worker and never inferred. Neither movement nor silence changes it.

availableunavailableon_assignment
presence string required

What was last heard from the worker, which is separate from what they set. A worker can be available and absent at the same time. That combination is ghost supply.

reportingstaleghost
rankable boolean required

False for ghost supply and for a worker suspended by an upheld integrity flag. The availability they set is untouched either way.

excluded_because string · nullable optional

Why the worker is not rankable: ghost_supply (silent past the threshold), integrity_flag_upheld (suspended by an upheld flag), no_executed_engagement (nothing executed in RAIL) or passport_not_live. null while rankable is true.

ghost_supplyintegrity_flag_upheldno_executed_engagementpassport_not_livenull
position one of optional

Null whenever the collection window is closed. This is the readable form of the rule that location is held only during availability or assignment.

GeoPointor
position_withheld_because string · nullable optional

Which rule closed the collection window, so a missing position is provable: availability_not_set (never set by the worker), worker_unavailable (they set unavailable) or no_active_assignment. null while the window is open.

availability_not_setworker_unavailableno_active_assignmentnull
collection_window string optional

Whether positions are being collected right now: open only while the worker is available or on_assignment, closed otherwise. While closed, position is null and position_withheld_because names the rule.

openclosed
zone one of optional

The operational zone the worker resolved to on their last accepted reading, with its id and name. null while they have not resolved to one.

Zoneor
last_heartbeat_at string · date-time · nullable optional

The worker's last heartbeat, as an RFC 3339 timestamp in UTC, or null when none has arrived. Silence past ghost_threshold_seconds from here makes an available worker ghost supply.

ghost_threshold_seconds integer optional

How long without a heartbeat before an available worker becomes ghost supply.

availability_set_at string · date-time · nullable optional

When the worker last set their availability. Preserved across a ghost exclusion.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X GET "https://sandbox.droomwork.io/v1/allocation/supply_states/sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationSupplyStatesRetrieve({ workerId: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/supply_states/sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
  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.MATCHApi(client)

result = api.allocation_supply_states_retrieve(worker_id='sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/allocation/supply_states/sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationSupplyStatesRetrieve(worker_id: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/supply_states/sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
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.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationSupplyStatesRetrieve("sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/supply_states/sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"))
    .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 MATCHApi(config);

var result = api.AllocationSupplyStatesRetrieve(workerId: "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/allocation/supply_states/sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
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.MATCHAPI.AllocationSupplyStatesRetrieve(ctx, "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/allocation/supply_states/sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", 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)
Response
{
  "object": "supply_state",
  "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "livemode": false,
  "mocked": true,
  "availability": "available",
  "presence": "reporting",
  "rankable": true,
  "excluded_because": "ghost_supply",
  "position": {
    "latitude": -90,
    "longitude": -180,
    "accuracy_metres": 0
  },
  "position_withheld_because": "availability_not_set",
  "collection_window": "open",
  "zone": {
    "id": "zone_lagos_mainland_03",
    "name": "Lagos Mainland 03"
  },
  "last_heartbeat_at": "2026-09-01T09:00:00Z",
  "ghost_threshold_seconds": 1,
  "availability_set_at": "2026-09-01T09:00:00Z"
}
PUT/v1/allocation/availability/{worker_id}#

Set the availability the worker chose

allocation.availability.update

This is the call behind the control a worker uses to say whether they can take work. One screen in your product can carry that control and show where the worker stands afterwards, because the answer is their whole supply state, not an acknowledgement.

You send the state they chose and name them as {worker_id} in the path. Back comes their supply state, and availability_set_at on it says when they set it.

{worker_id} is the sub_ subject reference you sent as subject_ref at POST /v1/identity/consent_tokens when the worker was first recorded.

Leave effective_at out and the change applies now. A future value schedules it instead, so read availability on the answer before you treat it as moved.

GET /v1/allocation/supply_states/{worker_id} answers with the same supply_state object, so one reader in your code handles both.

Path parameters

worker_id string required

The worker's sub_ subject reference: the subject_ref you chose and sent at POST /v1/identity/consent_tokens when you first recorded them with ANCHOR, as it reads in worker_id on each state at GET /v1/allocation/supply_states.

Body

availability string required

Set by the worker and never inferred. Neither movement nor silence changes it.

availableunavailableon_assignment
effective_at string · date-time optional

Defaults to now. A future time schedules the change rather than applying it.

Returns

The worker's supply state, carrying the availability they set and the time they set it.

object always "supply_state" required

Always supply_state. Tells you which kind of record you are looking at, so one handler can read any response.

worker_id string required

The worker this state describes: the sub_ subject reference you sent as subject_ref at POST /v1/identity/consent_tokens when you first recorded them with ANCHOR. Pass it as {worker_id} to retrieve one state or to set availability.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

availability string required

Set by the worker and never inferred. Neither movement nor silence changes it.

availableunavailableon_assignment
presence string required

What was last heard from the worker, which is separate from what they set. A worker can be available and absent at the same time. That combination is ghost supply.

reportingstaleghost
rankable boolean required

False for ghost supply and for a worker suspended by an upheld integrity flag. The availability they set is untouched either way.

excluded_because string · nullable optional

Why the worker is not rankable: ghost_supply (silent past the threshold), integrity_flag_upheld (suspended by an upheld flag), no_executed_engagement (nothing executed in RAIL) or passport_not_live. null while rankable is true.

ghost_supplyintegrity_flag_upheldno_executed_engagementpassport_not_livenull
position one of optional

Null whenever the collection window is closed. This is the readable form of the rule that location is held only during availability or assignment.

GeoPointor
position_withheld_because string · nullable optional

Which rule closed the collection window, so a missing position is provable: availability_not_set (never set by the worker), worker_unavailable (they set unavailable) or no_active_assignment. null while the window is open.

availability_not_setworker_unavailableno_active_assignmentnull
collection_window string optional

Whether positions are being collected right now: open only while the worker is available or on_assignment, closed otherwise. While closed, position is null and position_withheld_because names the rule.

openclosed
zone one of optional

The operational zone the worker resolved to on their last accepted reading, with its id and name. null while they have not resolved to one.

Zoneor
last_heartbeat_at string · date-time · nullable optional

The worker's last heartbeat, as an RFC 3339 timestamp in UTC, or null when none has arrived. Silence past ghost_threshold_seconds from here makes an available worker ghost supply.

ghost_threshold_seconds integer optional

How long without a heartbeat before an available worker becomes ghost supply.

availability_set_at string · date-time · nullable optional

When the worker last set their availability. Preserved across a ghost exclusion.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X PUT "https://sandbox.droomwork.io/v1/allocation/availability/sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"availability":"available","effective_at":"2026-09-01T09:00:00Z"}'
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationAvailabilityUpdate({
  workerId: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
  matchAvailabilityUpdateRequest: {"availability":"available","effectiveAt":"2026-09-01T09:00:00Z"},
});
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/availability/sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
  method: 'PUT',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "availability": "available",
    "effective_at": "2026-09-01T09:00:00Z"
  }),
});
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.MATCHApi(client)

result = api.allocation_availability_update(worker_id='sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', body={"availability": "available", "effective_at": "2026-09-01T09:00:00Z"})
import os

import requests

response = requests.request(
    'PUT',
    'https://sandbox.droomwork.io/v1/allocation/availability/sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
    json={"availability": "available", "effective_at": "2026-09-01T09:00:00Z"},
)
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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationAvailabilityUpdate(json_decode('{"availability":"available","effective_at":"2026-09-01T09:00:00Z"}', true));
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/availability/sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_CUSTOMREQUEST => 'PUT',
  CURLOPT_HTTPHEADER => [
    'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
    'Content-Type: application/json',
  ],
  CURLOPT_POSTFIELDS => '{"availability":"available","effective_at":"2026-09-01T09:00:00Z"}',
]);
$result = json_decode(curl_exec($ch), true);
import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationAvailabilityUpdate("sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", body);
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/availability/sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"))
    .header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
    .header("Content-Type", "application/json")
    .method("PUT", HttpRequest.BodyPublishers.ofString("""
        {
          "availability": "available",
          "effective_at": "2026-09-01T09:00:00Z"
        }
        """))
    .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 MATCHApi(config);

var result = api.AllocationAvailabilityUpdate(workerId: "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", body);
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Put, "https://sandbox.droomwork.io/v1/allocation/availability/sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Content = new StringContent("""
    {
      "availability": "available",
      "effective_at": "2026-09-01T09:00:00Z"
    }
    """, 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.MATCHAPI.AllocationAvailabilityUpdate(ctx, "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").MatchAvailabilityUpdateRequest(body).Execute()
body := strings.NewReader(`{
  "availability": "available",
  "effective_at": "2026-09-01T09:00:00Z"
}`)
req, _ := http.NewRequest("PUT", "https://sandbox.droomwork.io/v1/allocation/availability/sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", body)
	req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
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)
Response
{
  "object": "supply_state",
  "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "livemode": false,
  "mocked": true,
  "availability": "available",
  "presence": "reporting",
  "rankable": true,
  "excluded_because": "ghost_supply",
  "position": {
    "latitude": -90,
    "longitude": -180,
    "accuracy_metres": 0
  },
  "position_withheld_because": "availability_not_set",
  "collection_window": "open",
  "zone": {
    "id": "zone_lagos_mainland_03",
    "name": "Lagos Mainland 03"
  },
  "last_heartbeat_at": "2026-09-01T09:00:00Z",
  "ghost_threshold_seconds": 1,
  "availability_set_at": "2026-09-01T09:00:00Z"
}
GET/v1/allocation/integrity_flags#

See which positions were flagged, and why

allocation.integrity_flags.list

A flag is suspected position spoofing recorded against one worker, carrying each detection that fired and what it saw. Listing them lets your own product hold the review queue, so whoever weighs one has the whole flag in front of them.

Send nothing and every flag your organisation holds comes back, each one naming the worker it concerns, its status, the signals that fired with the time and detail of each, and its decision once one has been made.

A flag's worker_id is the sub_ subject reference ANCHOR holds for that person, and not an identifier of MATCH's own, so match a flag to somebody through what you stored when you recorded them.

Take a flag's id to POST /v1/allocation/integrity_flags/{integrity_flag_id}/adjudicate as integrity_flag_id when a person has decided it.

Query parameters

limit integer optional

How many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.

starting_after string optional

The id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.

status string optional

Return only flags in one state: open while nobody has decided, upheld or dismissed once a person has decided at POST /v1/allocation/integrity_flags/{integrity_flag_id}/adjudicate. Leave it out to get every flag.

openuphelddismissed
worker_id string optional

Return only flags raised against one worker, by the sub_ subject reference you sent as subject_ref at POST /v1/identity/consent_tokens when you first recorded them with ANCHOR. Leave it out to get flags for every worker.

Returns

A page of flags, each with the signals that fired and any decision made.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of IntegrityFlag required

The records on this page, in the order the list promises. Empty when nothing matched.

10 fields of IntegrityFlag
id string required

The flag's identifier, as it reads in id on each flag at GET /v1/allocation/integrity_flags. It starts with match_geo_aware_workforce_orchestrator_integrity_flag_, never changes, and is the integrity_flag_id you pass to adjudicate.

object always "integrity_flag" required

Always integrity_flag. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

worker_id string required

The worker the flag is against: the sub_ subject reference you sent as subject_ref at POST /v1/identity/consent_tokens in ANCHOR. While an upheld flag suspends them, their supply state's excluded_because is integrity_flag_upheld.

status string required
openuphelddismissed
signals array of IntegritySignal required

Every detection that fired, at least one: its source, when it fired and what it saw. suspends can only be true when more than one is here.

3 fields of IntegritySignal
source string required

The four independent detections. Agreement between them is what a flag rests on.

mock_locationdevice_integrityimplausible_movementnetwork_cross_check
fired_at string · date-time required

When this detection fired, as an RFC 3339 timestamp in UTC. Not the flag's raised_at: each signal on a flag carries its own time.

detail string required

What the signal saw, in terms a person adjudicating can weigh.

suspends boolean required

True only where more than one independent signal fired and a person upheld the flag. One signal on its own suspends nobody.

decision one of optional

The adjudication, once a person has made one: the outcome, who decided, when, their reason and the signals they accepted. null while the flag is open; once set it is never edited.

Decisionor
raised_at string · date-time optional

When the flag was raised, as an RFC 3339 timestamp in UTC. The signals it carries each have their own fired_at.

has_more boolean required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

next_cursor string · nullable optional

Where the next page starts, or null when this is the last page. Pass it back unchanged to continue.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# query parameters: limit (optional), starting_after (optional), status (optional), worker_id (optional)
curl -X GET "https://sandbox.droomwork.io/v1/allocation/integrity_flags?limit=25&status=open&worker_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

// query parameters: limit (optional), starting_after (optional), status (optional), worker_id (optional)
const result = await api.allocationIntegrityFlagsList({ limit: 25, status: 'open', workerId: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
// query parameters: limit (optional), starting_after (optional), status (optional), worker_id (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/integrity_flags?limit=25&status=open&worker_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
  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.MATCHApi(client)

# query parameters: limit (optional), starting_after (optional), status (optional), worker_id (optional)
result = api.allocation_integrity_flags_list(limit=25, status='open', worker_id='sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os

import requests

# query parameters: limit (optional), starting_after (optional), status (optional), worker_id (optional)
response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/allocation/integrity_flags?limit=25&status=open&worker_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

# query parameters: limit (optional), starting_after (optional), status (optional), worker_id (optional)
$result = $api->allocationIntegrityFlagsList(limit: 25, status: 'open', worker_id: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
// query parameters: limit (optional), starting_after (optional), status (optional), worker_id (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/integrity_flags?limit=25&status=open&worker_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
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.MatchApi;
import com.droomwork.sdk.model.*;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

// query parameters: limit (optional), starting_after (optional), status (optional), worker_id (optional)
var result = api.allocationIntegrityFlagsList(25, null, MatchIntegrityFlagStatus.fromValue("open"), "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
// query parameters: limit (optional), starting_after (optional), status (optional), worker_id (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/integrity_flags?limit=25&status=open&worker_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"))
    .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 MATCHApi(config);

// query parameters: limit (optional), starting_after (optional), status (optional), worker_id (optional)
var result = api.AllocationIntegrityFlagsList(limit: 25, status: MatchIntegrityFlagStatus.Open, workerId: "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
// query parameters: limit (optional), starting_after (optional), status (optional), worker_id (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/allocation/integrity_flags?limit=25&status=open&worker_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
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), starting_after (optional), status (optional), worker_id (optional)
result, _, err := client.MATCHAPI.AllocationIntegrityFlagsList(ctx).Limit(25).Status(droomwork.MatchIntegrityFlagStatus("open")).WorkerId("sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
// query parameters: limit (optional), starting_after (optional), status (optional), worker_id (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/allocation/integrity_flags?limit=25&status=open&worker_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", 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)
Response
{
  "object": "list",
  "data": [
    {
      "id": "match_geo_aware_workforce_orchestrator_integrity_flag_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "integrity_flag",
      "livemode": false,
      "mocked": true,
      "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "status": "open",
      "signals": [
        {
          "source": "mock_location",
          "fired_at": "2026-09-01T09:00:00Z",
          "detail": "The payee has no verified destination, so this line cannot be paid."
        }
      ],
      "suspends": true,
      "decision": {
        "outcome": "upheld",
        "decided_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
        "decided_at": "2026-09-01T09:00:00Z",
        "reason": "The requester confirmed the work in person.",
        "accepted_signals": [
          "example"
        ]
      },
      "raised_at": "2026-09-01T09:00:00Z"
    }
  ],
  "has_more": true,
  "next_cursor": "example"
}
POST/v1/allocation/integrity_flags/{integrity_flag_id}/adjudicate#

Decide whether the signals amount to spoofing

allocation.integrity_flags.adjudicate

Somebody has to decide whether what the detections saw amounts to spoofing, and this call records that decision. It is the only thing that moves a flag off open, so the judgement stays with a person in your organisation.

You name a flag you listed at GET /v1/allocation/integrity_flags and send the outcome you decided with the reason for it. What comes back is the whole flag, carrying a decision and a status no longer open.

Afterwards the flag carries its decision, and reads the same way at GET /v1/allocation/integrity_flags for anybody in your organisation who looks.

Path parameters

integrity_flag_id string required

The flag's identifier, from id on a flag you listed at GET /v1/allocation/integrity_flags. It starts with match_geo_aware_workforce_orchestrator_integrity_flag_.

Headers

Idempotency-Key string required

A 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

outcome string required

Your decision: upheld or dismissed. On a flag, upheld suspends the worker where more than one signal fired and dismissed returns them; on an exception, upheld seals the proof and dismissed closes the assignment without one.

uphelddismissed
reason string required

Why you decided as you did, in your own words, at least one character. It is recorded with the decision and can't be edited afterwards.

accepted_signals array of string optional

Name the signals you accepted, when only some persuaded you: by source from a flag's signals, or by signal from an exception's conflicting_signals. Recorded with the decision.

Returns

The flag as it now stands, with its decision and the status it reached.

id string required

The flag's identifier, as it reads in id on each flag at GET /v1/allocation/integrity_flags. It starts with match_geo_aware_workforce_orchestrator_integrity_flag_, never changes, and is the integrity_flag_id you pass to adjudicate.

object always "integrity_flag" required

Always integrity_flag. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

worker_id string required

The worker the flag is against: the sub_ subject reference you sent as subject_ref at POST /v1/identity/consent_tokens in ANCHOR. While an upheld flag suspends them, their supply state's excluded_because is integrity_flag_upheld.

status string required
openuphelddismissed
signals array of IntegritySignal required

Every detection that fired, at least one: its source, when it fired and what it saw. suspends can only be true when more than one is here.

3 fields of IntegritySignal
source string required

The four independent detections. Agreement between them is what a flag rests on.

mock_locationdevice_integrityimplausible_movementnetwork_cross_check
fired_at string · date-time required

When this detection fired, as an RFC 3339 timestamp in UTC. Not the flag's raised_at: each signal on a flag carries its own time.

detail string required

What the signal saw, in terms a person adjudicating can weigh.

suspends boolean required

True only where more than one independent signal fired and a person upheld the flag. One signal on its own suspends nobody.

decision one of optional

The adjudication, once a person has made one: the outcome, who decided, when, their reason and the signals they accepted. null while the flag is open; once set it is never edited.

Decisionor
raised_at string · date-time optional

When the flag was raised, as an RFC 3339 timestamp in UTC. The signals it carries each have their own fired_at.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X POST "https://sandbox.droomwork.io/v1/allocation/integrity_flags/match_geo_aware_workforce_orchestrator_integrity_flag_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/adjudicate" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"outcome":"upheld","reason":"The requester confirmed the work in person.","accepted_signals":["example"]}'
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationIntegrityFlagsAdjudicate({
  integrityFlagId: 'match_geo_aware_workforce_orchestrator_integrity_flag_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
  idempotencyKey: crypto.randomUUID(),
  matchAdjudicateRequest: {"outcome":"upheld","reason":"The requester confirmed the work in person.","acceptedSignals":["example"]},
});
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/integrity_flags/match_geo_aware_workforce_orchestrator_integrity_flag_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/adjudicate', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    "outcome": "upheld",
    "reason": "The requester confirmed the work in person.",
    "accepted_signals": [
      "example"
    ]
  }),
});
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.MATCHApi(client)

result = api.allocation_integrity_flags_adjudicate(integrity_flag_id='match_geo_aware_workforce_orchestrator_integrity_flag_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', body={"outcome": "upheld", "reason": "The requester confirmed the work in person.", "accepted_signals": ["example"]})
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/allocation/integrity_flags/match_geo_aware_workforce_orchestrator_integrity_flag_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/adjudicate',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
        'Idempotency-Key': str(uuid.uuid4()),
    },
    json={"outcome": "upheld", "reason": "The requester confirmed the work in person.", "accepted_signals": ["example"]},
)
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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationIntegrityFlagsAdjudicate($idempotencyKey, json_decode('{"outcome":"upheld","reason":"The requester confirmed the work in person.","accepted_signals":["example"]}', true));
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/integrity_flags/match_geo_aware_workforce_orchestrator_integrity_flag_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/adjudicate');
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 => '{"outcome":"upheld","reason":"The requester confirmed the work in person.","accepted_signals":["example"]}',
]);
$result = json_decode(curl_exec($ch), true);
import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationIntegrityFlagsAdjudicate("match_geo_aware_workforce_orchestrator_integrity_flag_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/integrity_flags/match_geo_aware_workforce_orchestrator_integrity_flag_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/adjudicate"))
    .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("""
        {
          "outcome": "upheld",
          "reason": "The requester confirmed the work in person.",
          "accepted_signals": [
            "example"
          ]
        }
        """))
    .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 MATCHApi(config);

var result = api.AllocationIntegrityFlagsAdjudicate(integrityFlagId: "match_geo_aware_workforce_orchestrator_integrity_flag_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/allocation/integrity_flags/match_geo_aware_workforce_orchestrator_integrity_flag_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/adjudicate");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
    {
      "outcome": "upheld",
      "reason": "The requester confirmed the work in person.",
      "accepted_signals": [
        "example"
      ]
    }
    """, 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.MATCHAPI.AllocationIntegrityFlagsAdjudicate(ctx, "match_geo_aware_workforce_orchestrator_integrity_flag_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").IdempotencyKey(key).MatchAdjudicateRequest(body).Execute()
body := strings.NewReader(`{
  "outcome": "upheld",
  "reason": "The requester confirmed the work in person.",
  "accepted_signals": [
    "example"
  ]
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/allocation/integrity_flags/match_geo_aware_workforce_orchestrator_integrity_flag_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/adjudicate", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_integrity_flag_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "integrity_flag",
  "livemode": false,
  "mocked": true,
  "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "status": "open",
  "signals": [
    {
      "source": "mock_location",
      "fired_at": "2026-09-01T09:00:00Z",
      "detail": "The payee has no verified destination, so this line cannot be paid."
    }
  ],
  "suspends": true,
  "decision": {
    "outcome": "upheld",
    "decided_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
    "decided_at": "2026-09-01T09:00:00Z",
    "reason": "The requester confirmed the work in person.",
    "accepted_signals": [
      "example"
    ]
  },
  "raised_at": "2026-09-01T09:00:00Z"
}
GET/v1/allocation/work_orders#

Every work order you have opened

allocation.work_orders.list

These are the work orders your organisation has opened, whatever state each one has reached. It is the read a dispatch screen in your own product is built on: what is still open, what was filled, and what lapsed with nobody found.

Send nothing and you get your work orders back, each one a single request for workers at one place inside one time window, carrying the state it has reached and the moment it lapses.

Read one in full at GET /v1/allocation/work_orders/{work_order_id}, passing a row's id in the path.

Query parameters

limit integer optional

How many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.

starting_after string optional

The id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.

status string optional

Return only orders in one state: open when created, offered once offers went out, filled once you recorded the headcount met, cancelled once you withdrew it, unfilled on expiry or cascade exhaustion. Leave it out to get every state.

openofferedfilledcancelledunfilled
occupation_code string optional

Return only orders for one occupation, by the taxonomy code you sent as occupation_code at POST /v1/allocation/work_orders, such as ng-7412. Leave it out to get orders for every occupation.

created_after string optional

Return only orders whose created_at is after this moment, as an RFC 3339 timestamp in UTC. Pair it with created_before to bound a window; leave it out for no earliest bound.

created_before string optional

Return only orders whose created_at is before this moment, as an RFC 3339 timestamp in UTC. Pair it with created_after to bound a window; leave it out for no latest bound.

Returns

Your work orders, each with the state it has reached and when it lapses.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of WorkOrder required

The records on this page, in the order the list promises. Empty when nothing matched.

21 fields of WorkOrder
id string required

The order's identifier, returned in id by POST /v1/allocation/work_orders when you opened it. It starts with match_geo_aware_workforce_orchestrator_work_order_, never changes, and is the work_order_id every call on the order takes.

object always "work_order" required

Always work_order. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

An order is open until it is offered, filled, cancelled or declared unfilled. Nothing moves it back.

openofferedfilledcancelledunfilled
requester_id string required

The requester the work is for, as you sent it in requester_id at POST /v1/allocation/work_orders: their sub_ subject reference, the subject_ref ANCHOR verified at POST /v1/identity/verifications before the order opened.

service_category string required

The kind of service the work is, as you named it when you opened the order, such as field_maintenance. It names the service; occupation_code names who can do it.

occupation_code string required

The occupation the work needs, as a code from the occupation taxonomy, such as ng-7412. Any supply gap declared for the order carries it, so sourcing demand names the occupation.

headcount integer · minimum 1 required

The order stays open until this many acceptances are held.

headcount_filled integer · minimum 0 optional

How many acceptances the order holds so far, counted from 0. Read it against headcount to see how far the order is from filling.

urgency_class string required

How hard the cascade pushes, and how quickly an offer expires.

standardpriorityemergency
location GeoPoint required

A position reading. Accuracy is carried because a reading without it cannot be judged.

3 fields of GeoPoint
latitude number · minimum -90 · maximum 90 required

Latitude in decimal degrees, from -90 to 90. Negative values are south of the equator.

longitude number · minimum -180 · maximum 180 required

Longitude in decimal degrees, from -180 to 180. Negative values are west of the Greenwich meridian.

accuracy_metres integer · minimum 0 required

The radius the device reports as its confidence. A large value weakens every inference drawn from the point.

location_description string optional

The place in words a worker would recognise, such as Ikeja industrial estate, gate 3. Optional; location is the position the ranking uses.

time_window TimeWindow required

When the work may be done. A window, never a single instant.

2 fields of TimeWindow
starts_at string · date-time required

When the window opens: the earliest the work may start, as an RFC 3339 timestamp in UTC.

ends_at string · date-time required

When the window closes: the latest the work may finish, as an RFC 3339 timestamp in UTC.

constraints array of Constraint optional

Hard constraints and soft preferences in one list, each naming which it is.

3 fields of Constraint
code string required

What the requirement is, as a code such as licence_electrical. A relaxation names the soft preference it dropped by this code, as its preference_code.

kind string required

hard for a requirement that is never relaxed and gates who can be ranked at all; soft for a preference the cascade may drop to widen the pool, with the drop recorded in the order's relaxations.

hardsoft
detail string optional

What the requirement means in words, such as Current electrical licence. Optional; the code is what a worker has to meet, and the detail is for people reading the order.

relaxations array of Relaxation optional

Every soft preference the cascade dropped, with the round it happened in.

4 fields of Relaxation
preference_code string required

The code of the soft preference that was dropped, as it appears in the order's constraints.

round integer · minimum 1 required

The cascade round the preference was dropped in, counted from 1. Match it to the same round in the ranking to see who the wider pool brought in.

relaxed_at string · date-time required

When the preference was dropped, as an RFC 3339 timestamp in UTC.

reason string required

Why the preference was dropped: pool_exhausted when the eligible pool ran out, expiry_approaching when the order's expires_at was near, headcount_unmet when acceptances fell short of headcount.

pool_exhaustedexpiry_approachingheadcount_unmet
escalation_bounds EscalationBounds optional

How far the search may widen. It's published on the order, so you can read the escalation policy.

3 fields of EscalationBounds
initial_radius_metres integer · minimum 0 required

The radius the first round searches within, in metres from location.

maximum_radius_metres integer · minimum 0 required

The furthest the radius may widen to, in metres from location. No round searches beyond it.

step_metres integer · minimum 1 required

How much the radius widens by on each escalation, in metres, at least 1. A round that widened is marked escalated in the ranking.

budget_ceiling Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

expires_at string · date-time required

On expiry the order is declared unfilled with a reason, never left open.

cancelled_reason string · nullable optional

Why the order was cancelled, as stated when it was cancelled. null while the order has not been cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

has_more boolean required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

next_cursor string · nullable optional

Where the next page starts, or null when this is the last page. Pass it back unchanged to continue.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# query parameters: limit (optional), starting_after (optional), status (optional), occupation_code (optional), created_after (optional), created_before (optional)
curl -X GET "https://sandbox.droomwork.io/v1/allocation/work_orders?limit=25&status=open&occupation_code=ng-7412&created_after=2026-01-01T00%3A00%3A00Z&created_before=2027-01-01T00%3A00%3A00Z" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

// query parameters: limit (optional), starting_after (optional), status (optional), occupation_code (optional), created_after (optional), created_before (optional)
const result = await api.allocationWorkOrdersList({ limit: 25, status: 'open', occupationCode: 'ng-7412', createdAfter: '2026-01-01T00:00:00Z', createdBefore: '2027-01-01T00:00:00Z' });
// query parameters: limit (optional), starting_after (optional), status (optional), occupation_code (optional), created_after (optional), created_before (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/work_orders?limit=25&status=open&occupation_code=ng-7412&created_after=2026-01-01T00%3A00%3A00Z&created_before=2027-01-01T00%3A00%3A00Z', {
  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.MATCHApi(client)

# query parameters: limit (optional), starting_after (optional), status (optional), occupation_code (optional), created_after (optional), created_before (optional)
result = api.allocation_work_orders_list(limit=25, status='open', occupation_code='ng-7412', created_after='2026-01-01T00:00:00Z', created_before='2027-01-01T00:00:00Z')
import os

import requests

# query parameters: limit (optional), starting_after (optional), status (optional), occupation_code (optional), created_after (optional), created_before (optional)
response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/allocation/work_orders?limit=25&status=open&occupation_code=ng-7412&created_after=2026-01-01T00%3A00%3A00Z&created_before=2027-01-01T00%3A00%3A00Z',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

# query parameters: limit (optional), starting_after (optional), status (optional), occupation_code (optional), created_after (optional), created_before (optional)
$result = $api->allocationWorkOrdersList(limit: 25, status: 'open', occupation_code: 'ng-7412', created_after: '2026-01-01T00:00:00Z', created_before: '2027-01-01T00:00:00Z');
<?php
// query parameters: limit (optional), starting_after (optional), status (optional), occupation_code (optional), created_after (optional), created_before (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/work_orders?limit=25&status=open&occupation_code=ng-7412&created_after=2026-01-01T00%3A00%3A00Z&created_before=2027-01-01T00%3A00%3A00Z');
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.MatchApi;
import com.droomwork.sdk.model.*;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

// query parameters: limit (optional), starting_after (optional), status (optional), occupation_code (optional), created_after (optional), created_before (optional)
var result = api.allocationWorkOrdersList(25, null, MatchWorkOrderStatus.fromValue("open"), "ng-7412", "2026-01-01T00:00:00Z", "2027-01-01T00:00:00Z");
// query parameters: limit (optional), starting_after (optional), status (optional), occupation_code (optional), created_after (optional), created_before (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/work_orders?limit=25&status=open&occupation_code=ng-7412&created_after=2026-01-01T00%3A00%3A00Z&created_before=2027-01-01T00%3A00%3A00Z"))
    .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 MATCHApi(config);

// query parameters: limit (optional), starting_after (optional), status (optional), occupation_code (optional), created_after (optional), created_before (optional)
var result = api.AllocationWorkOrdersList(limit: 25, status: MatchWorkOrderStatus.Open, occupationCode: "ng-7412", createdAfter: "2026-01-01T00:00:00Z", createdBefore: "2027-01-01T00:00:00Z");
// query parameters: limit (optional), starting_after (optional), status (optional), occupation_code (optional), created_after (optional), created_before (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/allocation/work_orders?limit=25&status=open&occupation_code=ng-7412&created_after=2026-01-01T00%3A00%3A00Z&created_before=2027-01-01T00%3A00%3A00Z");
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), starting_after (optional), status (optional), occupation_code (optional), created_after (optional), created_before (optional)
result, _, err := client.MATCHAPI.AllocationWorkOrdersList(ctx).Limit(25).Status(droomwork.MatchWorkOrderStatus("open")).OccupationCode("ng-7412").CreatedAfter("2026-01-01T00:00:00Z").CreatedBefore("2027-01-01T00:00:00Z").Execute()
// query parameters: limit (optional), starting_after (optional), status (optional), occupation_code (optional), created_after (optional), created_before (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/allocation/work_orders?limit=25&status=open&occupation_code=ng-7412&created_after=2026-01-01T00%3A00%3A00Z&created_before=2027-01-01T00%3A00%3A00Z", 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)
Response
{
  "object": "list",
  "data": [
    {
      "id": "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "work_order",
      "livemode": false,
      "mocked": true,
      "status": "open",
      "requester_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "service_category": "field_maintenance",
      "occupation_code": "ng-7412",
      "headcount": 1,
      "urgency_class": "standard",
      "location": {
        "latitude": -90,
        "longitude": -180,
        "accuracy_metres": 0
      },
      "time_window": {
        "starts_at": "2026-09-01T09:00:00Z",
        "ends_at": "2026-09-01T09:00:00Z"
      },
      "expires_at": "2026-09-01T09:00:00Z",
      "headcount_filled": 0,
      "location_description": "example",
      "constraints": [
        {
          "code": "licence_electrical",
          "kind": "hard",
          "detail": "The payee has no verified destination, so this line cannot be paid."
        }
      ],
      "relaxations": [
        {
          "preference_code": "example",
          "round": 1,
          "relaxed_at": "2026-09-01T09:00:00Z",
          "reason": "pool_exhausted"
        }
      ],
      "escalation_bounds": {
        "initial_radius_metres": 0,
        "maximum_radius_metres": 0,
        "step_metres": 1
      },
      "budget_ceiling": {
        "amount": 100000000,
        "currency": "NGN"
      },
      "cancelled_reason": "example",
      "created_at": "2026-09-01T09:00:00Z"
    }
  ],
  "has_more": true,
  "next_cursor": "example"
}
POST/v1/allocation/work_orders#

Open a work order

allocation.work_orders.create

Opening a work order is how a job you need done starts existing in MATCH. One order carries the demand whatever route it arrived by, and the identifier it answers with is what every later call takes.

Send who the work is for, the service, the occupation that can do it, how many people you need, where and when, how urgent it is and when the request lapses. You get the order back with an identifier beginning match_geo_aware_workforce_orchestrator_work_order_.

Have the requester's sub_ subject reference ready, the one ANCHOR verified at POST /v1/identity/verifications.

expires_at is not the end of time_window. It is when you stop wanting the order filled, and it can fall before the work is due to start.

The order opens in the open state, and POST /v1/allocation/work_orders/{work_order_id}/offer is what moves it next.

Headers

Idempotency-Key string required

A 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

requester_id string required

Who the work is for: the sub_ subject reference you sent as subject_ref at POST /v1/identity/consent_tokens and verified at POST /v1/identity/verifications. Refused with 422 subject_not_anchored until ANCHOR has verified them.

service_category string required

The kind of service the work is, such as field_maintenance, carried on the order as you sent it. It names the service; occupation_code names who can do it.

occupation_code string required

The occupation the work needs, as a code from the occupation taxonomy, such as ng-7412. If the order goes unfilled, the supply gap sent to FLOW names this occupation.

headcount integer · minimum 1 required

How many workers you need, at least 1. The order stays open until this many acceptances are held; if it lapses short of that, it's declared unfilled with a reason.

urgency_class string required

How hard the cascade pushes, and how quickly an offer expires.

standardpriorityemergency
location GeoPoint required

A position reading. Accuracy is carried because a reading without it cannot be judged.

3 fields of GeoPoint
latitude number · minimum -90 · maximum 90 required

Latitude in decimal degrees, from -90 to 90. Negative values are south of the equator.

longitude number · minimum -180 · maximum 180 required

Longitude in decimal degrees, from -180 to 180. Negative values are west of the Greenwich meridian.

accuracy_metres integer · minimum 0 required

The radius the device reports as its confidence. A large value weakens every inference drawn from the point.

location_description string optional

The place in words a worker would recognise, such as Ikeja industrial estate, gate 3. Optional, and it never replaces location.

time_window TimeWindow required

When the work may be done. A window, never a single instant.

2 fields of TimeWindow
starts_at string · date-time required

When the window opens: the earliest the work may start, as an RFC 3339 timestamp in UTC.

ends_at string · date-time required

When the window closes: the latest the work may finish, as an RFC 3339 timestamp in UTC.

constraints array of Constraint optional

Hard constraints and soft preferences in one list, each with a code and a kind. A hard one is never relaxed; a soft one may be dropped to widen the pool, and the relaxation is recorded on the order.

3 fields of Constraint
code string required

What the requirement is, as a code such as licence_electrical. A relaxation names the soft preference it dropped by this code, as its preference_code.

kind string required

hard for a requirement that is never relaxed and gates who can be ranked at all; soft for a preference the cascade may drop to widen the pool, with the drop recorded in the order's relaxations.

hardsoft
detail string optional

What the requirement means in words, such as Current electrical licence. Optional; the code is what a worker has to meet, and the detail is for people reading the order.

escalation_bounds EscalationBounds optional

How far the search may widen. It's published on the order, so you can read the escalation policy.

3 fields of EscalationBounds
initial_radius_metres integer · minimum 0 required

The radius the first round searches within, in metres from location.

maximum_radius_metres integer · minimum 0 required

The furthest the radius may widen to, in metres from location. No round searches beyond it.

step_metres integer · minimum 1 required

How much the radius widens by on each escalation, in metres, at least 1. A round that widened is marked escalated in the ranking.

budget_ceiling Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

expires_at string · date-time required

The moment the order lapses, as an RFC 3339 timestamp in UTC. If the headcount is unmet by then the order is declared unfilled with a reason and the shortfall raised as a supply gap.

Returns

The new order, open, with the identifier every later call on it takes.

id string required

The order's identifier, returned in id by POST /v1/allocation/work_orders when you opened it. It starts with match_geo_aware_workforce_orchestrator_work_order_, never changes, and is the work_order_id every call on the order takes.

object always "work_order" required

Always work_order. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

An order is open until it is offered, filled, cancelled or declared unfilled. Nothing moves it back.

openofferedfilledcancelledunfilled
requester_id string required

The requester the work is for, as you sent it in requester_id at POST /v1/allocation/work_orders: their sub_ subject reference, the subject_ref ANCHOR verified at POST /v1/identity/verifications before the order opened.

service_category string required

The kind of service the work is, as you named it when you opened the order, such as field_maintenance. It names the service; occupation_code names who can do it.

occupation_code string required

The occupation the work needs, as a code from the occupation taxonomy, such as ng-7412. Any supply gap declared for the order carries it, so sourcing demand names the occupation.

headcount integer · minimum 1 required

The order stays open until this many acceptances are held.

headcount_filled integer · minimum 0 optional

How many acceptances the order holds so far, counted from 0. Read it against headcount to see how far the order is from filling.

urgency_class string required

How hard the cascade pushes, and how quickly an offer expires.

standardpriorityemergency
location GeoPoint required

A position reading. Accuracy is carried because a reading without it cannot be judged.

3 fields of GeoPoint
latitude number · minimum -90 · maximum 90 required

Latitude in decimal degrees, from -90 to 90. Negative values are south of the equator.

longitude number · minimum -180 · maximum 180 required

Longitude in decimal degrees, from -180 to 180. Negative values are west of the Greenwich meridian.

accuracy_metres integer · minimum 0 required

The radius the device reports as its confidence. A large value weakens every inference drawn from the point.

location_description string optional

The place in words a worker would recognise, such as Ikeja industrial estate, gate 3. Optional; location is the position the ranking uses.

time_window TimeWindow required

When the work may be done. A window, never a single instant.

2 fields of TimeWindow
starts_at string · date-time required

When the window opens: the earliest the work may start, as an RFC 3339 timestamp in UTC.

ends_at string · date-time required

When the window closes: the latest the work may finish, as an RFC 3339 timestamp in UTC.

constraints array of Constraint optional

Hard constraints and soft preferences in one list, each naming which it is.

3 fields of Constraint
code string required

What the requirement is, as a code such as licence_electrical. A relaxation names the soft preference it dropped by this code, as its preference_code.

kind string required

hard for a requirement that is never relaxed and gates who can be ranked at all; soft for a preference the cascade may drop to widen the pool, with the drop recorded in the order's relaxations.

hardsoft
detail string optional

What the requirement means in words, such as Current electrical licence. Optional; the code is what a worker has to meet, and the detail is for people reading the order.

relaxations array of Relaxation optional

Every soft preference the cascade dropped, with the round it happened in.

4 fields of Relaxation
preference_code string required

The code of the soft preference that was dropped, as it appears in the order's constraints.

round integer · minimum 1 required

The cascade round the preference was dropped in, counted from 1. Match it to the same round in the ranking to see who the wider pool brought in.

relaxed_at string · date-time required

When the preference was dropped, as an RFC 3339 timestamp in UTC.

reason string required

Why the preference was dropped: pool_exhausted when the eligible pool ran out, expiry_approaching when the order's expires_at was near, headcount_unmet when acceptances fell short of headcount.

pool_exhaustedexpiry_approachingheadcount_unmet
escalation_bounds EscalationBounds optional

How far the search may widen. It's published on the order, so you can read the escalation policy.

3 fields of EscalationBounds
initial_radius_metres integer · minimum 0 required

The radius the first round searches within, in metres from location.

maximum_radius_metres integer · minimum 0 required

The furthest the radius may widen to, in metres from location. No round searches beyond it.

step_metres integer · minimum 1 required

How much the radius widens by on each escalation, in metres, at least 1. A round that widened is marked escalated in the ranking.

budget_ceiling Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

expires_at string · date-time required

On expiry the order is declared unfilled with a reason, never left open.

cancelled_reason string · nullable optional

Why the order was cancelled, as stated when it was cancelled. null while the order has not been cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X POST "https://sandbox.droomwork.io/v1/allocation/work_orders" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"requester_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","service_category":"field_maintenance","occupation_code":"ng-7412","headcount":1,"urgency_class":"standard","location":{"latitude":-90,"longitude":-180,"accuracy_metres":0},"time_window":{"starts_at":"2026-09-01T09:00:00Z","ends_at":"2026-09-01T09:00:00Z"},"expires_at":"2026-09-01T09:00:00Z","location_description":"example","constraints":[{"code":"licence_electrical","kind":"hard","detail":"The payee has no verified destination, so this line cannot be paid."}],"escalation_bounds":{"initial_radius_metres":0,"maximum_radius_metres":0,"step_metres":1},"budget_ceiling":{"amount":100000000,"currency":"NGN"}}'
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationWorkOrdersCreate({
  idempotencyKey: crypto.randomUUID(),
  matchWorkOrderCreateRequest: {"requesterId":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","serviceCategory":"field_maintenance","occupationCode":"ng-7412","headcount":1,"urgencyClass":"standard","location":{"latitude":-90,"longitude":-180,"accuracyMetres":0},"timeWindow":{"startsAt":"2026-09-01T09:00:00Z","endsAt":"2026-09-01T09:00:00Z"},"expiresAt":"2026-09-01T09:00:00Z","locationDescription":"example","constraints":[{"code":"licence_electrical","kind":"hard","detail":"The payee has no verified destination, so this line cannot be paid."}],"escalationBounds":{"initialRadiusMetres":0,"maximumRadiusMetres":0,"stepMetres":1},"budgetCeiling":{"amount":100000000,"currency":"NGN"}},
});
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/work_orders', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    "requester_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
    "service_category": "field_maintenance",
    "occupation_code": "ng-7412",
    "headcount": 1,
    "urgency_class": "standard",
    "location": {
      "latitude": -90,
      "longitude": -180,
      "accuracy_metres": 0
    },
    "time_window": {
      "starts_at": "2026-09-01T09:00:00Z",
      "ends_at": "2026-09-01T09:00:00Z"
    },
    "expires_at": "2026-09-01T09:00:00Z",
    "location_description": "example",
    "constraints": [
      {
        "code": "licence_electrical",
        "kind": "hard",
        "detail": "The payee has no verified destination, so this line cannot be paid."
      }
    ],
    "escalation_bounds": {
      "initial_radius_metres": 0,
      "maximum_radius_metres": 0,
      "step_metres": 1
    },
    "budget_ceiling": {
      "amount": 100000000,
      "currency": "NGN"
    }
  }),
});
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.MATCHApi(client)

result = api.allocation_work_orders_create(body={"requester_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "service_category": "field_maintenance", "occupation_code": "ng-7412", "headcount": 1, "urgency_class": "standard", "location": {"latitude": -90, "longitude": -180, "accuracy_metres": 0}, "time_window": {"starts_at": "2026-09-01T09:00:00Z", "ends_at": "2026-09-01T09:00:00Z"}, "expires_at": "2026-09-01T09:00:00Z", "location_description": "example", "constraints": [{"code": "licence_electrical", "kind": "hard", "detail": "The payee has no verified destination, so this line cannot be paid."}], "escalation_bounds": {"initial_radius_metres": 0, "maximum_radius_metres": 0, "step_metres": 1}, "budget_ceiling": {"amount": 100000000, "currency": "NGN"}})
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/allocation/work_orders',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
        'Idempotency-Key': str(uuid.uuid4()),
    },
    json={"requester_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "service_category": "field_maintenance", "occupation_code": "ng-7412", "headcount": 1, "urgency_class": "standard", "location": {"latitude": -90, "longitude": -180, "accuracy_metres": 0}, "time_window": {"starts_at": "2026-09-01T09:00:00Z", "ends_at": "2026-09-01T09:00:00Z"}, "expires_at": "2026-09-01T09:00:00Z", "location_description": "example", "constraints": [{"code": "licence_electrical", "kind": "hard", "detail": "The payee has no verified destination, so this line cannot be paid."}], "escalation_bounds": {"initial_radius_metres": 0, "maximum_radius_metres": 0, "step_metres": 1}, "budget_ceiling": {"amount": 100000000, "currency": "NGN"}},
)
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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationWorkOrdersCreate($idempotencyKey, json_decode('{"requester_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","service_category":"field_maintenance","occupation_code":"ng-7412","headcount":1,"urgency_class":"standard","location":{"latitude":-90,"longitude":-180,"accuracy_metres":0},"time_window":{"starts_at":"2026-09-01T09:00:00Z","ends_at":"2026-09-01T09:00:00Z"},"expires_at":"2026-09-01T09:00:00Z","location_description":"example","constraints":[{"code":"licence_electrical","kind":"hard","detail":"The payee has no verified destination, so this line cannot be paid."}],"escalation_bounds":{"initial_radius_metres":0,"maximum_radius_metres":0,"step_metres":1},"budget_ceiling":{"amount":100000000,"currency":"NGN"}}', true));
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/work_orders');
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 => '{"requester_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","service_category":"field_maintenance","occupation_code":"ng-7412","headcount":1,"urgency_class":"standard","location":{"latitude":-90,"longitude":-180,"accuracy_metres":0},"time_window":{"starts_at":"2026-09-01T09:00:00Z","ends_at":"2026-09-01T09:00:00Z"},"expires_at":"2026-09-01T09:00:00Z","location_description":"example","constraints":[{"code":"licence_electrical","kind":"hard","detail":"The payee has no verified destination, so this line cannot be paid."}],"escalation_bounds":{"initial_radius_metres":0,"maximum_radius_metres":0,"step_metres":1},"budget_ceiling":{"amount":100000000,"currency":"NGN"}}',
]);
$result = json_decode(curl_exec($ch), true);
import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationWorkOrdersCreate(idempotencyKey, body);
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/work_orders"))
    .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("""
        {
          "requester_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
          "service_category": "field_maintenance",
          "occupation_code": "ng-7412",
          "headcount": 1,
          "urgency_class": "standard",
          "location": {
            "latitude": -90,
            "longitude": -180,
            "accuracy_metres": 0
          },
          "time_window": {
            "starts_at": "2026-09-01T09:00:00Z",
            "ends_at": "2026-09-01T09:00:00Z"
          },
          "expires_at": "2026-09-01T09:00:00Z",
          "location_description": "example",
          "constraints": [
            {
              "code": "licence_electrical",
              "kind": "hard",
              "detail": "The payee has no verified destination, so this line cannot be paid."
            }
          ],
          "escalation_bounds": {
            "initial_radius_metres": 0,
            "maximum_radius_metres": 0,
            "step_metres": 1
          },
          "budget_ceiling": {
            "amount": 100000000,
            "currency": "NGN"
          }
        }
        """))
    .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 MATCHApi(config);

var result = api.AllocationWorkOrdersCreate(idempotencyKey, body);
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/allocation/work_orders");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
    {
      "requester_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "service_category": "field_maintenance",
      "occupation_code": "ng-7412",
      "headcount": 1,
      "urgency_class": "standard",
      "location": {
        "latitude": -90,
        "longitude": -180,
        "accuracy_metres": 0
      },
      "time_window": {
        "starts_at": "2026-09-01T09:00:00Z",
        "ends_at": "2026-09-01T09:00:00Z"
      },
      "expires_at": "2026-09-01T09:00:00Z",
      "location_description": "example",
      "constraints": [
        {
          "code": "licence_electrical",
          "kind": "hard",
          "detail": "The payee has no verified destination, so this line cannot be paid."
        }
      ],
      "escalation_bounds": {
        "initial_radius_metres": 0,
        "maximum_radius_metres": 0,
        "step_metres": 1
      },
      "budget_ceiling": {
        "amount": 100000000,
        "currency": "NGN"
      }
    }
    """, 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.MATCHAPI.AllocationWorkOrdersCreate(ctx).IdempotencyKey(key).MatchWorkOrderCreateRequest(body).Execute()
body := strings.NewReader(`{
  "requester_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "service_category": "field_maintenance",
  "occupation_code": "ng-7412",
  "headcount": 1,
  "urgency_class": "standard",
  "location": {
    "latitude": -90,
    "longitude": -180,
    "accuracy_metres": 0
  },
  "time_window": {
    "starts_at": "2026-09-01T09:00:00Z",
    "ends_at": "2026-09-01T09:00:00Z"
  },
  "expires_at": "2026-09-01T09:00:00Z",
  "location_description": "example",
  "constraints": [
    {
      "code": "licence_electrical",
      "kind": "hard",
      "detail": "The payee has no verified destination, so this line cannot be paid."
    }
  ],
  "escalation_bounds": {
    "initial_radius_metres": 0,
    "maximum_radius_metres": 0,
    "step_metres": 1
  },
  "budget_ceiling": {
    "amount": 100000000,
    "currency": "NGN"
  }
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/allocation/work_orders", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "work_order",
  "livemode": false,
  "mocked": true,
  "status": "open",
  "requester_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "service_category": "field_maintenance",
  "occupation_code": "ng-7412",
  "headcount": 1,
  "urgency_class": "standard",
  "location": {
    "latitude": -90,
    "longitude": -180,
    "accuracy_metres": 0
  },
  "time_window": {
    "starts_at": "2026-09-01T09:00:00Z",
    "ends_at": "2026-09-01T09:00:00Z"
  },
  "expires_at": "2026-09-01T09:00:00Z",
  "headcount_filled": 0,
  "location_description": "example",
  "constraints": [
    {
      "code": "licence_electrical",
      "kind": "hard",
      "detail": "The payee has no verified destination, so this line cannot be paid."
    }
  ],
  "relaxations": [
    {
      "preference_code": "example",
      "round": 1,
      "relaxed_at": "2026-09-01T09:00:00Z",
      "reason": "pool_exhausted"
    }
  ],
  "escalation_bounds": {
    "initial_radius_metres": 0,
    "maximum_radius_metres": 0,
    "step_metres": 1
  },
  "budget_ceiling": {
    "amount": 100000000,
    "currency": "NGN"
  },
  "cancelled_reason": "example",
  "created_at": "2026-09-01T09:00:00Z"
}
GET/v1/allocation/work_orders/{work_order_id}#

Check where one order stands

allocation.work_orders.retrieve

This is one order as it stands, which is how a detail page in your own product shows a single piece of demand without keeping its own copy of the order.

Pass the order's identifier in the path and the whole order comes back: who it is for, the occupation, how many people it needs, the place and how precisely it was given, the time window, the expiry and the state it has reached.

status is what tells you which calls are still open to you. An order reading offered can be filled; one reading open can still be offered or declared unfilled.

Path parameters

work_order_id string required

The order's identifier, from id on the order POST /v1/allocation/work_orders returned when you opened it, or on any order at GET /v1/allocation/work_orders. It starts with match_geo_aware_workforce_orchestrator_work_order_.

Returns

The order as it now stands, with the state it has reached and what it requires.

id string required

The order's identifier, returned in id by POST /v1/allocation/work_orders when you opened it. It starts with match_geo_aware_workforce_orchestrator_work_order_, never changes, and is the work_order_id every call on the order takes.

object always "work_order" required

Always work_order. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

An order is open until it is offered, filled, cancelled or declared unfilled. Nothing moves it back.

openofferedfilledcancelledunfilled
requester_id string required

The requester the work is for, as you sent it in requester_id at POST /v1/allocation/work_orders: their sub_ subject reference, the subject_ref ANCHOR verified at POST /v1/identity/verifications before the order opened.

service_category string required

The kind of service the work is, as you named it when you opened the order, such as field_maintenance. It names the service; occupation_code names who can do it.

occupation_code string required

The occupation the work needs, as a code from the occupation taxonomy, such as ng-7412. Any supply gap declared for the order carries it, so sourcing demand names the occupation.

headcount integer · minimum 1 required

The order stays open until this many acceptances are held.

headcount_filled integer · minimum 0 optional

How many acceptances the order holds so far, counted from 0. Read it against headcount to see how far the order is from filling.

urgency_class string required

How hard the cascade pushes, and how quickly an offer expires.

standardpriorityemergency
location GeoPoint required

A position reading. Accuracy is carried because a reading without it cannot be judged.

3 fields of GeoPoint
latitude number · minimum -90 · maximum 90 required

Latitude in decimal degrees, from -90 to 90. Negative values are south of the equator.

longitude number · minimum -180 · maximum 180 required

Longitude in decimal degrees, from -180 to 180. Negative values are west of the Greenwich meridian.

accuracy_metres integer · minimum 0 required

The radius the device reports as its confidence. A large value weakens every inference drawn from the point.

location_description string optional

The place in words a worker would recognise, such as Ikeja industrial estate, gate 3. Optional; location is the position the ranking uses.

time_window TimeWindow required

When the work may be done. A window, never a single instant.

2 fields of TimeWindow
starts_at string · date-time required

When the window opens: the earliest the work may start, as an RFC 3339 timestamp in UTC.

ends_at string · date-time required

When the window closes: the latest the work may finish, as an RFC 3339 timestamp in UTC.

constraints array of Constraint optional

Hard constraints and soft preferences in one list, each naming which it is.

3 fields of Constraint
code string required

What the requirement is, as a code such as licence_electrical. A relaxation names the soft preference it dropped by this code, as its preference_code.

kind string required

hard for a requirement that is never relaxed and gates who can be ranked at all; soft for a preference the cascade may drop to widen the pool, with the drop recorded in the order's relaxations.

hardsoft
detail string optional

What the requirement means in words, such as Current electrical licence. Optional; the code is what a worker has to meet, and the detail is for people reading the order.

relaxations array of Relaxation optional

Every soft preference the cascade dropped, with the round it happened in.

4 fields of Relaxation
preference_code string required

The code of the soft preference that was dropped, as it appears in the order's constraints.

round integer · minimum 1 required

The cascade round the preference was dropped in, counted from 1. Match it to the same round in the ranking to see who the wider pool brought in.

relaxed_at string · date-time required

When the preference was dropped, as an RFC 3339 timestamp in UTC.

reason string required

Why the preference was dropped: pool_exhausted when the eligible pool ran out, expiry_approaching when the order's expires_at was near, headcount_unmet when acceptances fell short of headcount.

pool_exhaustedexpiry_approachingheadcount_unmet
escalation_bounds EscalationBounds optional

How far the search may widen. It's published on the order, so you can read the escalation policy.

3 fields of EscalationBounds
initial_radius_metres integer · minimum 0 required

The radius the first round searches within, in metres from location.

maximum_radius_metres integer · minimum 0 required

The furthest the radius may widen to, in metres from location. No round searches beyond it.

step_metres integer · minimum 1 required

How much the radius widens by on each escalation, in metres, at least 1. A round that widened is marked escalated in the ranking.

budget_ceiling Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

expires_at string · date-time required

On expiry the order is declared unfilled with a reason, never left open.

cancelled_reason string · nullable optional

Why the order was cancelled, as stated when it was cancelled. null while the order has not been cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X GET "https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationWorkOrdersRetrieve({ workOrderId: 'match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
  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.MATCHApi(client)

result = api.allocation_work_orders_retrieve(work_order_id='match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationWorkOrdersRetrieve(work_order_id: 'match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
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.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationWorkOrdersRetrieve("match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"))
    .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 MATCHApi(config);

var result = api.AllocationWorkOrdersRetrieve(workOrderId: "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
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.MATCHAPI.AllocationWorkOrdersRetrieve(ctx, "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "work_order",
  "livemode": false,
  "mocked": true,
  "status": "open",
  "requester_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "service_category": "field_maintenance",
  "occupation_code": "ng-7412",
  "headcount": 1,
  "urgency_class": "standard",
  "location": {
    "latitude": -90,
    "longitude": -180,
    "accuracy_metres": 0
  },
  "time_window": {
    "starts_at": "2026-09-01T09:00:00Z",
    "ends_at": "2026-09-01T09:00:00Z"
  },
  "expires_at": "2026-09-01T09:00:00Z",
  "headcount_filled": 0,
  "location_description": "example",
  "constraints": [
    {
      "code": "licence_electrical",
      "kind": "hard",
      "detail": "The payee has no verified destination, so this line cannot be paid."
    }
  ],
  "relaxations": [
    {
      "preference_code": "example",
      "round": 1,
      "relaxed_at": "2026-09-01T09:00:00Z",
      "reason": "pool_exhausted"
    }
  ],
  "escalation_bounds": {
    "initial_radius_metres": 0,
    "maximum_radius_metres": 0,
    "step_metres": 1
  },
  "budget_ceiling": {
    "amount": 100000000,
    "currency": "NGN"
  },
  "cancelled_reason": "example",
  "created_at": "2026-09-01T09:00:00Z"
}
POST/v1/allocation/work_orders/{work_order_id}/cancel#

Cancel a work order

allocation.work_orders.cancel

Cancelling is how a job you no longer need stops being live demand. You pick the reason from a fixed set of four, so your own reporting can count why orders were cancelled instead of reading free text.

Send the reason you are cancelling as one of the four published codes. The call takes the order to cancelled, and the whole order comes back so your own screen can stop showing it as live demand.

Nothing else has to exist first: the identifier in the path is the order you opened at POST /v1/allocation/work_orders.

Once an order reads cancelled, offering, filling or expiring it is refused with conflict.

Path parameters

work_order_id string required

The order's identifier, from id on the order POST /v1/allocation/work_orders returned when you opened it, or on any order at GET /v1/allocation/work_orders. It starts with match_geo_aware_workforce_orchestrator_work_order_.

Headers

Idempotency-Key string required

A 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

reason string required

Why you are cancelling: requester_withdrew, no_longer_required, raised_in_error or worker_removed_from_cascade (the worker lost standing in ANCHOR or RAIL). It reaches the worker where one was offered or assigned.

requester_withdrewno_longer_requiredraised_in_errorworker_removed_from_cascade
detail string optional

More about why, in your own words, for whoever reads the record later. Optional; reason is the fixed code.

Returns

The order after the cancellation, which is what your own records should now match.

id string required

The order's identifier, returned in id by POST /v1/allocation/work_orders when you opened it. It starts with match_geo_aware_workforce_orchestrator_work_order_, never changes, and is the work_order_id every call on the order takes.

object always "work_order" required

Always work_order. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

An order is open until it is offered, filled, cancelled or declared unfilled. Nothing moves it back.

openofferedfilledcancelledunfilled
requester_id string required

The requester the work is for, as you sent it in requester_id at POST /v1/allocation/work_orders: their sub_ subject reference, the subject_ref ANCHOR verified at POST /v1/identity/verifications before the order opened.

service_category string required

The kind of service the work is, as you named it when you opened the order, such as field_maintenance. It names the service; occupation_code names who can do it.

occupation_code string required

The occupation the work needs, as a code from the occupation taxonomy, such as ng-7412. Any supply gap declared for the order carries it, so sourcing demand names the occupation.

headcount integer · minimum 1 required

The order stays open until this many acceptances are held.

headcount_filled integer · minimum 0 optional

How many acceptances the order holds so far, counted from 0. Read it against headcount to see how far the order is from filling.

urgency_class string required

How hard the cascade pushes, and how quickly an offer expires.

standardpriorityemergency
location GeoPoint required

A position reading. Accuracy is carried because a reading without it cannot be judged.

3 fields of GeoPoint
latitude number · minimum -90 · maximum 90 required

Latitude in decimal degrees, from -90 to 90. Negative values are south of the equator.

longitude number · minimum -180 · maximum 180 required

Longitude in decimal degrees, from -180 to 180. Negative values are west of the Greenwich meridian.

accuracy_metres integer · minimum 0 required

The radius the device reports as its confidence. A large value weakens every inference drawn from the point.

location_description string optional

The place in words a worker would recognise, such as Ikeja industrial estate, gate 3. Optional; location is the position the ranking uses.

time_window TimeWindow required

When the work may be done. A window, never a single instant.

2 fields of TimeWindow
starts_at string · date-time required

When the window opens: the earliest the work may start, as an RFC 3339 timestamp in UTC.

ends_at string · date-time required

When the window closes: the latest the work may finish, as an RFC 3339 timestamp in UTC.

constraints array of Constraint optional

Hard constraints and soft preferences in one list, each naming which it is.

3 fields of Constraint
code string required

What the requirement is, as a code such as licence_electrical. A relaxation names the soft preference it dropped by this code, as its preference_code.

kind string required

hard for a requirement that is never relaxed and gates who can be ranked at all; soft for a preference the cascade may drop to widen the pool, with the drop recorded in the order's relaxations.

hardsoft
detail string optional

What the requirement means in words, such as Current electrical licence. Optional; the code is what a worker has to meet, and the detail is for people reading the order.

relaxations array of Relaxation optional

Every soft preference the cascade dropped, with the round it happened in.

4 fields of Relaxation
preference_code string required

The code of the soft preference that was dropped, as it appears in the order's constraints.

round integer · minimum 1 required

The cascade round the preference was dropped in, counted from 1. Match it to the same round in the ranking to see who the wider pool brought in.

relaxed_at string · date-time required

When the preference was dropped, as an RFC 3339 timestamp in UTC.

reason string required

Why the preference was dropped: pool_exhausted when the eligible pool ran out, expiry_approaching when the order's expires_at was near, headcount_unmet when acceptances fell short of headcount.

pool_exhaustedexpiry_approachingheadcount_unmet
escalation_bounds EscalationBounds optional

How far the search may widen. It's published on the order, so you can read the escalation policy.

3 fields of EscalationBounds
initial_radius_metres integer · minimum 0 required

The radius the first round searches within, in metres from location.

maximum_radius_metres integer · minimum 0 required

The furthest the radius may widen to, in metres from location. No round searches beyond it.

step_metres integer · minimum 1 required

How much the radius widens by on each escalation, in metres, at least 1. A round that widened is marked escalated in the ranking.

budget_ceiling Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

expires_at string · date-time required

On expiry the order is declared unfilled with a reason, never left open.

cancelled_reason string · nullable optional

Why the order was cancelled, as stated when it was cancelled. null while the order has not been cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X POST "https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/cancel" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"reason":"requester_withdrew","detail":"The payee has no verified destination, so this line cannot be paid."}'
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationWorkOrdersCancel({
  workOrderId: 'match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
  idempotencyKey: crypto.randomUUID(),
  matchCancellationRequest: {"reason":"requester_withdrew","detail":"The payee has no verified destination, so this line cannot be paid."},
});
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/cancel', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    "reason": "requester_withdrew",
    "detail": "The payee has no verified destination, so this line cannot be paid."
  }),
});
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.MATCHApi(client)

result = api.allocation_work_orders_cancel(work_order_id='match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', body={"reason": "requester_withdrew", "detail": "The payee has no verified destination, so this line cannot be paid."})
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/cancel',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
        'Idempotency-Key': str(uuid.uuid4()),
    },
    json={"reason": "requester_withdrew", "detail": "The payee has no verified destination, so this line cannot be paid."},
)
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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationWorkOrdersCancel($idempotencyKey, json_decode('{"reason":"requester_withdrew","detail":"The payee has no verified destination, so this line cannot be paid."}', true));
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/cancel');
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 => '{"reason":"requester_withdrew","detail":"The payee has no verified destination, so this line cannot be paid."}',
]);
$result = json_decode(curl_exec($ch), true);
import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationWorkOrdersCancel("match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/cancel"))
    .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("""
        {
          "reason": "requester_withdrew",
          "detail": "The payee has no verified destination, so this line cannot be paid."
        }
        """))
    .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 MATCHApi(config);

var result = api.AllocationWorkOrdersCancel(workOrderId: "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/cancel");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
    {
      "reason": "requester_withdrew",
      "detail": "The payee has no verified destination, so this line cannot be paid."
    }
    """, 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.MATCHAPI.AllocationWorkOrdersCancel(ctx, "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").IdempotencyKey(key).MatchCancellationRequest(body).Execute()
body := strings.NewReader(`{
  "reason": "requester_withdrew",
  "detail": "The payee has no verified destination, so this line cannot be paid."
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/cancel", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "work_order",
  "livemode": false,
  "mocked": true,
  "status": "open",
  "requester_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "service_category": "field_maintenance",
  "occupation_code": "ng-7412",
  "headcount": 1,
  "urgency_class": "standard",
  "location": {
    "latitude": -90,
    "longitude": -180,
    "accuracy_metres": 0
  },
  "time_window": {
    "starts_at": "2026-09-01T09:00:00Z",
    "ends_at": "2026-09-01T09:00:00Z"
  },
  "expires_at": "2026-09-01T09:00:00Z",
  "headcount_filled": 0,
  "location_description": "example",
  "constraints": [
    {
      "code": "licence_electrical",
      "kind": "hard",
      "detail": "The payee has no verified destination, so this line cannot be paid."
    }
  ],
  "relaxations": [
    {
      "preference_code": "example",
      "round": 1,
      "relaxed_at": "2026-09-01T09:00:00Z",
      "reason": "pool_exhausted"
    }
  ],
  "escalation_bounds": {
    "initial_radius_metres": 0,
    "maximum_radius_metres": 0,
    "step_metres": 1
  },
  "budget_ceiling": {
    "amount": 100000000,
    "currency": "NGN"
  },
  "cancelled_reason": "example",
  "created_at": "2026-09-01T09:00:00Z"
}
GET/v1/allocation/work_orders/{work_order_id}/ranking#

Who was considered, and who was offered

allocation.rankings.retrieve

The ranking is the allocation record kept against one work order. A screen in your own product can show who was considered for a job and who was offered it, instead of only the assignment that came out of it.

You name the work order in the path and get its ranking back, one entry per pass of the cascade, each with the candidates on it and the radius it searched.

A candidate whose eligible is false carries no scores and no rank, so read eligible before you read a number and handle the nulls. The surge figures are placeholders in both realms, so don't act on the index.

Each offer at GET /v1/allocation/work_orders/{work_order_id}/offers carries the round it was made in, so you can line the offers up with the passes here. A candidate who was offered nothing carries non_offer_reason, which is what that worker is told.

Path parameters

work_order_id string required

The order's identifier, from id on the order POST /v1/allocation/work_orders returned when you opened it, or on any order at GET /v1/allocation/work_orders. It starts with match_geo_aware_workforce_orchestrator_work_order_.

Query parameters

round integer optional

One pass of the cascade, counted from 1: the round on each entry of the ranking's rounds, and on each offer from GET /v1/allocation/work_orders/{work_order_id}/offers. Leave it out to get every round.

Returns

The work order's ranking, which is where you read who was considered and who was offered.

object always "ranking" required

Always ranking. Tells you which kind of record you are looking at, so one handler can read any response.

work_order_id string required

The id of the work order this ranking is for, as POST /v1/allocation/work_orders returned it, starting with match_geo_aware_workforce_orchestrator_work_order_. One ranking per order, with a round appended on each re-rank.

weight_set_version string required

The version of the weights in force when this ranking ran.

rounds array of RankingRound required

Every pass of the cascade in order from round 1, each with every candidate considered, the radius it searched and whether it widened. Pass round on the request to read one pass only.

5 fields of RankingRound
round integer · minimum 1 required

Which pass of the cascade this is, counted from 1. A decline or a timeout opens the next, and offers and relaxations carry the same number so you can line them up.

ranked_at string · date-time required

When this pass ranked the pool, as an RFC 3339 timestamp in UTC. Supply as it stood at that moment is what was ranked, not the pool an earlier pass saw.

radius_metres integer · minimum 0 required

Within the bounds published on the work order.

escalated boolean optional

True where this round widened the radius over the previous one.

candidates array of RankingCandidate required

Every candidate considered, eligible or not. Stored in full.

8 fields of RankingCandidate
worker_id string required

The worker this candidate is, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens. The same identifier you read their supply state with at GET /v1/allocation/supply_states/{worker_id}.

eligible boolean required

true when the worker passed every gate and was scored. false when a gate excluded them: failed_gates names it, and no score is present.

failed_gates array of EligibilityGate optional

Empty for an eligible candidate.

components one of optional

The five scores this candidate was ranked on: travel time, competence, reliability, fatigue and cost. Null for an ineligible candidate, who is never scored.

ScoreComponentsor
total_score number · nullable optional

The candidate's overall score, combining the five components under the weight set the ranking names. Null for an ineligible candidate.

rank integer · nullable · minimum 1 optional

The candidate's position in this round, 1 being the top. Null for an ineligible candidate.

offered boolean optional

true when this round made the worker an offer, false when it did not. Where it is false, non_offer_reason says why.

non_offer_reason one of optional

Why this worker got no offer in this round: a code from a closed set, with the wording the worker sees. Null where they were offered.

NonOfferReasonor
surge object optional

Surge index and incentive targeting are deferred. The values here are placeholders and carry mocked true even in the live realm.

2 fields
mocked always true required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

index number required

The surge index, as a placeholder. Surge and incentive targeting are deferred, so this value is mocked even in the live realm; don't act on it.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# query parameters: round (optional)
curl -X GET "https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/ranking?round=1" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

// query parameters: round (optional)
const result = await api.allocationRankingsRetrieve({ workOrderId: 'match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', round: 1 });
// query parameters: round (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/ranking?round=1', {
  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.MATCHApi(client)

# query parameters: round (optional)
result = api.allocation_rankings_retrieve(work_order_id='match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', round=1)
import os

import requests

# query parameters: round (optional)
response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/ranking?round=1',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

# query parameters: round (optional)
$result = $api->allocationRankingsRetrieve(work_order_id: 'match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', round: 1);
<?php
// query parameters: round (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/ranking?round=1');
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.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

// query parameters: round (optional)
var result = api.allocationRankingsRetrieve("match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", 1);
// query parameters: round (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/ranking?round=1"))
    .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 MATCHApi(config);

// query parameters: round (optional)
var result = api.AllocationRankingsRetrieve(workOrderId: "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", round: 1);
// query parameters: round (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/ranking?round=1");
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: round (optional)
result, _, err := client.MATCHAPI.AllocationRankingsRetrieve(ctx, "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Round(1).Execute()
// query parameters: round (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/ranking?round=1", 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)
Response
{
  "object": "ranking",
  "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "weight_set_version": "2026.08.1",
  "rounds": [
    {
      "round": 1,
      "ranked_at": "2026-09-01T09:00:00Z",
      "radius_metres": 0,
      "candidates": [
        {
          "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
          "eligible": true,
          "failed_gates": [
            "passport_live"
          ],
          "components": {
            "travel_time": {
              "seconds": 0,
              "score": 1,
              "route_distance_metres": 0
            },
            "competence": {
              "score": 1,
              "proof_claim_ids": [
                "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
              ]
            },
            "reliability": {
              "score": 1,
              "completed_assignments": 0
            },
            "fatigue": {
              "score": 1,
              "hours_worked_last_7_days": 1
            },
            "cost": {
              "score": 1
            }
          },
          "total_score": 1,
          "rank": 1,
          "offered": true,
          "non_offer_reason": {
            "code": "ranked_below_threshold",
            "worker_message": "This job was filled before your turn came round."
          }
        }
      ],
      "escalated": true
    }
  ],
  "surge": {
    "mocked": true,
    "index": 1
  }
}
GET/v1/allocation/work_orders/{work_order_id}/offers#

Follow the cascade for one order

allocation.offers.list

This is the record of who one work order has been put to, and in what order. It gives you the dispatcher's view of a job in flight: who was asked, who answered, and who is still holding it.

You get every offer made for the work order in the path, oldest first, each one naming its round, the worker it went to and whether that worker has answered yet.

Read the list whole and narrow it in your own code.

An unknown work order identifier is refused with not_found, so you get that refusal and not an empty list. Read one offer in full at GET /v1/allocation/offers/{offer_id}.

Path parameters

work_order_id string required

The order's identifier, from id on the order POST /v1/allocation/work_orders returned when you opened it, or on any order at GET /v1/allocation/work_orders. It starts with match_geo_aware_workforce_orchestrator_work_order_.

Query parameters

limit integer optional

How many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.

starting_after string optional

The id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.

status string optional

Return only offers in one state: made while the worker can still answer, accepted, declined, expired once expires_at passed unanswered, or withdrawn when the order filled elsewhere, was cancelled, or the worker left the cascade. Leave it out to get every offer.

madeaccepteddeclinedexpiredwithdrawn

Returns

Every offer made for this work order, oldest first, with the round each belongs to

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of Offer required

The records on this page, in the order the list promises. Empty when nothing matched.

13 fields of Offer
id string required

The offer's identifier. It starts with match_geo_aware_workforce_orchestrator_offer_ and never changes; pass it as {offer_id} to GET /v1/allocation/offers/{offer_id} and to its /accept and /decline calls.

object always "offer" required

Always offer. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

work_order_id string required

The id of the work order this offer is for, as POST /v1/allocation/work_orders returned it, starting with match_geo_aware_workforce_orchestrator_work_order_. Pass it as {work_order_id} to read the order and its ranking.

worker_id string required

The worker this offer went to, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens. One offer names one worker.

status string required
madeaccepteddeclinedexpiredwithdrawn
round integer · minimum 1 required

The cascade round this offer belongs to.

rank integer · minimum 1 optional

Where the worker stood in the round's ranking when this offer was made, 1 being the top.

expires_at string · date-time required

An offer that is not answered by this time is treated as a decline.

closed_reason one of optional

Set where the offer ended without acceptance, including where the order filled elsewhere.

NonOfferReasonor
assignment_id string · nullable optional

The id of the assignment this offer became, starting with match_geo_aware_workforce_orchestrator_assignment_: the record POST /v1/allocation/offers/{offer_id}/accept returned. null until the offer is accepted.

made_at string · date-time optional

When the offer was made, as an RFC 3339 timestamp in UTC. Offers list in this order.

has_more boolean required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

next_cursor string · nullable optional

Where the next page starts, or null when this is the last page. Pass it back unchanged to continue.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# query parameters: limit (optional), starting_after (optional), status (optional)
curl -X GET "https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/offers?limit=25&status=made" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

// query parameters: limit (optional), starting_after (optional), status (optional)
const result = await api.allocationOffersList({ workOrderId: 'match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', limit: 25, status: 'made' });
// query parameters: limit (optional), starting_after (optional), status (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/offers?limit=25&status=made', {
  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.MATCHApi(client)

# query parameters: limit (optional), starting_after (optional), status (optional)
result = api.allocation_offers_list(work_order_id='match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', limit=25, status='made')
import os

import requests

# query parameters: limit (optional), starting_after (optional), status (optional)
response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/offers?limit=25&status=made',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

# query parameters: limit (optional), starting_after (optional), status (optional)
$result = $api->allocationOffersList(work_order_id: 'match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', limit: 25, status: 'made');
<?php
// query parameters: limit (optional), starting_after (optional), status (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/offers?limit=25&status=made');
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.MatchApi;
import com.droomwork.sdk.model.*;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

// query parameters: limit (optional), starting_after (optional), status (optional)
var result = api.allocationOffersList("match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", 25, null, MatchOfferStatus.fromValue("made"));
// query parameters: limit (optional), starting_after (optional), status (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/offers?limit=25&status=made"))
    .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 MATCHApi(config);

// query parameters: limit (optional), starting_after (optional), status (optional)
var result = api.AllocationOffersList(workOrderId: "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", limit: 25, status: MatchOfferStatus.Made);
// query parameters: limit (optional), starting_after (optional), status (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/offers?limit=25&status=made");
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), starting_after (optional), status (optional)
result, _, err := client.MATCHAPI.AllocationOffersList(ctx, "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Limit(25).Status(droomwork.MatchOfferStatus("made")).Execute()
// query parameters: limit (optional), starting_after (optional), status (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/offers?limit=25&status=made", 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)
Response
{
  "object": "list",
  "data": [
    {
      "id": "match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "offer",
      "livemode": false,
      "mocked": true,
      "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "status": "made",
      "round": 1,
      "expires_at": "2026-09-01T09:00:00Z",
      "rank": 1,
      "closed_reason": {
        "code": "ranked_below_threshold",
        "worker_message": "This job was filled before your turn came round."
      },
      "assignment_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "made_at": "2026-09-01T09:00:00Z"
    }
  ],
  "has_more": true,
  "next_cursor": "example"
}
GET/v1/allocation/offers/{offer_id}#

Check where one offer stands

allocation.offers.retrieve

This answers what became of one offer: whether the worker still has it, and where it ended if it ended. It is the detail behind a single row of the cascade, and it is enough to build the worker's own view of the job they were asked to do.

You pass one offer's identifier and get that offer whole: the work order behind it, the worker it went to, its round, and where it stands. An offer is one job put to one worker in one round of the cascade.

Where closed_reason is present it carries the wording written for the worker. Show that wording instead of composing your own.

The cascade this offer sits in is at GET /v1/allocation/work_orders/{work_order_id}/offers.

Path parameters

offer_id string required

The offer's identifier, from the id of an offer listed at GET /v1/allocation/work_orders/{work_order_id}/offers or received on the offer.made event. It starts with match_geo_aware_workforce_orchestrator_offer_.

Returns

One offer, the worker it went to, and where it now stands

id string required

The offer's identifier. It starts with match_geo_aware_workforce_orchestrator_offer_ and never changes; pass it as {offer_id} to GET /v1/allocation/offers/{offer_id} and to its /accept and /decline calls.

object always "offer" required

Always offer. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

work_order_id string required

The id of the work order this offer is for, as POST /v1/allocation/work_orders returned it, starting with match_geo_aware_workforce_orchestrator_work_order_. Pass it as {work_order_id} to read the order and its ranking.

worker_id string required

The worker this offer went to, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens. One offer names one worker.

status string required
madeaccepteddeclinedexpiredwithdrawn
round integer · minimum 1 required

The cascade round this offer belongs to.

rank integer · minimum 1 optional

Where the worker stood in the round's ranking when this offer was made, 1 being the top.

expires_at string · date-time required

An offer that is not answered by this time is treated as a decline.

closed_reason one of optional

Set where the offer ended without acceptance, including where the order filled elsewhere.

NonOfferReasonor
assignment_id string · nullable optional

The id of the assignment this offer became, starting with match_geo_aware_workforce_orchestrator_assignment_: the record POST /v1/allocation/offers/{offer_id}/accept returned. null until the offer is accepted.

made_at string · date-time optional

When the offer was made, as an RFC 3339 timestamp in UTC. Offers list in this order.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X GET "https://sandbox.droomwork.io/v1/allocation/offers/match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationOffersRetrieve({ offerId: 'match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/offers/match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
  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.MATCHApi(client)

result = api.allocation_offers_retrieve(offer_id='match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/allocation/offers/match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationOffersRetrieve(offer_id: 'match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/offers/match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
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.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationOffersRetrieve("match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/offers/match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"))
    .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 MATCHApi(config);

var result = api.AllocationOffersRetrieve(offerId: "match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/allocation/offers/match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
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.MATCHAPI.AllocationOffersRetrieve(ctx, "match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/allocation/offers/match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "offer",
  "livemode": false,
  "mocked": true,
  "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "status": "made",
  "round": 1,
  "expires_at": "2026-09-01T09:00:00Z",
  "rank": 1,
  "closed_reason": {
    "code": "ranked_below_threshold",
    "worker_message": "This job was filled before your turn came round."
  },
  "assignment_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "made_at": "2026-09-01T09:00:00Z"
}
POST/v1/allocation/offers/{offer_id}/accept#

Accept an offer

allocation.offers.accept

This is where a worker says yes. It is the one call behind the accept button in their app, and it is the point at which a job stops being an ask and becomes work somebody is committed to.

Send the moment the worker accepted as accepted_at, and the position their device read if it had one. One call answers one offer, and the offer names the one worker it went to.

The offer in the path is the prerequisite; the cascade made it when the work order went out.

accepted_at is the worker's own moment, not the moment your call arrives, so send what their device recorded and not a reading from your own clock.

Path parameters

offer_id string required

The offer's identifier, from the id of an offer listed at GET /v1/allocation/work_orders/{work_order_id}/offers or received on the offer.made event. It starts with match_geo_aware_workforce_orchestrator_offer_.

Headers

Idempotency-Key string required

A 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

accepted_at string · date-time required

When the worker accepted, as an RFC 3339 timestamp in UTC. An offer left unanswered past its expires_at has already been treated as a decline.

position GeoPoint optional

A position reading. Accuracy is carried because a reading without it cannot be judged.

3 fields of GeoPoint
latitude number · minimum -90 · maximum 90 required

Latitude in decimal degrees, from -90 to 90. Negative values are south of the equator.

longitude number · minimum -180 · maximum 180 required

Longitude in decimal degrees, from -180 to 180. Negative values are west of the Greenwich meridian.

accuracy_metres integer · minimum 0 required

The radius the device reports as its confidence. A large value weakens every inference drawn from the point.

Returns

The assignment the acceptance created, at accepted, which is where every later step on this job starts.

id string required

The assignment's identifier. It starts with match_geo_aware_workforce_orchestrator_assignment_ and never changes; pass it as {assignment_id} to GET /v1/allocation/assignments/{assignment_id} and its arrival, completion, cancel and proof calls.

object always "assignment" required

Always assignment. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

work_order_id string required

The id of the work order this assignment fills, as POST /v1/allocation/work_orders returned it, starting with match_geo_aware_workforce_orchestrator_work_order_. Pass it as {work_order_id} to read the order.

worker_id string required

The worker doing the work, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens. Every arrival and completion signal on this record is about this one person.

offer_id string optional

The id of the accepted offer this assignment came from, as listed at GET /v1/allocation/work_orders/{work_order_id}/offers, starting with match_geo_aware_workforce_orchestrator_offer_. Absent when you created it directly for work agreed elsewhere.

status string required

Work that has started resolves through completion or through exception. It is never cancelled, so the evidence of what happened survives.

accepteden_routearrivedin_progresscompletedexceptioncancelled
geofence_entry one of optional

The worker's entry into the geofence around the work location: when, where and the radius, one of the two signals arrival rests on. Null until arrival is confirmed.

GeofenceEntryor
start_passcode_verified_at string · date-time · nullable optional

When the start passcode the requester gave the worker on site was verified, as an RFC 3339 timestamp in UTC: the second of the two arrival signals. Null until arrival is confirmed.

completion_passcode_verified_at string · date-time · nullable optional

When the completion passcode was verified, as an RFC 3339 timestamp in UTC: one of the three signals completion rests on. Null until you confirm completion.

dwell one of optional

How long the worker was at the location, as you sent it on completion: when it started, when it ended and the whole seconds between. null until you confirm completion.

DwellRecordor
telemetry_continuity one of optional

Whether the heartbeat stream held across the working window, as you sent it on completion: continuous or not, how many gaps, and the longest in seconds. null until you confirm completion.

TelemetryContinuityor
proof_of_service_id string · nullable optional

The id of the sealed proof for this assignment, starting with match_geo_aware_workforce_orchestrator_proof_, as GET /v1/allocation/assignments/{assignment_id}/proof returns it. null until the bundle is sealed.

exception_id string · nullable optional

The id of the exception raised on this assignment, starting with match_geo_aware_workforce_orchestrator_exception_, as listed at GET /v1/allocation/exceptions. null unless the signals disagreed and a person was asked.

cancelled_reason string · nullable optional

Why the assignment was cancelled: the reason sent with the cancellation, or worker_removed_from_cascade when ANCHOR or RAIL removed the worker. null unless status is cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X POST "https://sandbox.droomwork.io/v1/allocation/offers/match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/accept" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"accepted_at":"2026-09-01T09:00:00Z","position":{"latitude":-90,"longitude":-180,"accuracy_metres":0}}'
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationOffersAccept({
  offerId: 'match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
  idempotencyKey: crypto.randomUUID(),
  matchOfferAcceptRequest: {"acceptedAt":"2026-09-01T09:00:00Z","position":{"latitude":-90,"longitude":-180,"accuracyMetres":0}},
});
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/offers/match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/accept', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    "accepted_at": "2026-09-01T09:00:00Z",
    "position": {
      "latitude": -90,
      "longitude": -180,
      "accuracy_metres": 0
    }
  }),
});
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.MATCHApi(client)

result = api.allocation_offers_accept(offer_id='match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', body={"accepted_at": "2026-09-01T09:00:00Z", "position": {"latitude": -90, "longitude": -180, "accuracy_metres": 0}})
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/allocation/offers/match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/accept',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
        'Idempotency-Key': str(uuid.uuid4()),
    },
    json={"accepted_at": "2026-09-01T09:00:00Z", "position": {"latitude": -90, "longitude": -180, "accuracy_metres": 0}},
)
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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationOffersAccept($idempotencyKey, json_decode('{"accepted_at":"2026-09-01T09:00:00Z","position":{"latitude":-90,"longitude":-180,"accuracy_metres":0}}', true));
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/offers/match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/accept');
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 => '{"accepted_at":"2026-09-01T09:00:00Z","position":{"latitude":-90,"longitude":-180,"accuracy_metres":0}}',
]);
$result = json_decode(curl_exec($ch), true);
import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationOffersAccept("match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/offers/match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/accept"))
    .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("""
        {
          "accepted_at": "2026-09-01T09:00:00Z",
          "position": {
            "latitude": -90,
            "longitude": -180,
            "accuracy_metres": 0
          }
        }
        """))
    .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 MATCHApi(config);

var result = api.AllocationOffersAccept(offerId: "match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/allocation/offers/match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/accept");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
    {
      "accepted_at": "2026-09-01T09:00:00Z",
      "position": {
        "latitude": -90,
        "longitude": -180,
        "accuracy_metres": 0
      }
    }
    """, 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.MATCHAPI.AllocationOffersAccept(ctx, "match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").IdempotencyKey(key).MatchOfferAcceptRequest(body).Execute()
body := strings.NewReader(`{
  "accepted_at": "2026-09-01T09:00:00Z",
  "position": {
    "latitude": -90,
    "longitude": -180,
    "accuracy_metres": 0
  }
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/allocation/offers/match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/accept", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "assignment",
  "livemode": false,
  "mocked": true,
  "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "status": "accepted",
  "offer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "geofence_entry": {
    "entered_at": "2026-09-01T09:00:00Z",
    "position": {
      "latitude": -90,
      "longitude": -180,
      "accuracy_metres": 0
    },
    "radius_metres": 0
  },
  "start_passcode_verified_at": "2026-09-01T09:00:00Z",
  "completion_passcode_verified_at": "2026-09-01T09:00:00Z",
  "dwell": {
    "started_at": "2026-09-01T09:00:00Z",
    "ended_at": "2026-09-01T09:00:00Z",
    "seconds": 0
  },
  "telemetry_continuity": {
    "continuous": true,
    "gap_count": 0,
    "longest_gap_seconds": 0
  },
  "proof_of_service_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "exception_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "cancelled_reason": "example",
  "created_at": "2026-09-01T09:00:00Z"
}
POST/v1/allocation/offers/{offer_id}/decline#

Decline an offer

allocation.offers.decline

This is where a worker says no, and says why. It is the other half of the accept button, and it is what separates a worker who turned the job down from one who never answered at all.

Send the reason the worker gave, and any words behind it as detail. You get the declined offer back, so your own record of the cascade and Droomwork's say the same thing.

The offer in the path is the prerequisite; the cascade made it when the work order went out.

reason is required and comes from a fixed set, so a worker who gave no explanation still needs a code chosen for them. Put what they actually said in detail, where a code alone would lose it.

Path parameters

offer_id string required

The offer's identifier, from the id of an offer listed at GET /v1/allocation/work_orders/{work_order_id}/offers or received on the offer.made event. It starts with match_geo_aware_workforce_orchestrator_offer_.

Headers

Idempotency-Key string required

A 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

reason string required

Why the worker declined: too_far, unavailable, rate_too_low, not_qualified, no_reason_given or offer_expired (unanswered past expires_at). The offer keeps it, so you can read the cascade as a sequence later.

too_farunavailablerate_too_lownot_qualifiedno_reason_givenoffer_expired
detail string optional

Optional words behind the reason, such as what the worker said. The reason code is what's required; send this when a code alone would lose something.

Returns

The same offer, now declined, with the round it belonged to

id string required

The offer's identifier. It starts with match_geo_aware_workforce_orchestrator_offer_ and never changes; pass it as {offer_id} to GET /v1/allocation/offers/{offer_id} and to its /accept and /decline calls.

object always "offer" required

Always offer. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

work_order_id string required

The id of the work order this offer is for, as POST /v1/allocation/work_orders returned it, starting with match_geo_aware_workforce_orchestrator_work_order_. Pass it as {work_order_id} to read the order and its ranking.

worker_id string required

The worker this offer went to, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens. One offer names one worker.

status string required
madeaccepteddeclinedexpiredwithdrawn
round integer · minimum 1 required

The cascade round this offer belongs to.

rank integer · minimum 1 optional

Where the worker stood in the round's ranking when this offer was made, 1 being the top.

expires_at string · date-time required

An offer that is not answered by this time is treated as a decline.

closed_reason one of optional

Set where the offer ended without acceptance, including where the order filled elsewhere.

NonOfferReasonor
assignment_id string · nullable optional

The id of the assignment this offer became, starting with match_geo_aware_workforce_orchestrator_assignment_: the record POST /v1/allocation/offers/{offer_id}/accept returned. null until the offer is accepted.

made_at string · date-time optional

When the offer was made, as an RFC 3339 timestamp in UTC. Offers list in this order.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X POST "https://sandbox.droomwork.io/v1/allocation/offers/match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/decline" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"reason":"too_far","detail":"The payee has no verified destination, so this line cannot be paid."}'
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationOffersDecline({
  offerId: 'match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
  idempotencyKey: crypto.randomUUID(),
  matchOfferDeclineRequest: {"reason":"too_far","detail":"The payee has no verified destination, so this line cannot be paid."},
});
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/offers/match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/decline', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    "reason": "too_far",
    "detail": "The payee has no verified destination, so this line cannot be paid."
  }),
});
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.MATCHApi(client)

result = api.allocation_offers_decline(offer_id='match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', body={"reason": "too_far", "detail": "The payee has no verified destination, so this line cannot be paid."})
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/allocation/offers/match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/decline',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
        'Idempotency-Key': str(uuid.uuid4()),
    },
    json={"reason": "too_far", "detail": "The payee has no verified destination, so this line cannot be paid."},
)
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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationOffersDecline($idempotencyKey, json_decode('{"reason":"too_far","detail":"The payee has no verified destination, so this line cannot be paid."}', true));
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/offers/match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/decline');
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 => '{"reason":"too_far","detail":"The payee has no verified destination, so this line cannot be paid."}',
]);
$result = json_decode(curl_exec($ch), true);
import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationOffersDecline("match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/offers/match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/decline"))
    .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("""
        {
          "reason": "too_far",
          "detail": "The payee has no verified destination, so this line cannot be paid."
        }
        """))
    .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 MATCHApi(config);

var result = api.AllocationOffersDecline(offerId: "match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/allocation/offers/match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/decline");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
    {
      "reason": "too_far",
      "detail": "The payee has no verified destination, so this line cannot be paid."
    }
    """, 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.MATCHAPI.AllocationOffersDecline(ctx, "match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").IdempotencyKey(key).MatchOfferDeclineRequest(body).Execute()
body := strings.NewReader(`{
  "reason": "too_far",
  "detail": "The payee has no verified destination, so this line cannot be paid."
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/allocation/offers/match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/decline", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_offer_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "offer",
  "livemode": false,
  "mocked": true,
  "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "status": "made",
  "round": 1,
  "expires_at": "2026-09-01T09:00:00Z",
  "rank": 1,
  "closed_reason": {
    "code": "ranked_below_threshold",
    "worker_message": "This job was filled before your turn came round."
  },
  "assignment_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "made_at": "2026-09-01T09:00:00Z"
}
GET/v1/allocation/dispersion#

See how offers are spread

allocation.dispersion.retrieve

This read is about the spread of your offers and not about one piece of work. A fairness review in your own product can come from this one call, instead of counting offers work order by work order yourself.

Send nothing and one reading comes back: the minimum offers a worker is entitled to in the window, a row for each band of the population with its offer counts, and how many workers sit under the minimum.

mean_offers_per_worker is an average over a whole band, so a band can read above the minimum and still hold workers under it. workers_below_floor is the headcount to act on.

The report counts people and never names them. No worker identifier appears anywhere in it, so a short band tells you how many workers are under the minimum and not which ones.

Query parameters

window string optional

The period the figures cover: last_24_hours, last_7_days or last_30_days. Leave it out for last_7_days.

last_24_hourslast_7_dayslast_30_days
occupation_code string optional

Restricts the report to one occupation, by its taxonomy code such as ng-7412, the occupation_code you set at POST /v1/allocation/work_orders: the offers made for that work and the workers eligible for it. Leave it out to get every occupation.

zone string optional

Restricts the report to one operational zone, by the zone's id such as zone_lagos_mainland_03, as zone.id reads on a supply state from GET /v1/allocation/supply_states. Leave it out to get every zone.

Returns

One reading of offer dispersion, with the minimum in force and how many workers are under it.

object always "dispersion_report" required

Always dispersion_report. Tells you which kind of record you are looking at, so one handler can read any response.

window string required

The period the figures cover: last_24_hours, last_7_days or last_30_days. The window you sent to GET /v1/allocation/dispersion, or last_7_days when you left it out.

last_24_hourslast_7_dayslast_30_days
floor_offers_per_window integer · minimum 0 required

The defined minimum offer exposure an eligible worker is entitled to.

bands array of DispersionBand required

One entry per band of the eligible population, from top_decile to long_tail: how many workers it holds, the offers they got, the mean per worker and how many fell below the floor.

5 fields of DispersionBand
band string required

Which slice of the eligible population this row is, from the most often offered to the least: top_decile, upper_quartile, median, lower_quartile or long_tail. The long tail is the band the floor protects.

top_decileupper_quartilemedianlower_quartilelong_tail
eligible_workers integer · minimum 0 required

How many eligible workers fall in this band in the window. Eligible means they passed every gate and could lawfully have been offered the work.

offers_made integer · minimum 0 required

How many offers went to workers in this band during the window, whatever each offer's outcome.

mean_offers_per_worker number optional

The average number of offers each eligible worker in this band received in the window. Read it against floor_offers_per_window: a band mean under the floor is a band to act on.

workers_below_floor integer · minimum 0 required

Eligible workers who did not reach the minimum exposure in this window.

workers_below_floor integer · minimum 0 required

How many eligible workers received fewer than floor_offers_per_window offers in this window, across every band. These are the people to act on today.

computed_at string · date-time optional

When these figures were taken, as an RFC 3339 timestamp in UTC. The floor is reported continuously, so ask again for a later reading.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# query parameters: window (optional), occupation_code (optional), zone (optional)
curl -X GET "https://sandbox.droomwork.io/v1/allocation/dispersion?window=last_24_hours&occupation_code=ng-7412&zone=zone_lagos_mainland_03" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

// query parameters: window (optional), occupation_code (optional), zone (optional)
const result = await api.allocationDispersionRetrieve({ window: 'last_24_hours', occupationCode: 'ng-7412', zone: 'zone_lagos_mainland_03' });
// query parameters: window (optional), occupation_code (optional), zone (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/dispersion?window=last_24_hours&occupation_code=ng-7412&zone=zone_lagos_mainland_03', {
  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.MATCHApi(client)

# query parameters: window (optional), occupation_code (optional), zone (optional)
result = api.allocation_dispersion_retrieve(window='last_24_hours', occupation_code='ng-7412', zone='zone_lagos_mainland_03')
import os

import requests

# query parameters: window (optional), occupation_code (optional), zone (optional)
response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/allocation/dispersion?window=last_24_hours&occupation_code=ng-7412&zone=zone_lagos_mainland_03',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

# query parameters: window (optional), occupation_code (optional), zone (optional)
$result = $api->allocationDispersionRetrieve(window: 'last_24_hours', occupation_code: 'ng-7412', zone: 'zone_lagos_mainland_03');
<?php
// query parameters: window (optional), occupation_code (optional), zone (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/dispersion?window=last_24_hours&occupation_code=ng-7412&zone=zone_lagos_mainland_03');
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.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

// query parameters: window (optional), occupation_code (optional), zone (optional)
var result = api.allocationDispersionRetrieve("last_24_hours", "ng-7412", "zone_lagos_mainland_03");
// query parameters: window (optional), occupation_code (optional), zone (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/dispersion?window=last_24_hours&occupation_code=ng-7412&zone=zone_lagos_mainland_03"))
    .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 MATCHApi(config);

// query parameters: window (optional), occupation_code (optional), zone (optional)
var result = api.AllocationDispersionRetrieve(window: "last_24_hours", occupationCode: "ng-7412", zone: "zone_lagos_mainland_03");
// query parameters: window (optional), occupation_code (optional), zone (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/allocation/dispersion?window=last_24_hours&occupation_code=ng-7412&zone=zone_lagos_mainland_03");
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: window (optional), occupation_code (optional), zone (optional)
result, _, err := client.MATCHAPI.AllocationDispersionRetrieve(ctx).Window("last_24_hours").OccupationCode("ng-7412").Zone("zone_lagos_mainland_03").Execute()
// query parameters: window (optional), occupation_code (optional), zone (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/allocation/dispersion?window=last_24_hours&occupation_code=ng-7412&zone=zone_lagos_mainland_03", 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)
Response
{
  "object": "dispersion_report",
  "window": "last_24_hours",
  "floor_offers_per_window": 0,
  "bands": [
    {
      "band": "top_decile",
      "eligible_workers": 0,
      "offers_made": 0,
      "workers_below_floor": 0,
      "mean_offers_per_worker": 1
    }
  ],
  "workers_below_floor": 0,
  "computed_at": "2026-09-01T09:00:00Z"
}
GET/v1/allocation/assignments#

See who is on which job

allocation.assignments.list

An assignment is one worker on one work order, and this is every one your organisation holds. From it a product can build the screen that answers who is out today, which order each of them is filling, and how far along each job has got.

Send nothing and the assignments come back, each carrying the worker_id of the person doing the work, the work_order_id it fills, and the status it has reached.

Read one in full at GET /v1/allocation/assignments/{assignment_id}, passing a row's id as assignment_id. A row's work_order_id is what you pass to GET /v1/allocation/work_orders/{work_order_id} to read the order behind it.

Query parameters

limit integer optional

How many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.

starting_after string optional

The id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.

status string optional

Return only assignments in one state: accepted at creation, en_route while the worker travels, arrived, in_progress, completed, exception where the signals disagreed, or cancelled if never started. Leave it out to get every state.

accepteden_routearrivedin_progresscompletedexceptioncancelled
work_order_id string optional

Return only the assignments filling one work order, by its id from POST /v1/allocation/work_orders or GET /v1/allocation/work_orders, starting with match_geo_aware_workforce_orchestrator_work_order_. Leave it out to get every work order.

worker_id string optional

Return only one worker's assignments, by their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens, the same worker_id as on their supply state. Leave it out to get every worker.

Returns

A page of assignments, each naming the worker, the work order and the state reached.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of Assignment required

The records on this page, in the order the list promises. Empty when nothing matched.

17 fields of Assignment
id string required

The assignment's identifier. It starts with match_geo_aware_workforce_orchestrator_assignment_ and never changes; pass it as {assignment_id} to GET /v1/allocation/assignments/{assignment_id} and its arrival, completion, cancel and proof calls.

object always "assignment" required

Always assignment. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

work_order_id string required

The id of the work order this assignment fills, as POST /v1/allocation/work_orders returned it, starting with match_geo_aware_workforce_orchestrator_work_order_. Pass it as {work_order_id} to read the order.

worker_id string required

The worker doing the work, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens. Every arrival and completion signal on this record is about this one person.

offer_id string optional

The id of the accepted offer this assignment came from, as listed at GET /v1/allocation/work_orders/{work_order_id}/offers, starting with match_geo_aware_workforce_orchestrator_offer_. Absent when you created it directly for work agreed elsewhere.

status string required

Work that has started resolves through completion or through exception. It is never cancelled, so the evidence of what happened survives.

accepteden_routearrivedin_progresscompletedexceptioncancelled
geofence_entry one of optional

The worker's entry into the geofence around the work location: when, where and the radius, one of the two signals arrival rests on. Null until arrival is confirmed.

GeofenceEntryor
start_passcode_verified_at string · date-time · nullable optional

When the start passcode the requester gave the worker on site was verified, as an RFC 3339 timestamp in UTC: the second of the two arrival signals. Null until arrival is confirmed.

completion_passcode_verified_at string · date-time · nullable optional

When the completion passcode was verified, as an RFC 3339 timestamp in UTC: one of the three signals completion rests on. Null until you confirm completion.

dwell one of optional

How long the worker was at the location, as you sent it on completion: when it started, when it ended and the whole seconds between. null until you confirm completion.

DwellRecordor
telemetry_continuity one of optional

Whether the heartbeat stream held across the working window, as you sent it on completion: continuous or not, how many gaps, and the longest in seconds. null until you confirm completion.

TelemetryContinuityor
proof_of_service_id string · nullable optional

The id of the sealed proof for this assignment, starting with match_geo_aware_workforce_orchestrator_proof_, as GET /v1/allocation/assignments/{assignment_id}/proof returns it. null until the bundle is sealed.

exception_id string · nullable optional

The id of the exception raised on this assignment, starting with match_geo_aware_workforce_orchestrator_exception_, as listed at GET /v1/allocation/exceptions. null unless the signals disagreed and a person was asked.

cancelled_reason string · nullable optional

Why the assignment was cancelled: the reason sent with the cancellation, or worker_removed_from_cascade when ANCHOR or RAIL removed the worker. null unless status is cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

has_more boolean required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

next_cursor string · nullable optional

Where the next page starts, or null when this is the last page. Pass it back unchanged to continue.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# query parameters: limit (optional), starting_after (optional), status (optional), work_order_id (optional), worker_id (optional)
curl -X GET "https://sandbox.droomwork.io/v1/allocation/assignments?limit=25&status=accepted&work_order_id=match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&worker_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

// query parameters: limit (optional), starting_after (optional), status (optional), work_order_id (optional), worker_id (optional)
const result = await api.allocationAssignmentsList({ limit: 25, status: 'accepted', workOrderId: 'match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', workerId: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
// query parameters: limit (optional), starting_after (optional), status (optional), work_order_id (optional), worker_id (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/assignments?limit=25&status=accepted&work_order_id=match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&worker_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
  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.MATCHApi(client)

# query parameters: limit (optional), starting_after (optional), status (optional), work_order_id (optional), worker_id (optional)
result = api.allocation_assignments_list(limit=25, status='accepted', work_order_id='match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', worker_id='sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os

import requests

# query parameters: limit (optional), starting_after (optional), status (optional), work_order_id (optional), worker_id (optional)
response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/allocation/assignments?limit=25&status=accepted&work_order_id=match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&worker_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

# query parameters: limit (optional), starting_after (optional), status (optional), work_order_id (optional), worker_id (optional)
$result = $api->allocationAssignmentsList(limit: 25, status: 'accepted', work_order_id: 'match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', worker_id: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
// query parameters: limit (optional), starting_after (optional), status (optional), work_order_id (optional), worker_id (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/assignments?limit=25&status=accepted&work_order_id=match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&worker_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
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.MatchApi;
import com.droomwork.sdk.model.*;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

// query parameters: limit (optional), starting_after (optional), status (optional), work_order_id (optional), worker_id (optional)
var result = api.allocationAssignmentsList(25, null, MatchAssignmentStatus.fromValue("accepted"), "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
// query parameters: limit (optional), starting_after (optional), status (optional), work_order_id (optional), worker_id (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/assignments?limit=25&status=accepted&work_order_id=match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&worker_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"))
    .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 MATCHApi(config);

// query parameters: limit (optional), starting_after (optional), status (optional), work_order_id (optional), worker_id (optional)
var result = api.AllocationAssignmentsList(limit: 25, status: MatchAssignmentStatus.Accepted, workOrderId: "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", workerId: "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
// query parameters: limit (optional), starting_after (optional), status (optional), work_order_id (optional), worker_id (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/allocation/assignments?limit=25&status=accepted&work_order_id=match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&worker_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
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), starting_after (optional), status (optional), work_order_id (optional), worker_id (optional)
result, _, err := client.MATCHAPI.AllocationAssignmentsList(ctx).Limit(25).Status(droomwork.MatchAssignmentStatus("accepted")).WorkOrderId("match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").WorkerId("sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
// query parameters: limit (optional), starting_after (optional), status (optional), work_order_id (optional), worker_id (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/allocation/assignments?limit=25&status=accepted&work_order_id=match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&worker_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", 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)
Response
{
  "object": "list",
  "data": [
    {
      "id": "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "assignment",
      "livemode": false,
      "mocked": true,
      "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "status": "accepted",
      "offer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "geofence_entry": {
        "entered_at": "2026-09-01T09:00:00Z",
        "position": {
          "latitude": -90,
          "longitude": -180,
          "accuracy_metres": 0
        },
        "radius_metres": 0
      },
      "start_passcode_verified_at": "2026-09-01T09:00:00Z",
      "completion_passcode_verified_at": "2026-09-01T09:00:00Z",
      "dwell": {
        "started_at": "2026-09-01T09:00:00Z",
        "ended_at": "2026-09-01T09:00:00Z",
        "seconds": 0
      },
      "telemetry_continuity": {
        "continuous": true,
        "gap_count": 0,
        "longest_gap_seconds": 0
      },
      "proof_of_service_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "exception_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "cancelled_reason": "example",
      "created_at": "2026-09-01T09:00:00Z"
    }
  ],
  "has_more": true,
  "next_cursor": "example"
}
POST/v1/allocation/assignments#

Put work agreed elsewhere on the record

allocation.assignments.create

An assignment normally arrives with a filled work order, out of the cascade. Creating one directly covers the job you arranged outside that, over the phone or on site, so your product holds one shape of record for every job and one place to collect the evidence for it.

Send the work_order_id the job fills and the worker_id doing it. You get the assignment back in accepted, with an identifier beginning match_geo_aware_workforce_orchestrator_assignment_ that every later call on it takes.

Open the work order first at POST /v1/allocation/work_orders; its id is what you send here. The worker_id is the sub_ subject reference you chose for that person, never an offer or assignment identifier.

From here the assignment moves by its own calls, beginning with POST /v1/allocation/assignments/{assignment_id}/depart when the worker sets off.

Headers

Idempotency-Key string required

A 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

work_order_id string optional

The work order this assignment fills, by its id from POST /v1/allocation/work_orders or GET /v1/allocation/work_orders, starting with match_geo_aware_workforce_orchestrator_work_order_.

worker_id string optional

The worker assigned, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens, the same worker_id as on their supply state at GET /v1/allocation/supply_states/{worker_id}.

Returns

The assignment you just created, in accepted, with the identifier every later call takes.

id string required

The assignment's identifier. It starts with match_geo_aware_workforce_orchestrator_assignment_ and never changes; pass it as {assignment_id} to GET /v1/allocation/assignments/{assignment_id} and its arrival, completion, cancel and proof calls.

object always "assignment" required

Always assignment. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

work_order_id string required

The id of the work order this assignment fills, as POST /v1/allocation/work_orders returned it, starting with match_geo_aware_workforce_orchestrator_work_order_. Pass it as {work_order_id} to read the order.

worker_id string required

The worker doing the work, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens. Every arrival and completion signal on this record is about this one person.

offer_id string optional

The id of the accepted offer this assignment came from, as listed at GET /v1/allocation/work_orders/{work_order_id}/offers, starting with match_geo_aware_workforce_orchestrator_offer_. Absent when you created it directly for work agreed elsewhere.

status string required

Work that has started resolves through completion or through exception. It is never cancelled, so the evidence of what happened survives.

accepteden_routearrivedin_progresscompletedexceptioncancelled
geofence_entry one of optional

The worker's entry into the geofence around the work location: when, where and the radius, one of the two signals arrival rests on. Null until arrival is confirmed.

GeofenceEntryor
start_passcode_verified_at string · date-time · nullable optional

When the start passcode the requester gave the worker on site was verified, as an RFC 3339 timestamp in UTC: the second of the two arrival signals. Null until arrival is confirmed.

completion_passcode_verified_at string · date-time · nullable optional

When the completion passcode was verified, as an RFC 3339 timestamp in UTC: one of the three signals completion rests on. Null until you confirm completion.

dwell one of optional

How long the worker was at the location, as you sent it on completion: when it started, when it ended and the whole seconds between. null until you confirm completion.

DwellRecordor
telemetry_continuity one of optional

Whether the heartbeat stream held across the working window, as you sent it on completion: continuous or not, how many gaps, and the longest in seconds. null until you confirm completion.

TelemetryContinuityor
proof_of_service_id string · nullable optional

The id of the sealed proof for this assignment, starting with match_geo_aware_workforce_orchestrator_proof_, as GET /v1/allocation/assignments/{assignment_id}/proof returns it. null until the bundle is sealed.

exception_id string · nullable optional

The id of the exception raised on this assignment, starting with match_geo_aware_workforce_orchestrator_exception_, as listed at GET /v1/allocation/exceptions. null unless the signals disagreed and a person was asked.

cancelled_reason string · nullable optional

Why the assignment was cancelled: the reason sent with the cancellation, or worker_removed_from_cascade when ANCHOR or RAIL removed the worker. null unless status is cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
409The record is not in a state that allows this.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X POST "https://sandbox.droomwork.io/v1/allocation/assignments" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"work_order_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","worker_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}'
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationAssignmentsCreate({});
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/assignments', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
    "worker_id": "sub_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.MATCHApi(client)

result = api.allocation_assignments_create()
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/allocation/assignments',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
        'Idempotency-Key': str(uuid.uuid4()),
    },
    json={"work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "worker_id": "sub_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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationAssignmentsCreate();
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/assignments');
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 => '{"work_order_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","worker_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}',
]);
$result = json_decode(curl_exec($ch), true);
import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationAssignmentsCreate();
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/assignments"))
    .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("""
        {
          "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
          "worker_id": "sub_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 MATCHApi(config);

var result = api.AllocationAssignmentsCreate();
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/allocation/assignments");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
    {
      "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "worker_id": "sub_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.MATCHAPI.AllocationAssignmentsCreate(ctx).Execute()
body := strings.NewReader(`{
  "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/allocation/assignments", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "assignment",
  "livemode": false,
  "mocked": true,
  "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "status": "accepted",
  "offer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "geofence_entry": {
    "entered_at": "2026-09-01T09:00:00Z",
    "position": {
      "latitude": -90,
      "longitude": -180,
      "accuracy_metres": 0
    },
    "radius_metres": 0
  },
  "start_passcode_verified_at": "2026-09-01T09:00:00Z",
  "completion_passcode_verified_at": "2026-09-01T09:00:00Z",
  "dwell": {
    "started_at": "2026-09-01T09:00:00Z",
    "ended_at": "2026-09-01T09:00:00Z",
    "seconds": 0
  },
  "telemetry_continuity": {
    "continuous": true,
    "gap_count": 0,
    "longest_gap_seconds": 0
  },
  "proof_of_service_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "exception_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "cancelled_reason": "example",
  "created_at": "2026-09-01T09:00:00Z"
}
GET/v1/allocation/assignments/{assignment_id}#

Check where one job has got to

allocation.assignments.retrieve

This is one assignment in full, which is the record a question about a single job gets answered from. Your product can show whoever asked what state the job is in and which pieces of evidence have been written on it so far.

Send the assignment's identifier in the path and you get it back whole: the worker, the work order it fills, the state it has reached, and the arrival and completion evidence recorded against it.

Anything not yet recorded comes back null. Read those fields before you treat a job as further along than it is.

The sealed proof is a separate read at GET /v1/allocation/assignments/{assignment_id}/proof, and it answers conflict until the assignment is completed.

Path parameters

assignment_id string required

The assignment's identifier: the id returned by POST /v1/allocation/assignments or POST /v1/allocation/offers/{offer_id}/accept, or listed at GET /v1/allocation/assignments. It starts with match_geo_aware_workforce_orchestrator_assignment_.

Returns

One assignment in full, with the evidence written on it so far.

id string required

The assignment's identifier. It starts with match_geo_aware_workforce_orchestrator_assignment_ and never changes; pass it as {assignment_id} to GET /v1/allocation/assignments/{assignment_id} and its arrival, completion, cancel and proof calls.

object always "assignment" required

Always assignment. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

work_order_id string required

The id of the work order this assignment fills, as POST /v1/allocation/work_orders returned it, starting with match_geo_aware_workforce_orchestrator_work_order_. Pass it as {work_order_id} to read the order.

worker_id string required

The worker doing the work, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens. Every arrival and completion signal on this record is about this one person.

offer_id string optional

The id of the accepted offer this assignment came from, as listed at GET /v1/allocation/work_orders/{work_order_id}/offers, starting with match_geo_aware_workforce_orchestrator_offer_. Absent when you created it directly for work agreed elsewhere.

status string required

Work that has started resolves through completion or through exception. It is never cancelled, so the evidence of what happened survives.

accepteden_routearrivedin_progresscompletedexceptioncancelled
geofence_entry one of optional

The worker's entry into the geofence around the work location: when, where and the radius, one of the two signals arrival rests on. Null until arrival is confirmed.

GeofenceEntryor
start_passcode_verified_at string · date-time · nullable optional

When the start passcode the requester gave the worker on site was verified, as an RFC 3339 timestamp in UTC: the second of the two arrival signals. Null until arrival is confirmed.

completion_passcode_verified_at string · date-time · nullable optional

When the completion passcode was verified, as an RFC 3339 timestamp in UTC: one of the three signals completion rests on. Null until you confirm completion.

dwell one of optional

How long the worker was at the location, as you sent it on completion: when it started, when it ended and the whole seconds between. null until you confirm completion.

DwellRecordor
telemetry_continuity one of optional

Whether the heartbeat stream held across the working window, as you sent it on completion: continuous or not, how many gaps, and the longest in seconds. null until you confirm completion.

TelemetryContinuityor
proof_of_service_id string · nullable optional

The id of the sealed proof for this assignment, starting with match_geo_aware_workforce_orchestrator_proof_, as GET /v1/allocation/assignments/{assignment_id}/proof returns it. null until the bundle is sealed.

exception_id string · nullable optional

The id of the exception raised on this assignment, starting with match_geo_aware_workforce_orchestrator_exception_, as listed at GET /v1/allocation/exceptions. null unless the signals disagreed and a person was asked.

cancelled_reason string · nullable optional

Why the assignment was cancelled: the reason sent with the cancellation, or worker_removed_from_cascade when ANCHOR or RAIL removed the worker. null unless status is cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X GET "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationAssignmentsRetrieve({ assignmentId: 'match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
  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.MATCHApi(client)

result = api.allocation_assignments_retrieve(assignment_id='match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationAssignmentsRetrieve(assignment_id: 'match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
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.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationAssignmentsRetrieve("match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"))
    .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 MATCHApi(config);

var result = api.AllocationAssignmentsRetrieve(assignmentId: "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
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.MATCHAPI.AllocationAssignmentsRetrieve(ctx, "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "assignment",
  "livemode": false,
  "mocked": true,
  "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "status": "accepted",
  "offer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "geofence_entry": {
    "entered_at": "2026-09-01T09:00:00Z",
    "position": {
      "latitude": -90,
      "longitude": -180,
      "accuracy_metres": 0
    },
    "radius_metres": 0
  },
  "start_passcode_verified_at": "2026-09-01T09:00:00Z",
  "completion_passcode_verified_at": "2026-09-01T09:00:00Z",
  "dwell": {
    "started_at": "2026-09-01T09:00:00Z",
    "ended_at": "2026-09-01T09:00:00Z",
    "seconds": 0
  },
  "telemetry_continuity": {
    "continuous": true,
    "gap_count": 0,
    "longest_gap_seconds": 0
  },
  "proof_of_service_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "exception_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "cancelled_reason": "example",
  "created_at": "2026-09-01T09:00:00Z"
}
POST/v1/allocation/assignments/{assignment_id}/arrival#

Confirm arrival at the work location

allocation.assignments.confirm_arrival

Arrival is the moment a job stops being travel and becomes presence at the place the work is. Recording it gives you and the worker one agreed time for that, which is a different time from when the work itself began.

Send the geofence entry the worker's device read and the start passcode they were given on site. Both are required. You get the assignment back with its status now arrived.

The assignment has to exist already: its id comes from POST /v1/allocation/assignments or GET /v1/allocation/assignments.

The start passcode is write-only and is returned on no response, so keep your own copy of whatever you issued.

POST /v1/allocation/assignments/{assignment_id}/arrive reaches the same arrived state from en_route, with no signals on it.

Path parameters

assignment_id string required

The assignment's identifier: the id returned by POST /v1/allocation/assignments or POST /v1/allocation/offers/{offer_id}/accept, or listed at GET /v1/allocation/assignments. It starts with match_geo_aware_workforce_orchestrator_assignment_.

Headers

Idempotency-Key string required

A 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

geofence_entry GeofenceEntry required

One of the two signals arrival rests on.

3 fields of GeofenceEntry
entered_at string · date-time required

When the worker's position first fell inside the geofence, as an RFC 3339 timestamp in UTC.

position GeoPoint required

A position reading. Accuracy is carried because a reading without it cannot be judged.

3 fields of GeoPoint
latitude number · minimum -90 · maximum 90 required

Latitude in decimal degrees, from -90 to 90. Negative values are south of the equator.

longitude number · minimum -180 · maximum 180 required

Longitude in decimal degrees, from -180 to 180. Negative values are west of the Greenwich meridian.

accuracy_metres integer · minimum 0 required

The radius the device reports as its confidence. A large value weakens every inference drawn from the point.

radius_metres integer · minimum 0 optional

The geofence's radius around the work location, in whole metres: how near a reading had to be to count as inside.

start_passcode string · write only required

Held by the requester and given to the worker on site. Never returned.

Returns

The assignment, now arrived, which is the state a start is reached from.

id string required

The assignment's identifier. It starts with match_geo_aware_workforce_orchestrator_assignment_ and never changes; pass it as {assignment_id} to GET /v1/allocation/assignments/{assignment_id} and its arrival, completion, cancel and proof calls.

object always "assignment" required

Always assignment. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

work_order_id string required

The id of the work order this assignment fills, as POST /v1/allocation/work_orders returned it, starting with match_geo_aware_workforce_orchestrator_work_order_. Pass it as {work_order_id} to read the order.

worker_id string required

The worker doing the work, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens. Every arrival and completion signal on this record is about this one person.

offer_id string optional

The id of the accepted offer this assignment came from, as listed at GET /v1/allocation/work_orders/{work_order_id}/offers, starting with match_geo_aware_workforce_orchestrator_offer_. Absent when you created it directly for work agreed elsewhere.

status string required

Work that has started resolves through completion or through exception. It is never cancelled, so the evidence of what happened survives.

accepteden_routearrivedin_progresscompletedexceptioncancelled
geofence_entry one of optional

The worker's entry into the geofence around the work location: when, where and the radius, one of the two signals arrival rests on. Null until arrival is confirmed.

GeofenceEntryor
start_passcode_verified_at string · date-time · nullable optional

When the start passcode the requester gave the worker on site was verified, as an RFC 3339 timestamp in UTC: the second of the two arrival signals. Null until arrival is confirmed.

completion_passcode_verified_at string · date-time · nullable optional

When the completion passcode was verified, as an RFC 3339 timestamp in UTC: one of the three signals completion rests on. Null until you confirm completion.

dwell one of optional

How long the worker was at the location, as you sent it on completion: when it started, when it ended and the whole seconds between. null until you confirm completion.

DwellRecordor
telemetry_continuity one of optional

Whether the heartbeat stream held across the working window, as you sent it on completion: continuous or not, how many gaps, and the longest in seconds. null until you confirm completion.

TelemetryContinuityor
proof_of_service_id string · nullable optional

The id of the sealed proof for this assignment, starting with match_geo_aware_workforce_orchestrator_proof_, as GET /v1/allocation/assignments/{assignment_id}/proof returns it. null until the bundle is sealed.

exception_id string · nullable optional

The id of the exception raised on this assignment, starting with match_geo_aware_workforce_orchestrator_exception_, as listed at GET /v1/allocation/exceptions. null unless the signals disagreed and a person was asked.

cancelled_reason string · nullable optional

Why the assignment was cancelled: the reason sent with the cancellation, or worker_removed_from_cascade when ANCHOR or RAIL removed the worker. null unless status is cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X POST "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/arrival" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"geofence_entry":{"entered_at":"2026-09-01T09:00:00Z","position":{"latitude":-90,"longitude":-180,"accuracy_metres":0},"radius_metres":0},"start_passcode":"example"}'
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationAssignmentsConfirmArrival({
  assignmentId: 'match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
  idempotencyKey: crypto.randomUUID(),
  matchArrivalRequest: {"geofenceEntry":{"enteredAt":"2026-09-01T09:00:00Z","position":{"latitude":-90,"longitude":-180,"accuracyMetres":0},"radiusMetres":0},"startPasscode":"example"},
});
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/arrival', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    "geofence_entry": {
      "entered_at": "2026-09-01T09:00:00Z",
      "position": {
        "latitude": -90,
        "longitude": -180,
        "accuracy_metres": 0
      },
      "radius_metres": 0
    },
    "start_passcode": "example"
  }),
});
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.MATCHApi(client)

result = api.allocation_assignments_confirm_arrival(assignment_id='match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', body={"geofence_entry": {"entered_at": "2026-09-01T09:00:00Z", "position": {"latitude": -90, "longitude": -180, "accuracy_metres": 0}, "radius_metres": 0}, "start_passcode": "example"})
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/arrival',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
        'Idempotency-Key': str(uuid.uuid4()),
    },
    json={"geofence_entry": {"entered_at": "2026-09-01T09:00:00Z", "position": {"latitude": -90, "longitude": -180, "accuracy_metres": 0}, "radius_metres": 0}, "start_passcode": "example"},
)
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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationAssignmentsConfirmArrival($idempotencyKey, json_decode('{"geofence_entry":{"entered_at":"2026-09-01T09:00:00Z","position":{"latitude":-90,"longitude":-180,"accuracy_metres":0},"radius_metres":0},"start_passcode":"example"}', true));
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/arrival');
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 => '{"geofence_entry":{"entered_at":"2026-09-01T09:00:00Z","position":{"latitude":-90,"longitude":-180,"accuracy_metres":0},"radius_metres":0},"start_passcode":"example"}',
]);
$result = json_decode(curl_exec($ch), true);
import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationAssignmentsConfirmArrival("match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/arrival"))
    .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("""
        {
          "geofence_entry": {
            "entered_at": "2026-09-01T09:00:00Z",
            "position": {
              "latitude": -90,
              "longitude": -180,
              "accuracy_metres": 0
            },
            "radius_metres": 0
          },
          "start_passcode": "example"
        }
        """))
    .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 MATCHApi(config);

var result = api.AllocationAssignmentsConfirmArrival(assignmentId: "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/arrival");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
    {
      "geofence_entry": {
        "entered_at": "2026-09-01T09:00:00Z",
        "position": {
          "latitude": -90,
          "longitude": -180,
          "accuracy_metres": 0
        },
        "radius_metres": 0
      },
      "start_passcode": "example"
    }
    """, 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.MATCHAPI.AllocationAssignmentsConfirmArrival(ctx, "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").IdempotencyKey(key).MatchArrivalRequest(body).Execute()
body := strings.NewReader(`{
  "geofence_entry": {
    "entered_at": "2026-09-01T09:00:00Z",
    "position": {
      "latitude": -90,
      "longitude": -180,
      "accuracy_metres": 0
    },
    "radius_metres": 0
  },
  "start_passcode": "example"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/arrival", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "assignment",
  "livemode": false,
  "mocked": true,
  "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "status": "accepted",
  "offer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "geofence_entry": {
    "entered_at": "2026-09-01T09:00:00Z",
    "position": {
      "latitude": -90,
      "longitude": -180,
      "accuracy_metres": 0
    },
    "radius_metres": 0
  },
  "start_passcode_verified_at": "2026-09-01T09:00:00Z",
  "completion_passcode_verified_at": "2026-09-01T09:00:00Z",
  "dwell": {
    "started_at": "2026-09-01T09:00:00Z",
    "ended_at": "2026-09-01T09:00:00Z",
    "seconds": 0
  },
  "telemetry_continuity": {
    "continuous": true,
    "gap_count": 0,
    "longest_gap_seconds": 0
  },
  "proof_of_service_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "exception_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "cancelled_reason": "example",
  "created_at": "2026-09-01T09:00:00Z"
}
POST/v1/allocation/assignments/{assignment_id}/completion#

Confirm completion of the work

allocation.assignments.confirm_completion

Completion is where a job becomes something you can account for. It is the state the sealed proof of service is read from, so your product has one point at which work in progress becomes evidence you can hold.

Send the completion passcode, the dwell at the location and the telemetry continuity across the working window. All three are required. You get the assignment back with its status now completed. Nothing else has to exist first.

Compute dwell.seconds from the started_at and ended_at you send beside it; nothing recomputes it for you.

POST /v1/allocation/assignments/{assignment_id}/complete reaches the same completed state as a lifecycle step. Once the assignment is completed, read its proof at GET /v1/allocation/assignments/{assignment_id}/proof.

Path parameters

assignment_id string required

The assignment's identifier: the id returned by POST /v1/allocation/assignments or POST /v1/allocation/offers/{offer_id}/accept, or listed at GET /v1/allocation/assignments. It starts with match_geo_aware_workforce_orchestrator_assignment_.

Headers

Idempotency-Key string required

A 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

completion_passcode string · write only required

Never returned on any response.

dwell DwellRecord required

How long the worker was at the location. One of the three signals completion rests on.

3 fields of DwellRecord
started_at string · date-time required

When the worker's time at the location began, as an RFC 3339 timestamp in UTC.

ended_at string · date-time required

When the worker's time at the location ended, as an RFC 3339 timestamp in UTC.

seconds integer · minimum 0 required

The whole seconds between started_at and ended_at: the dwell duration completion is judged on. Never negative.

telemetry_continuity TelemetryContinuity required

Whether the heartbeat stream held across the working window. A long gap does not disprove the work, and it does mean the completion needs a person rather than a rule.

3 fields of TelemetryContinuity
continuous boolean required

true when the heartbeat stream held across the whole working window, false when it broke. false doesn't disprove the work; gap_count and longest_gap_seconds say how badly it broke.

gap_count integer · minimum 0 required

How many times the heartbeat stream broke during the working window. 0 when it was continuous.

longest_gap_seconds integer · minimum 0 required

The longest single break in the heartbeat stream during the working window, in whole seconds, or 0 when there was none. A long gap doesn't disprove the work; it sends the completion to a person.

notes string optional

Free text to send with the completion, such as what the worker reported on site. It is not a signal: the three signals decide, and a note never stands in for one.

Returns

The assignment, now completed, which is the point its proof becomes readable.

id string required

The assignment's identifier. It starts with match_geo_aware_workforce_orchestrator_assignment_ and never changes; pass it as {assignment_id} to GET /v1/allocation/assignments/{assignment_id} and its arrival, completion, cancel and proof calls.

object always "assignment" required

Always assignment. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

work_order_id string required

The id of the work order this assignment fills, as POST /v1/allocation/work_orders returned it, starting with match_geo_aware_workforce_orchestrator_work_order_. Pass it as {work_order_id} to read the order.

worker_id string required

The worker doing the work, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens. Every arrival and completion signal on this record is about this one person.

offer_id string optional

The id of the accepted offer this assignment came from, as listed at GET /v1/allocation/work_orders/{work_order_id}/offers, starting with match_geo_aware_workforce_orchestrator_offer_. Absent when you created it directly for work agreed elsewhere.

status string required

Work that has started resolves through completion or through exception. It is never cancelled, so the evidence of what happened survives.

accepteden_routearrivedin_progresscompletedexceptioncancelled
geofence_entry one of optional

The worker's entry into the geofence around the work location: when, where and the radius, one of the two signals arrival rests on. Null until arrival is confirmed.

GeofenceEntryor
start_passcode_verified_at string · date-time · nullable optional

When the start passcode the requester gave the worker on site was verified, as an RFC 3339 timestamp in UTC: the second of the two arrival signals. Null until arrival is confirmed.

completion_passcode_verified_at string · date-time · nullable optional

When the completion passcode was verified, as an RFC 3339 timestamp in UTC: one of the three signals completion rests on. Null until you confirm completion.

dwell one of optional

How long the worker was at the location, as you sent it on completion: when it started, when it ended and the whole seconds between. null until you confirm completion.

DwellRecordor
telemetry_continuity one of optional

Whether the heartbeat stream held across the working window, as you sent it on completion: continuous or not, how many gaps, and the longest in seconds. null until you confirm completion.

TelemetryContinuityor
proof_of_service_id string · nullable optional

The id of the sealed proof for this assignment, starting with match_geo_aware_workforce_orchestrator_proof_, as GET /v1/allocation/assignments/{assignment_id}/proof returns it. null until the bundle is sealed.

exception_id string · nullable optional

The id of the exception raised on this assignment, starting with match_geo_aware_workforce_orchestrator_exception_, as listed at GET /v1/allocation/exceptions. null unless the signals disagreed and a person was asked.

cancelled_reason string · nullable optional

Why the assignment was cancelled: the reason sent with the cancellation, or worker_removed_from_cascade when ANCHOR or RAIL removed the worker. null unless status is cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X POST "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/completion" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"completion_passcode":"example","dwell":{"started_at":"2026-09-01T09:00:00Z","ended_at":"2026-09-01T09:00:00Z","seconds":0},"telemetry_continuity":{"continuous":true,"gap_count":0,"longest_gap_seconds":0},"notes":"example"}'
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationAssignmentsConfirmCompletion({
  assignmentId: 'match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
  idempotencyKey: crypto.randomUUID(),
  matchCompletionRequest: {"completionPasscode":"example","dwell":{"startedAt":"2026-09-01T09:00:00Z","endedAt":"2026-09-01T09:00:00Z","seconds":0},"telemetryContinuity":{"continuous":true,"gapCount":0,"longestGapSeconds":0},"notes":"example"},
});
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/completion', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    "completion_passcode": "example",
    "dwell": {
      "started_at": "2026-09-01T09:00:00Z",
      "ended_at": "2026-09-01T09:00:00Z",
      "seconds": 0
    },
    "telemetry_continuity": {
      "continuous": true,
      "gap_count": 0,
      "longest_gap_seconds": 0
    },
    "notes": "example"
  }),
});
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.MATCHApi(client)

result = api.allocation_assignments_confirm_completion(assignment_id='match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', body={"completion_passcode": "example", "dwell": {"started_at": "2026-09-01T09:00:00Z", "ended_at": "2026-09-01T09:00:00Z", "seconds": 0}, "telemetry_continuity": {"continuous": True, "gap_count": 0, "longest_gap_seconds": 0}, "notes": "example"})
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/completion',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
        'Idempotency-Key': str(uuid.uuid4()),
    },
    json={"completion_passcode": "example", "dwell": {"started_at": "2026-09-01T09:00:00Z", "ended_at": "2026-09-01T09:00:00Z", "seconds": 0}, "telemetry_continuity": {"continuous": True, "gap_count": 0, "longest_gap_seconds": 0}, "notes": "example"},
)
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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationAssignmentsConfirmCompletion($idempotencyKey, json_decode('{"completion_passcode":"example","dwell":{"started_at":"2026-09-01T09:00:00Z","ended_at":"2026-09-01T09:00:00Z","seconds":0},"telemetry_continuity":{"continuous":true,"gap_count":0,"longest_gap_seconds":0},"notes":"example"}', true));
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/completion');
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 => '{"completion_passcode":"example","dwell":{"started_at":"2026-09-01T09:00:00Z","ended_at":"2026-09-01T09:00:00Z","seconds":0},"telemetry_continuity":{"continuous":true,"gap_count":0,"longest_gap_seconds":0},"notes":"example"}',
]);
$result = json_decode(curl_exec($ch), true);
import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationAssignmentsConfirmCompletion("match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/completion"))
    .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("""
        {
          "completion_passcode": "example",
          "dwell": {
            "started_at": "2026-09-01T09:00:00Z",
            "ended_at": "2026-09-01T09:00:00Z",
            "seconds": 0
          },
          "telemetry_continuity": {
            "continuous": true,
            "gap_count": 0,
            "longest_gap_seconds": 0
          },
          "notes": "example"
        }
        """))
    .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 MATCHApi(config);

var result = api.AllocationAssignmentsConfirmCompletion(assignmentId: "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/completion");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
    {
      "completion_passcode": "example",
      "dwell": {
        "started_at": "2026-09-01T09:00:00Z",
        "ended_at": "2026-09-01T09:00:00Z",
        "seconds": 0
      },
      "telemetry_continuity": {
        "continuous": true,
        "gap_count": 0,
        "longest_gap_seconds": 0
      },
      "notes": "example"
    }
    """, 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.MATCHAPI.AllocationAssignmentsConfirmCompletion(ctx, "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").IdempotencyKey(key).MatchCompletionRequest(body).Execute()
body := strings.NewReader(`{
  "completion_passcode": "example",
  "dwell": {
    "started_at": "2026-09-01T09:00:00Z",
    "ended_at": "2026-09-01T09:00:00Z",
    "seconds": 0
  },
  "telemetry_continuity": {
    "continuous": true,
    "gap_count": 0,
    "longest_gap_seconds": 0
  },
  "notes": "example"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/completion", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "assignment",
  "livemode": false,
  "mocked": true,
  "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "status": "accepted",
  "offer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "geofence_entry": {
    "entered_at": "2026-09-01T09:00:00Z",
    "position": {
      "latitude": -90,
      "longitude": -180,
      "accuracy_metres": 0
    },
    "radius_metres": 0
  },
  "start_passcode_verified_at": "2026-09-01T09:00:00Z",
  "completion_passcode_verified_at": "2026-09-01T09:00:00Z",
  "dwell": {
    "started_at": "2026-09-01T09:00:00Z",
    "ended_at": "2026-09-01T09:00:00Z",
    "seconds": 0
  },
  "telemetry_continuity": {
    "continuous": true,
    "gap_count": 0,
    "longest_gap_seconds": 0
  },
  "proof_of_service_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "exception_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "cancelled_reason": "example",
  "created_at": "2026-09-01T09:00:00Z"
}
POST/v1/allocation/assignments/{assignment_id}/cancel#

Call off a job you no longer need

allocation.assignments.cancel

Cancelling is how a job that is not going to happen leaves your open work. A screen built on status stops carrying it, so what is left on the list is work somebody still expects to be done.

Send the reason you are calling it off, and detail in your own words where the code alone does not say enough. You get the assignment back with its status now cancelled. Nothing else has to exist first.

reason is a fixed code and detail is yours to write. Keep worker_removed_from_cascade for where the worker lost standing in ANCHOR or RAIL; your own change of plan takes one of the other codes.

A cancelled assignment seals no proof, so GET /v1/allocation/assignments/{assignment_id}/proof answers conflict on it.

Path parameters

assignment_id string required

The assignment's identifier: the id returned by POST /v1/allocation/assignments or POST /v1/allocation/offers/{offer_id}/accept, or listed at GET /v1/allocation/assignments. It starts with match_geo_aware_workforce_orchestrator_assignment_.

Headers

Idempotency-Key string required

A 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

reason string required

Why you are cancelling: requester_withdrew, no_longer_required, raised_in_error or worker_removed_from_cascade (the worker lost standing in ANCHOR or RAIL). It reaches the worker where one was offered or assigned.

requester_withdrewno_longer_requiredraised_in_errorworker_removed_from_cascade
detail string optional

More about why, in your own words, for whoever reads the record later. Optional; reason is the fixed code.

Returns

The assignment, now cancelled, which is where a job that never ran ends.

id string required

The assignment's identifier. It starts with match_geo_aware_workforce_orchestrator_assignment_ and never changes; pass it as {assignment_id} to GET /v1/allocation/assignments/{assignment_id} and its arrival, completion, cancel and proof calls.

object always "assignment" required

Always assignment. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

work_order_id string required

The id of the work order this assignment fills, as POST /v1/allocation/work_orders returned it, starting with match_geo_aware_workforce_orchestrator_work_order_. Pass it as {work_order_id} to read the order.

worker_id string required

The worker doing the work, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens. Every arrival and completion signal on this record is about this one person.

offer_id string optional

The id of the accepted offer this assignment came from, as listed at GET /v1/allocation/work_orders/{work_order_id}/offers, starting with match_geo_aware_workforce_orchestrator_offer_. Absent when you created it directly for work agreed elsewhere.

status string required

Work that has started resolves through completion or through exception. It is never cancelled, so the evidence of what happened survives.

accepteden_routearrivedin_progresscompletedexceptioncancelled
geofence_entry one of optional

The worker's entry into the geofence around the work location: when, where and the radius, one of the two signals arrival rests on. Null until arrival is confirmed.

GeofenceEntryor
start_passcode_verified_at string · date-time · nullable optional

When the start passcode the requester gave the worker on site was verified, as an RFC 3339 timestamp in UTC: the second of the two arrival signals. Null until arrival is confirmed.

completion_passcode_verified_at string · date-time · nullable optional

When the completion passcode was verified, as an RFC 3339 timestamp in UTC: one of the three signals completion rests on. Null until you confirm completion.

dwell one of optional

How long the worker was at the location, as you sent it on completion: when it started, when it ended and the whole seconds between. null until you confirm completion.

DwellRecordor
telemetry_continuity one of optional

Whether the heartbeat stream held across the working window, as you sent it on completion: continuous or not, how many gaps, and the longest in seconds. null until you confirm completion.

TelemetryContinuityor
proof_of_service_id string · nullable optional

The id of the sealed proof for this assignment, starting with match_geo_aware_workforce_orchestrator_proof_, as GET /v1/allocation/assignments/{assignment_id}/proof returns it. null until the bundle is sealed.

exception_id string · nullable optional

The id of the exception raised on this assignment, starting with match_geo_aware_workforce_orchestrator_exception_, as listed at GET /v1/allocation/exceptions. null unless the signals disagreed and a person was asked.

cancelled_reason string · nullable optional

Why the assignment was cancelled: the reason sent with the cancellation, or worker_removed_from_cascade when ANCHOR or RAIL removed the worker. null unless status is cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X POST "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/cancel" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"reason":"requester_withdrew","detail":"The payee has no verified destination, so this line cannot be paid."}'
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationAssignmentsCancel({
  assignmentId: 'match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
  idempotencyKey: crypto.randomUUID(),
  matchCancellationRequest: {"reason":"requester_withdrew","detail":"The payee has no verified destination, so this line cannot be paid."},
});
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/cancel', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    "reason": "requester_withdrew",
    "detail": "The payee has no verified destination, so this line cannot be paid."
  }),
});
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.MATCHApi(client)

result = api.allocation_assignments_cancel(assignment_id='match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', body={"reason": "requester_withdrew", "detail": "The payee has no verified destination, so this line cannot be paid."})
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/cancel',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
        'Idempotency-Key': str(uuid.uuid4()),
    },
    json={"reason": "requester_withdrew", "detail": "The payee has no verified destination, so this line cannot be paid."},
)
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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationAssignmentsCancel($idempotencyKey, json_decode('{"reason":"requester_withdrew","detail":"The payee has no verified destination, so this line cannot be paid."}', true));
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/cancel');
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 => '{"reason":"requester_withdrew","detail":"The payee has no verified destination, so this line cannot be paid."}',
]);
$result = json_decode(curl_exec($ch), true);
import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationAssignmentsCancel("match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/cancel"))
    .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("""
        {
          "reason": "requester_withdrew",
          "detail": "The payee has no verified destination, so this line cannot be paid."
        }
        """))
    .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 MATCHApi(config);

var result = api.AllocationAssignmentsCancel(assignmentId: "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/cancel");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
    {
      "reason": "requester_withdrew",
      "detail": "The payee has no verified destination, so this line cannot be paid."
    }
    """, 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.MATCHAPI.AllocationAssignmentsCancel(ctx, "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").IdempotencyKey(key).MatchCancellationRequest(body).Execute()
body := strings.NewReader(`{
  "reason": "requester_withdrew",
  "detail": "The payee has no verified destination, so this line cannot be paid."
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/cancel", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "assignment",
  "livemode": false,
  "mocked": true,
  "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "status": "accepted",
  "offer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "geofence_entry": {
    "entered_at": "2026-09-01T09:00:00Z",
    "position": {
      "latitude": -90,
      "longitude": -180,
      "accuracy_metres": 0
    },
    "radius_metres": 0
  },
  "start_passcode_verified_at": "2026-09-01T09:00:00Z",
  "completion_passcode_verified_at": "2026-09-01T09:00:00Z",
  "dwell": {
    "started_at": "2026-09-01T09:00:00Z",
    "ended_at": "2026-09-01T09:00:00Z",
    "seconds": 0
  },
  "telemetry_continuity": {
    "continuous": true,
    "gap_count": 0,
    "longest_gap_seconds": 0
  },
  "proof_of_service_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "exception_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "cancelled_reason": "example",
  "created_at": "2026-09-01T09:00:00Z"
}
GET/v1/allocation/assignments/{assignment_id}/proof#

Read the sealed record of finished work

allocation.proofs.retrieve

A finished assignment leaves one record behind, and this is how you read it. It gives each completed job a single object your own system can store, so the work and the order that asked for it stay tied together in your records.

You pass the assignment's id in the path and get back its proof of service, naming the assignment and the work_order_id of the order it filled.

The bundle is there once the assignment reaches completed. Ask for it while the assignment is still running and the call is refused with conflict.

No call in this API edits a proof or removes one. These two reads are the whole of what you can do with a sealed bundle, and completing the assignment is what brings one into being.

Path parameters

assignment_id string required

The assignment's identifier: the id returned by POST /v1/allocation/assignments or POST /v1/allocation/offers/{offer_id}/accept, or listed at GET /v1/allocation/assignments. It starts with match_geo_aware_workforce_orchestrator_assignment_.

Returns

The proof of service for one finished assignment, ready to store against your own record of the job.

id string required

The proof's identifier. It starts with match_geo_aware_workforce_orchestrator_proof_ and never changes; it matches proof_of_service_id on the assignment, so you can tie a sealed bundle back to the work it proves.

object always "proof_of_service" required

Always proof_of_service. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

assignment_id string required

The id of the assignment this proof was sealed for, starting with match_geo_aware_workforce_orchestrator_assignment_. Pass it to GET /v1/allocation/assignments/{assignment_id} to read the signals as they were collected.

work_order_id string optional

The work order the assignment filled, by its id from POST /v1/allocation/work_orders, starting match_geo_aware_workforce_orchestrator_work_order_. Use it to match the proof to the order when you reconcile what settled, without reading the assignment.

worker_id string optional

The worker the proof is for, as the sub_ subject reference you chose for them and first sent as subject_ref at POST /v1/identity/consent_tokens. It is the worker_id on the assignment this proof was sealed for.

signals array of string required

Never fewer than two. One signal cannot establish that work occurred, so a bundle resting on one is not a bundle.

established_by string optional

How the proof was established: signals_agreed when the collected signals agreed on their own, adjudication when a person upheld the completion at POST /v1/allocation/exceptions/{exception_id}/adjudicate.

signals_agreedadjudication
seal Seal required

The cryptographic seal over the bundle as it stood. Anything added afterwards is a new bundle, so a sealed proof cannot be edited into agreement with a later claim.

4 fields of Seal
algorithm string required

The algorithm behind digest, such as sha256. Keep it with digest and key_reference: together they say exactly how this bundle was sealed.

digest string required

The seal itself: the digest over the bundle as sealed, prefixed with its algorithm, such as sha256:9f2c1e0043a1b8. Any change to the bundle gives a different digest.

sealed_at string · date-time required

When the bundle was sealed, as an RFC 3339 timestamp in UTC. Nothing added after this moment is inside the seal.

key_reference string optional

Which signing key sealed it, so a seal stays verifiable across a key rotation.

settlement_event_id string optional

The id of the proof_of_service.sealed event that carried this proof to Droomwork Pay, the event settlement runs from. It starts with evt_; read it at GET /v1/allocation/events/{event_id}.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X GET "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/proof" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationProofsRetrieve({ assignmentId: 'match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/proof', {
  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.MATCHApi(client)

result = api.allocation_proofs_retrieve(assignment_id='match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/proof',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationProofsRetrieve(assignment_id: 'match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/proof');
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.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationProofsRetrieve("match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/proof"))
    .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 MATCHApi(config);

var result = api.AllocationProofsRetrieve(assignmentId: "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/proof");
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.MATCHAPI.AllocationProofsRetrieve(ctx, "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/proof", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_proof_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "proof_of_service",
  "livemode": false,
  "mocked": true,
  "assignment_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "signals": [
    "geofence_entry",
    "geofence_entry"
  ],
  "seal": {
    "algorithm": "sha256",
    "digest": "sha256:9f2c1e0043a1b8",
    "sealed_at": "2026-09-01T09:00:00Z",
    "key_reference": "paye-2026-09-rivers"
  },
  "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "established_by": "signals_agreed",
  "settlement_event_id": "evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
GET/v1/allocation/proofs#

Reconcile the work that was sealed

allocation.proofs.list

This is where your organisation's proofs of service are read together. One call covers many assignments, so a reconciliation does not have to walk your assignments one at a time asking each whether a proof exists.

You get back the sealed proofs your organisation holds, each one carrying the assignment_id of the job it belongs to, which is what ties a proof to the work your own system dispatched.

Do not narrow this list with the query. assignment_id and sealed_after are published on it and neither changes what comes back, so match on the proofs you read.

Read a proof against the assignment it belongs to at GET /v1/allocation/assignments/{assignment_id}/proof, passing the assignment_id you read off the row.

Query parameters

limit integer optional

How many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.

starting_after string optional

The id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.

assignment_id string optional

Return only the proof sealed for one assignment, by its id from POST /v1/allocation/assignments or POST /v1/allocation/offers/{offer_id}/accept, starting match_geo_aware_workforce_orchestrator_assignment_. Leave it out to get every proof.

sealed_after string optional

Return only proofs sealed after this moment, as an RFC 3339 timestamp in UTC, matched against seal.sealed_at. Use it to pick up what was sealed since your last reconciliation; leave it out to get every proof.

Returns

The sealed proofs your organisation holds, each one naming the assignment it belongs to.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of ProofOfService required

The records on this page, in the order the list promises. Empty when nothing matched.

11 fields of ProofOfService
id string required

The proof's identifier. It starts with match_geo_aware_workforce_orchestrator_proof_ and never changes; it matches proof_of_service_id on the assignment, so you can tie a sealed bundle back to the work it proves.

object always "proof_of_service" required

Always proof_of_service. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

assignment_id string required

The id of the assignment this proof was sealed for, starting with match_geo_aware_workforce_orchestrator_assignment_. Pass it to GET /v1/allocation/assignments/{assignment_id} to read the signals as they were collected.

work_order_id string optional

The work order the assignment filled, by its id from POST /v1/allocation/work_orders, starting match_geo_aware_workforce_orchestrator_work_order_. Use it to match the proof to the order when you reconcile what settled, without reading the assignment.

worker_id string optional

The worker the proof is for, as the sub_ subject reference you chose for them and first sent as subject_ref at POST /v1/identity/consent_tokens. It is the worker_id on the assignment this proof was sealed for.

signals array of string required

Never fewer than two. One signal cannot establish that work occurred, so a bundle resting on one is not a bundle.

established_by string optional

How the proof was established: signals_agreed when the collected signals agreed on their own, adjudication when a person upheld the completion at POST /v1/allocation/exceptions/{exception_id}/adjudicate.

signals_agreedadjudication
seal Seal required

The cryptographic seal over the bundle as it stood. Anything added afterwards is a new bundle, so a sealed proof cannot be edited into agreement with a later claim.

4 fields of Seal
algorithm string required

The algorithm behind digest, such as sha256. Keep it with digest and key_reference: together they say exactly how this bundle was sealed.

digest string required

The seal itself: the digest over the bundle as sealed, prefixed with its algorithm, such as sha256:9f2c1e0043a1b8. Any change to the bundle gives a different digest.

sealed_at string · date-time required

When the bundle was sealed, as an RFC 3339 timestamp in UTC. Nothing added after this moment is inside the seal.

key_reference string optional

Which signing key sealed it, so a seal stays verifiable across a key rotation.

settlement_event_id string optional

The id of the proof_of_service.sealed event that carried this proof to Droomwork Pay, the event settlement runs from. It starts with evt_; read it at GET /v1/allocation/events/{event_id}.

has_more boolean required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

next_cursor string · nullable optional

Where the next page starts, or null when this is the last page. Pass it back unchanged to continue.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# query parameters: limit (optional), starting_after (optional), assignment_id (optional), sealed_after (optional)
curl -X GET "https://sandbox.droomwork.io/v1/allocation/proofs?limit=25&assignment_id=match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&sealed_after=2026-01-01T00%3A00%3A00Z" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

// query parameters: limit (optional), starting_after (optional), assignment_id (optional), sealed_after (optional)
const result = await api.allocationProofsList({ limit: 25, assignmentId: 'match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', sealedAfter: '2026-01-01T00:00:00Z' });
// query parameters: limit (optional), starting_after (optional), assignment_id (optional), sealed_after (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/proofs?limit=25&assignment_id=match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&sealed_after=2026-01-01T00%3A00%3A00Z', {
  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.MATCHApi(client)

# query parameters: limit (optional), starting_after (optional), assignment_id (optional), sealed_after (optional)
result = api.allocation_proofs_list(limit=25, assignment_id='match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', sealed_after='2026-01-01T00:00:00Z')
import os

import requests

# query parameters: limit (optional), starting_after (optional), assignment_id (optional), sealed_after (optional)
response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/allocation/proofs?limit=25&assignment_id=match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&sealed_after=2026-01-01T00%3A00%3A00Z',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

# query parameters: limit (optional), starting_after (optional), assignment_id (optional), sealed_after (optional)
$result = $api->allocationProofsList(limit: 25, assignment_id: 'match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', sealed_after: '2026-01-01T00:00:00Z');
<?php
// query parameters: limit (optional), starting_after (optional), assignment_id (optional), sealed_after (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/proofs?limit=25&assignment_id=match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&sealed_after=2026-01-01T00%3A00%3A00Z');
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.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

// query parameters: limit (optional), starting_after (optional), assignment_id (optional), sealed_after (optional)
var result = api.allocationProofsList(25, null, "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "2026-01-01T00:00:00Z");
// query parameters: limit (optional), starting_after (optional), assignment_id (optional), sealed_after (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/proofs?limit=25&assignment_id=match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&sealed_after=2026-01-01T00%3A00%3A00Z"))
    .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 MATCHApi(config);

// query parameters: limit (optional), starting_after (optional), assignment_id (optional), sealed_after (optional)
var result = api.AllocationProofsList(limit: 25, assignmentId: "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", sealedAfter: "2026-01-01T00:00:00Z");
// query parameters: limit (optional), starting_after (optional), assignment_id (optional), sealed_after (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/allocation/proofs?limit=25&assignment_id=match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&sealed_after=2026-01-01T00%3A00%3A00Z");
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), starting_after (optional), assignment_id (optional), sealed_after (optional)
result, _, err := client.MATCHAPI.AllocationProofsList(ctx).Limit(25).AssignmentId("match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").SealedAfter("2026-01-01T00:00:00Z").Execute()
// query parameters: limit (optional), starting_after (optional), assignment_id (optional), sealed_after (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/allocation/proofs?limit=25&assignment_id=match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&sealed_after=2026-01-01T00%3A00%3A00Z", 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)
Response
{
  "object": "list",
  "data": [
    {
      "id": "match_geo_aware_workforce_orchestrator_proof_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "proof_of_service",
      "livemode": false,
      "mocked": true,
      "assignment_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "signals": [
        "geofence_entry",
        "geofence_entry"
      ],
      "seal": {
        "algorithm": "sha256",
        "digest": "sha256:9f2c1e0043a1b8",
        "sealed_at": "2026-09-01T09:00:00Z",
        "key_reference": "paye-2026-09-rivers"
      },
      "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "established_by": "signals_agreed",
      "settlement_event_id": "evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
    }
  ],
  "has_more": true,
  "next_cursor": "example"
}
GET/v1/allocation/exceptions#

Completions put to a person

allocation.exceptions.list

An exception is one assignment's completion put to a person to decide. Reading them gives your product an adjudication queue of its own, where somebody can see every completion that is outstanding and the argument on each one before they rule.

Send nothing and every exception your organisation holds comes back, each with the assignment_id it was raised against, its status, what each conflicting signal said, and the ruling where one has been made.

There is no single-exception read, so this list is where you open one. A row's id is what you send as exception_id to POST /v1/allocation/exceptions/{exception_id}/adjudicate.

Query parameters

limit integer optional

How many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.

starting_after string optional

The id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.

status string optional

Return only exceptions in one state: open while a person has yet to decide, upheld once the completion was accepted and the proof sealed, rejected once it was not and the assignment closed without a proof. Leave it out to get every state.

openupheldrejected
assignment_id string optional

Return only the exceptions raised on one assignment, by its id from POST /v1/allocation/assignments or POST /v1/allocation/offers/{offer_id}/accept, starting match_geo_aware_workforce_orchestrator_assignment_. Leave it out to get every assignment.

Returns

A page of exceptions, each naming its assignment, the signals in conflict and any ruling made.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of AssignmentException required

The records on this page, in the order the list promises. Empty when nothing matched.

9 fields of AssignmentException
id string required

The exception's identifier. It starts with match_geo_aware_workforce_orchestrator_exception_ and never changes; pass it as exception_id at POST /v1/allocation/exceptions/{exception_id}/adjudicate, or as starting_after to page past it.

object always "assignment_exception" required

Always assignment_exception. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

assignment_id string required

The assignment whose completion is in question, by its id from POST /v1/allocation/assignments or POST /v1/allocation/offers/{offer_id}/accept, prefix match_geo_aware_workforce_orchestrator_assignment_. Nothing settles against it while open.

status string required
openupheldrejected
conflicting_signals array of object required

Which signals disagreed, named rather than summarised as a failure.

2 fields
signal string required

Which signal disagreed: geofence_entry or start_passcode for arrival; completion_passcode, dwell_duration or telemetry_continuity for completion. Name it in accepted_signals at POST /v1/allocation/exceptions/{exception_id}/adjudicate.

geofence_entrystart_passcodecompletion_passcodedwell_durationtelemetry_continuity
detail string required

What this signal said, in plain words, and where it parted from the others. Read every one before you decide: the conflict is stated for you, never resolved by a default.

decision one of optional

The ruling once a person has made one: the outcome, who decided, when, their reason and the signals they accepted, or null while the exception is open. It's never edited; a later disagreement is a new decision.

Decisionor
raised_at string · date-time optional

When the exception was raised, as an RFC 3339 timestamp in UTC. Nothing has settled against the assignment since this moment, and nothing will until a person decides.

has_more boolean required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

next_cursor string · nullable optional

Where the next page starts, or null when this is the last page. Pass it back unchanged to continue.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# query parameters: limit (optional), starting_after (optional), status (optional), assignment_id (optional)
curl -X GET "https://sandbox.droomwork.io/v1/allocation/exceptions?limit=25&status=open&assignment_id=match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

// query parameters: limit (optional), starting_after (optional), status (optional), assignment_id (optional)
const result = await api.allocationExceptionsList({ limit: 25, status: 'open', assignmentId: 'match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
// query parameters: limit (optional), starting_after (optional), status (optional), assignment_id (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/exceptions?limit=25&status=open&assignment_id=match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
  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.MATCHApi(client)

# query parameters: limit (optional), starting_after (optional), status (optional), assignment_id (optional)
result = api.allocation_exceptions_list(limit=25, status='open', assignment_id='match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os

import requests

# query parameters: limit (optional), starting_after (optional), status (optional), assignment_id (optional)
response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/allocation/exceptions?limit=25&status=open&assignment_id=match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

# query parameters: limit (optional), starting_after (optional), status (optional), assignment_id (optional)
$result = $api->allocationExceptionsList(limit: 25, status: 'open', assignment_id: 'match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
// query parameters: limit (optional), starting_after (optional), status (optional), assignment_id (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/exceptions?limit=25&status=open&assignment_id=match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
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.MatchApi;
import com.droomwork.sdk.model.*;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

// query parameters: limit (optional), starting_after (optional), status (optional), assignment_id (optional)
var result = api.allocationExceptionsList(25, null, MatchExceptionStatus.fromValue("open"), "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
// query parameters: limit (optional), starting_after (optional), status (optional), assignment_id (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/exceptions?limit=25&status=open&assignment_id=match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"))
    .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 MATCHApi(config);

// query parameters: limit (optional), starting_after (optional), status (optional), assignment_id (optional)
var result = api.AllocationExceptionsList(limit: 25, status: MatchExceptionStatus.Open, assignmentId: "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
// query parameters: limit (optional), starting_after (optional), status (optional), assignment_id (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/allocation/exceptions?limit=25&status=open&assignment_id=match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
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), starting_after (optional), status (optional), assignment_id (optional)
result, _, err := client.MATCHAPI.AllocationExceptionsList(ctx).Limit(25).Status(droomwork.MatchExceptionStatus("open")).AssignmentId("match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
// query parameters: limit (optional), starting_after (optional), status (optional), assignment_id (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/allocation/exceptions?limit=25&status=open&assignment_id=match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", 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)
Response
{
  "object": "list",
  "data": [
    {
      "id": "match_geo_aware_workforce_orchestrator_exception_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "assignment_exception",
      "livemode": false,
      "mocked": true,
      "assignment_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "status": "open",
      "conflicting_signals": [
        {
          "signal": "geofence_entry",
          "detail": "The payee has no verified destination, so this line cannot be paid."
        }
      ],
      "decision": {
        "outcome": "upheld",
        "decided_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
        "decided_at": "2026-09-01T09:00:00Z",
        "reason": "The requester confirmed the work in person.",
        "accepted_signals": [
          "example"
        ]
      },
      "raised_at": "2026-09-01T09:00:00Z"
    }
  ],
  "has_more": true,
  "next_cursor": "example"
}
POST/v1/allocation/exceptions/{exception_id}/adjudicate#

Decide whether the work happened

allocation.exceptions.adjudicate

This records a person's ruling on one exception, and it is the only call that writes one. Your own product can keep the whole review in one place, with the decision landing on the record the conflict is written on.

Send the outcome you decided and, in your own words, why. You get the exception back with the decision recorded on it and the status it now carries. The exception has to exist already: its id comes from GET /v1/allocation/exceptions.

accepted_signals is optional, and it is for the case where only some of the signals persuaded you; name them with the signal values from this exception's own conflicting_signals, which you read off the record.

An unknown exception_id is refused with not_found. Read the exception back at GET /v1/allocation/exceptions afterwards; there is no single-exception read.

Path parameters

exception_id string required

The exception's identifier, from the id of an exception you listed at GET /v1/allocation/exceptions or received on the assignment.exception_raised webhook. It starts with match_geo_aware_workforce_orchestrator_exception_.

Headers

Idempotency-Key string required

A 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

outcome string required

Your decision: upheld or dismissed. On a flag, upheld suspends the worker where more than one signal fired and dismissed returns them; on an exception, upheld seals the proof and dismissed closes the assignment without one.

uphelddismissed
reason string required

Why you decided as you did, in your own words, at least one character. It is recorded with the decision and can't be edited afterwards.

accepted_signals array of string optional

Name the signals you accepted, when only some persuaded you: by source from a flag's signals, or by signal from an exception's conflicting_signals. Recorded with the decision.

Returns

The exception with the decision now recorded on it and the state it has reached.

id string required

The exception's identifier. It starts with match_geo_aware_workforce_orchestrator_exception_ and never changes; pass it as exception_id at POST /v1/allocation/exceptions/{exception_id}/adjudicate, or as starting_after to page past it.

object always "assignment_exception" required

Always assignment_exception. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

assignment_id string required

The assignment whose completion is in question, by its id from POST /v1/allocation/assignments or POST /v1/allocation/offers/{offer_id}/accept, prefix match_geo_aware_workforce_orchestrator_assignment_. Nothing settles against it while open.

status string required
openupheldrejected
conflicting_signals array of object required

Which signals disagreed, named rather than summarised as a failure.

2 fields
signal string required

Which signal disagreed: geofence_entry or start_passcode for arrival; completion_passcode, dwell_duration or telemetry_continuity for completion. Name it in accepted_signals at POST /v1/allocation/exceptions/{exception_id}/adjudicate.

geofence_entrystart_passcodecompletion_passcodedwell_durationtelemetry_continuity
detail string required

What this signal said, in plain words, and where it parted from the others. Read every one before you decide: the conflict is stated for you, never resolved by a default.

decision one of optional

The ruling once a person has made one: the outcome, who decided, when, their reason and the signals they accepted, or null while the exception is open. It's never edited; a later disagreement is a new decision.

Decisionor
raised_at string · date-time optional

When the exception was raised, as an RFC 3339 timestamp in UTC. Nothing has settled against the assignment since this moment, and nothing will until a person decides.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X POST "https://sandbox.droomwork.io/v1/allocation/exceptions/match_geo_aware_workforce_orchestrator_exception_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/adjudicate" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"outcome":"upheld","reason":"The requester confirmed the work in person.","accepted_signals":["example"]}'
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationExceptionsAdjudicate({
  exceptionId: 'match_geo_aware_workforce_orchestrator_exception_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
  idempotencyKey: crypto.randomUUID(),
  matchAdjudicateRequest: {"outcome":"upheld","reason":"The requester confirmed the work in person.","acceptedSignals":["example"]},
});
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/exceptions/match_geo_aware_workforce_orchestrator_exception_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/adjudicate', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    "outcome": "upheld",
    "reason": "The requester confirmed the work in person.",
    "accepted_signals": [
      "example"
    ]
  }),
});
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.MATCHApi(client)

result = api.allocation_exceptions_adjudicate(exception_id='match_geo_aware_workforce_orchestrator_exception_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', body={"outcome": "upheld", "reason": "The requester confirmed the work in person.", "accepted_signals": ["example"]})
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/allocation/exceptions/match_geo_aware_workforce_orchestrator_exception_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/adjudicate',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
        'Idempotency-Key': str(uuid.uuid4()),
    },
    json={"outcome": "upheld", "reason": "The requester confirmed the work in person.", "accepted_signals": ["example"]},
)
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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationExceptionsAdjudicate($idempotencyKey, json_decode('{"outcome":"upheld","reason":"The requester confirmed the work in person.","accepted_signals":["example"]}', true));
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/exceptions/match_geo_aware_workforce_orchestrator_exception_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/adjudicate');
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 => '{"outcome":"upheld","reason":"The requester confirmed the work in person.","accepted_signals":["example"]}',
]);
$result = json_decode(curl_exec($ch), true);
import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationExceptionsAdjudicate("match_geo_aware_workforce_orchestrator_exception_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/exceptions/match_geo_aware_workforce_orchestrator_exception_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/adjudicate"))
    .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("""
        {
          "outcome": "upheld",
          "reason": "The requester confirmed the work in person.",
          "accepted_signals": [
            "example"
          ]
        }
        """))
    .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 MATCHApi(config);

var result = api.AllocationExceptionsAdjudicate(exceptionId: "match_geo_aware_workforce_orchestrator_exception_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/allocation/exceptions/match_geo_aware_workforce_orchestrator_exception_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/adjudicate");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
    {
      "outcome": "upheld",
      "reason": "The requester confirmed the work in person.",
      "accepted_signals": [
        "example"
      ]
    }
    """, 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.MATCHAPI.AllocationExceptionsAdjudicate(ctx, "match_geo_aware_workforce_orchestrator_exception_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").IdempotencyKey(key).MatchAdjudicateRequest(body).Execute()
body := strings.NewReader(`{
  "outcome": "upheld",
  "reason": "The requester confirmed the work in person.",
  "accepted_signals": [
    "example"
  ]
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/allocation/exceptions/match_geo_aware_workforce_orchestrator_exception_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/adjudicate", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_exception_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "assignment_exception",
  "livemode": false,
  "mocked": true,
  "assignment_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "status": "open",
  "conflicting_signals": [
    {
      "signal": "geofence_entry",
      "detail": "The payee has no verified destination, so this line cannot be paid."
    }
  ],
  "decision": {
    "outcome": "upheld",
    "decided_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
    "decided_at": "2026-09-01T09:00:00Z",
    "reason": "The requester confirmed the work in person.",
    "accepted_signals": [
      "example"
    ]
  },
  "raised_at": "2026-09-01T09:00:00Z"
}
GET/v1/allocation/supply_gaps#

Why orders went unfilled

allocation.supply_gaps.list

A supply gap is one work order that went unfilled. Each one names the occupation asked for and the zone it fell in, so you can see which occupations and which zones your orders keep failing in, instead of reading them order by order.

You get every gap your organisation holds, each naming the work order it was declared for, the reason it went unfilled, how many offers had gone out by then, and when that was.

Take work_order_id to GET /v1/allocation/work_orders/{work_order_id} for the headcount, location, time window and constraints the order asked for.

Query parameters

limit integer optional

How many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.

starting_after string optional

The id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.

reason string optional

Return only gaps declared for one reason: cascade_exhausted (the eligible pool ran out), expired (the order's expires_at passed), no_eligible_supply (nobody passed the gates) or all_offers_declined. Leave it out to get every reason.

cascade_exhaustedexpiredno_eligible_supplyall_offers_declined
occupation_code string optional

Return only gaps in one occupation, by the occupation_code you sent at POST /v1/allocation/work_orders, such as ng-7412. Leave it out to get gaps in every occupation.

zone string optional

Return only gaps whose order fell in one operational zone, by the zone's id as it reads in zone.id on each gap or on a receipt from POST /v1/allocation/heartbeats, such as zone_lagos_mainland_03. Leave it out to get gaps in every zone.

Returns

A page of supply gaps, each naming the order that went unfilled and why.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of SupplyGap required

The records on this page, in the order the list promises. Empty when nothing matched.

9 fields of SupplyGap
object always "supply_gap" required

Always supply_gap. Tells you which kind of record you are looking at, so one handler can read any response.

work_order_id string required

The unfilled order, by its id from POST /v1/allocation/work_orders, starting match_geo_aware_workforce_orchestrator_work_order_. Read it at GET /v1/allocation/work_orders/{work_order_id} for the occupation, location, window and constraints.

reason string required

Why nobody took the work. Specific, because a gap without a reason cannot be acted on.

cascade_exhaustedexpiredno_eligible_supplyall_offers_declined
occupation_code string required

The occupation the order asked for, as its taxonomy code such as ng-7412. Filter the list on it to see where one trade keeps going unfilled.

zone one of optional

The operational zone the order's location falls in, with its id and name, or null when it falls in none. Pass the id as zone to filter the list to it.

Zoneor
eligible_considered integer · minimum 0 optional

How many workers the gates let through before the cascade ran out.

offers_made integer · minimum 0 optional

How many offers went out before the gap was declared, from 0 up. Read it beside eligible_considered: many eligible and few offers is a different problem from nobody eligible at all.

emitted_to_flow boolean optional

Whether the sourcing demand event was accepted by FLOW.

declared_at string · date-time required

When the order was declared unfilled, as an RFC 3339 timestamp in UTC: the moment the cascade ran out or the order expired, not the end of a reporting period.

has_more boolean required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

next_cursor string · nullable optional

Where the next page starts, or null when this is the last page. Pass it back unchanged to continue.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# query parameters: limit (optional), starting_after (optional), reason (optional), occupation_code (optional), zone (optional)
curl -X GET "https://sandbox.droomwork.io/v1/allocation/supply_gaps?limit=25&reason=cascade_exhausted&occupation_code=ng-7412&zone=zone_lagos_mainland_03" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

// query parameters: limit (optional), starting_after (optional), reason (optional), occupation_code (optional), zone (optional)
const result = await api.allocationSupplyGapsList({ limit: 25, reason: 'cascade_exhausted', occupationCode: 'ng-7412', zone: 'zone_lagos_mainland_03' });
// query parameters: limit (optional), starting_after (optional), reason (optional), occupation_code (optional), zone (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/supply_gaps?limit=25&reason=cascade_exhausted&occupation_code=ng-7412&zone=zone_lagos_mainland_03', {
  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.MATCHApi(client)

# query parameters: limit (optional), starting_after (optional), reason (optional), occupation_code (optional), zone (optional)
result = api.allocation_supply_gaps_list(limit=25, reason='cascade_exhausted', occupation_code='ng-7412', zone='zone_lagos_mainland_03')
import os

import requests

# query parameters: limit (optional), starting_after (optional), reason (optional), occupation_code (optional), zone (optional)
response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/allocation/supply_gaps?limit=25&reason=cascade_exhausted&occupation_code=ng-7412&zone=zone_lagos_mainland_03',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

# query parameters: limit (optional), starting_after (optional), reason (optional), occupation_code (optional), zone (optional)
$result = $api->allocationSupplyGapsList(limit: 25, reason: 'cascade_exhausted', occupation_code: 'ng-7412', zone: 'zone_lagos_mainland_03');
<?php
// query parameters: limit (optional), starting_after (optional), reason (optional), occupation_code (optional), zone (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/supply_gaps?limit=25&reason=cascade_exhausted&occupation_code=ng-7412&zone=zone_lagos_mainland_03');
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.MatchApi;
import com.droomwork.sdk.model.*;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

// query parameters: limit (optional), starting_after (optional), reason (optional), occupation_code (optional), zone (optional)
var result = api.allocationSupplyGapsList(25, null, MatchSupplyGapReason.fromValue("cascade_exhausted"), "ng-7412", "zone_lagos_mainland_03");
// query parameters: limit (optional), starting_after (optional), reason (optional), occupation_code (optional), zone (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/supply_gaps?limit=25&reason=cascade_exhausted&occupation_code=ng-7412&zone=zone_lagos_mainland_03"))
    .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 MATCHApi(config);

// query parameters: limit (optional), starting_after (optional), reason (optional), occupation_code (optional), zone (optional)
var result = api.AllocationSupplyGapsList(limit: 25, reason: MatchSupplyGapReason.CascadeExhausted, occupationCode: "ng-7412", zone: "zone_lagos_mainland_03");
// query parameters: limit (optional), starting_after (optional), reason (optional), occupation_code (optional), zone (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/allocation/supply_gaps?limit=25&reason=cascade_exhausted&occupation_code=ng-7412&zone=zone_lagos_mainland_03");
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), starting_after (optional), reason (optional), occupation_code (optional), zone (optional)
result, _, err := client.MATCHAPI.AllocationSupplyGapsList(ctx).Limit(25).Reason(droomwork.MatchSupplyGapReason("cascade_exhausted")).OccupationCode("ng-7412").Zone("zone_lagos_mainland_03").Execute()
// query parameters: limit (optional), starting_after (optional), reason (optional), occupation_code (optional), zone (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/allocation/supply_gaps?limit=25&reason=cascade_exhausted&occupation_code=ng-7412&zone=zone_lagos_mainland_03", 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)
Response
{
  "object": "list",
  "data": [
    {
      "object": "supply_gap",
      "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "reason": "cascade_exhausted",
      "occupation_code": "ng-7412",
      "declared_at": "2026-09-01T09:00:00Z",
      "zone": {
        "id": "zone_lagos_mainland_03",
        "name": "Lagos Mainland 03"
      },
      "eligible_considered": 0,
      "offers_made": 0,
      "emitted_to_flow": true
    }
  ],
  "has_more": true,
  "next_cursor": "example"
}
GET/v1/allocation/readiness#

What MATCH needs from other modules

allocation.readiness.retrieve

MATCH asks for facts that other modules establish, and this call is where it names them. So the order you build in is a decision you can take from the document, before a line of allocation code exists.

Send no parameters. Each row in rows names one fact MATCH asks for, the owner that establishes it when you run integrated, and the assurance it has to reach. always_enforced sits beside the rows and names the gates no attestation of yours lifts.

What comes back is MATCH's requirement, and not the progress of any one order. It takes no identifier and names no record of yours. One order's own state is on the order, at GET /v1/allocation/work_orders/{work_order_id}.

Nothing else waits on this call.

Returns

The facts MATCH asks for, each with its owning module and the assurance that fact needs.

object always "readiness_report" required

Always readiness_report. Tells you which kind of record you are looking at, so one handler can read any response.

module string required

The module whose readiness endpoint you called, such as GET /v1/payroll/readiness: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance) or route (payouts).

anchorproofrailflowmatchrunremitroute
mode string required

integrated means a required fact comes from the Droomwork module that owns it; standalone means you supply it yourself under an attestation. The checks are the same in both, and what each fact is worth is recorded on its row as held.

integratedstandalone
ready boolean required

true when every row is satisfied and the module has what it needs from you. false when a required fact is missing or held at too low an assurance; rows says which.

rows array of ReadinessRow required

One row per fact the module requires: who owns it, the assurance it needs, what you hold and whether that satisfies it. The rows that are not satisfied are what to bring.

8 fields of ReadinessRow
fact string required

Named for what it is, not for who supplies it.

owner string required

The sibling module that owns this fact when running integrated.

contract_row string optional
required string required

How a fact was established. Recorded on the fact rather than in configuration, so an attested identity and a verified one stay distinguishable a year later, which is the distinction that matters when something is disputed.

attestedverified
held one of required
Assuranceor
source string · nullable optional
integratedstandalonenull
satisfied boolean required
missing_because string · nullable optional
not_suppliedassurance_too_lownull
always_enforced array of string optional

Gates that hold in every mode and cannot be attested away. Consent before any source is queried, bank account validation, the tax identifiers returns are filed under, and duplicate detection within the organisation's own population.

weight_set_in_force string · nullable optional

No ranking runs without one.

gates_applied array of EligibilityGate optional

The gates the ranking applies, listed here so that nothing is checked at ranking time that was not reported first.

workers_without_executed_engagement integer optional

They can hold availability and will never receive an offer.

workers_without_live_passport integer optional

How many of your workers have no live, unrevoked Passport from ANCHOR. They never enter a ranking and never receive an offer, whatever availability they set.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X GET "https://sandbox.droomwork.io/v1/allocation/readiness" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationReadinessRetrieve({});
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/readiness', {
  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.MATCHApi(client)

result = api.allocation_readiness_retrieve()
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/allocation/readiness',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationReadinessRetrieve();
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/readiness');
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.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationReadinessRetrieve();
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/readiness"))
    .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 MATCHApi(config);

var result = api.AllocationReadinessRetrieve();
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/allocation/readiness");
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.MATCHAPI.AllocationReadinessRetrieve(ctx).Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/allocation/readiness", 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)
Response
{
  "object": "readiness_report",
  "module": "match",
  "mode": "integrated",
  "ready": true,
  "rows": [
    {
      "fact": "anchored_subject",
      "owner": "anchor",
      "contract_row": "6.4.20",
      "required": "attested",
      "held": "attested",
      "source": "integrated",
      "satisfied": true,
      "missing_because": null
    },
    {
      "fact": "executed_engagement",
      "owner": "rail",
      "contract_row": "6.6.24",
      "required": "attested",
      "held": "attested",
      "source": "integrated",
      "satisfied": true,
      "missing_because": null
    },
    {
      "fact": "competence_assertion",
      "owner": "proof",
      "contract_row": "6.8.7",
      "required": "attested",
      "held": "attested",
      "source": "integrated",
      "satisfied": true,
      "missing_because": null
    }
  ],
  "always_enforced": [
    "consent_token",
    "bank_account_validation",
    "tax_identifier_validation",
    "duplicate_detection"
  ],
  "weight_set_in_force": "example",
  "gates_applied": [
    "passport_live"
  ],
  "workers_without_executed_engagement": 1,
  "workers_without_live_passport": 1
}
POST/v1/allocation/assignments/{assignment_id}/start#

Say the work itself has begun

allocation.assignments.start

Being on site and working are two different things, and this is the step that says the work itself has begun. It leaves your product holding a start time separate from the arrival time, which is the pair a later question about the job is read against.

Send the assignment's identifier in the path and no request body. You get the assignment back with its status now in_progress, which is the state a completion is reached from.

Only an assignment in arrived can start. Either arrival call writes that state; one in any other state answers conflict.

POST /v1/allocation/assignments/{assignment_id}/complete is what moves it on from here.

Path parameters

assignment_id string required

The assignment's identifier: the id returned by POST /v1/allocation/assignments or POST /v1/allocation/offers/{offer_id}/accept, or listed at GET /v1/allocation/assignments. It starts with match_geo_aware_workforce_orchestrator_assignment_.

Headers

Idempotency-Key string required

A 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 assignment, now in_progress, with the work itself recorded as begun.

id string required

The assignment's identifier. It starts with match_geo_aware_workforce_orchestrator_assignment_ and never changes; pass it as {assignment_id} to GET /v1/allocation/assignments/{assignment_id} and its arrival, completion, cancel and proof calls.

object always "assignment" required

Always assignment. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

work_order_id string required

The id of the work order this assignment fills, as POST /v1/allocation/work_orders returned it, starting with match_geo_aware_workforce_orchestrator_work_order_. Pass it as {work_order_id} to read the order.

worker_id string required

The worker doing the work, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens. Every arrival and completion signal on this record is about this one person.

offer_id string optional

The id of the accepted offer this assignment came from, as listed at GET /v1/allocation/work_orders/{work_order_id}/offers, starting with match_geo_aware_workforce_orchestrator_offer_. Absent when you created it directly for work agreed elsewhere.

status string required

Work that has started resolves through completion or through exception. It is never cancelled, so the evidence of what happened survives.

accepteden_routearrivedin_progresscompletedexceptioncancelled
geofence_entry one of optional

The worker's entry into the geofence around the work location: when, where and the radius, one of the two signals arrival rests on. Null until arrival is confirmed.

GeofenceEntryor
start_passcode_verified_at string · date-time · nullable optional

When the start passcode the requester gave the worker on site was verified, as an RFC 3339 timestamp in UTC: the second of the two arrival signals. Null until arrival is confirmed.

completion_passcode_verified_at string · date-time · nullable optional

When the completion passcode was verified, as an RFC 3339 timestamp in UTC: one of the three signals completion rests on. Null until you confirm completion.

dwell one of optional

How long the worker was at the location, as you sent it on completion: when it started, when it ended and the whole seconds between. null until you confirm completion.

DwellRecordor
telemetry_continuity one of optional

Whether the heartbeat stream held across the working window, as you sent it on completion: continuous or not, how many gaps, and the longest in seconds. null until you confirm completion.

TelemetryContinuityor
proof_of_service_id string · nullable optional

The id of the sealed proof for this assignment, starting with match_geo_aware_workforce_orchestrator_proof_, as GET /v1/allocation/assignments/{assignment_id}/proof returns it. null until the bundle is sealed.

exception_id string · nullable optional

The id of the exception raised on this assignment, starting with match_geo_aware_workforce_orchestrator_exception_, as listed at GET /v1/allocation/exceptions. null unless the signals disagreed and a person was asked.

cancelled_reason string · nullable optional

Why the assignment was cancelled: the reason sent with the cancellation, or worker_removed_from_cascade when ANCHOR or RAIL removed the worker. null unless status is cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X POST "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/start" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationAssignmentsStart({ assignmentId: 'match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/start', {
  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.MATCHApi(client)

result = api.allocation_assignments_start(assignment_id='match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/start',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationAssignmentsStart(assignment_id: 'match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/start');
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.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationAssignmentsStart("match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/start"))
    .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 MATCHApi(config);

var result = api.AllocationAssignmentsStart(assignmentId: "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/start");
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.MATCHAPI.AllocationAssignmentsStart(ctx, "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/start", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "assignment",
  "livemode": false,
  "mocked": true,
  "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "status": "in_progress",
  "offer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "geofence_entry": {
    "entered_at": "2026-09-01T09:00:00Z",
    "position": {
      "latitude": -90,
      "longitude": -180,
      "accuracy_metres": 0
    },
    "radius_metres": 0
  },
  "start_passcode_verified_at": "2026-09-01T09:00:00Z",
  "completion_passcode_verified_at": "2026-09-01T09:00:00Z",
  "dwell": {
    "started_at": "2026-09-01T09:00:00Z",
    "ended_at": "2026-09-01T09:00:00Z",
    "seconds": 0
  },
  "telemetry_continuity": {
    "continuous": true,
    "gap_count": 0,
    "longest_gap_seconds": 0
  },
  "proof_of_service_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "exception_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "cancelled_reason": "example",
  "created_at": "2026-09-01T09:00:00Z"
}
POST/v1/allocation/assignments/{assignment_id}/arrive#

Mark the worker as on site

allocation.assignments.arrive

This is the lifecycle step for a worker who has reached the work location, and the one that publishes an event. A product watching events learns that somebody is on site without polling the assignment for it.

Send the assignment's identifier in the path and no request body. You get the assignment back with its status now arrived, and assignment.arrived published on the assignment's own event stream.

Only an assignment in en_route can arrive, which POST /v1/allocation/assignments/{assignment_id}/depart writes. One in any other state answers conflict.

Follow the event at GET /v1/allocation/events with stream set to the assignment's id. POST /v1/allocation/assignments/{assignment_id}/arrival reaches the same arrived state carrying the geofence entry and the start passcode.

Path parameters

assignment_id string required

The assignment's identifier: the id returned by POST /v1/allocation/assignments or POST /v1/allocation/offers/{offer_id}/accept, or listed at GET /v1/allocation/assignments. It starts with match_geo_aware_workforce_orchestrator_assignment_.

Headers

Idempotency-Key string required

A 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 assignment, now arrived, with assignment.arrived published on its own event stream.

id string required

The assignment's identifier. It starts with match_geo_aware_workforce_orchestrator_assignment_ and never changes; pass it as {assignment_id} to GET /v1/allocation/assignments/{assignment_id} and its arrival, completion, cancel and proof calls.

object always "assignment" required

Always assignment. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

work_order_id string required

The id of the work order this assignment fills, as POST /v1/allocation/work_orders returned it, starting with match_geo_aware_workforce_orchestrator_work_order_. Pass it as {work_order_id} to read the order.

worker_id string required

The worker doing the work, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens. Every arrival and completion signal on this record is about this one person.

offer_id string optional

The id of the accepted offer this assignment came from, as listed at GET /v1/allocation/work_orders/{work_order_id}/offers, starting with match_geo_aware_workforce_orchestrator_offer_. Absent when you created it directly for work agreed elsewhere.

status string required

Work that has started resolves through completion or through exception. It is never cancelled, so the evidence of what happened survives.

accepteden_routearrivedin_progresscompletedexceptioncancelled
geofence_entry one of optional

The worker's entry into the geofence around the work location: when, where and the radius, one of the two signals arrival rests on. Null until arrival is confirmed.

GeofenceEntryor
start_passcode_verified_at string · date-time · nullable optional

When the start passcode the requester gave the worker on site was verified, as an RFC 3339 timestamp in UTC: the second of the two arrival signals. Null until arrival is confirmed.

completion_passcode_verified_at string · date-time · nullable optional

When the completion passcode was verified, as an RFC 3339 timestamp in UTC: one of the three signals completion rests on. Null until you confirm completion.

dwell one of optional

How long the worker was at the location, as you sent it on completion: when it started, when it ended and the whole seconds between. null until you confirm completion.

DwellRecordor
telemetry_continuity one of optional

Whether the heartbeat stream held across the working window, as you sent it on completion: continuous or not, how many gaps, and the longest in seconds. null until you confirm completion.

TelemetryContinuityor
proof_of_service_id string · nullable optional

The id of the sealed proof for this assignment, starting with match_geo_aware_workforce_orchestrator_proof_, as GET /v1/allocation/assignments/{assignment_id}/proof returns it. null until the bundle is sealed.

exception_id string · nullable optional

The id of the exception raised on this assignment, starting with match_geo_aware_workforce_orchestrator_exception_, as listed at GET /v1/allocation/exceptions. null unless the signals disagreed and a person was asked.

cancelled_reason string · nullable optional

Why the assignment was cancelled: the reason sent with the cancellation, or worker_removed_from_cascade when ANCHOR or RAIL removed the worker. null unless status is cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X POST "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/arrive" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationAssignmentsArrive({ assignmentId: 'match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/arrive', {
  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.MATCHApi(client)

result = api.allocation_assignments_arrive(assignment_id='match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/arrive',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationAssignmentsArrive(assignment_id: 'match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/arrive');
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.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationAssignmentsArrive("match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/arrive"))
    .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 MATCHApi(config);

var result = api.AllocationAssignmentsArrive(assignmentId: "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/arrive");
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.MATCHAPI.AllocationAssignmentsArrive(ctx, "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/arrive", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "assignment",
  "livemode": false,
  "mocked": true,
  "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "status": "arrived",
  "offer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "geofence_entry": {
    "entered_at": "2026-09-01T09:00:00Z",
    "position": {
      "latitude": -90,
      "longitude": -180,
      "accuracy_metres": 0
    },
    "radius_metres": 0
  },
  "start_passcode_verified_at": "2026-09-01T09:00:00Z",
  "completion_passcode_verified_at": "2026-09-01T09:00:00Z",
  "dwell": {
    "started_at": "2026-09-01T09:00:00Z",
    "ended_at": "2026-09-01T09:00:00Z",
    "seconds": 0
  },
  "telemetry_continuity": {
    "continuous": true,
    "gap_count": 0,
    "longest_gap_seconds": 0
  },
  "proof_of_service_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "exception_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "cancelled_reason": "example",
  "created_at": "2026-09-01T09:00:00Z"
}
POST/v1/allocation/assignments/{assignment_id}/depart#

Record that the worker has set off

allocation.assignments.depart

This is the step that says a worker is on the way. Until it is recorded a job looks the same whether somebody has left for it or not, and en_route is the state your own screens can show travel from.

Send the assignment's identifier in the path and no request body. You get the assignment back with its status now en_route, the state it sits in while the worker travels to the work location.

Only an assignment in accepted can depart, which is the state every assignment is created in. One in any other state answers conflict.

Arrival is the next step along, at POST /v1/allocation/assignments/{assignment_id}/arrive.

Path parameters

assignment_id string required

The assignment's identifier: the id returned by POST /v1/allocation/assignments or POST /v1/allocation/offers/{offer_id}/accept, or listed at GET /v1/allocation/assignments. It starts with match_geo_aware_workforce_orchestrator_assignment_.

Headers

Idempotency-Key string required

A 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 assignment, now en_route, the state it sits in while the worker travels.

id string required

The assignment's identifier. It starts with match_geo_aware_workforce_orchestrator_assignment_ and never changes; pass it as {assignment_id} to GET /v1/allocation/assignments/{assignment_id} and its arrival, completion, cancel and proof calls.

object always "assignment" required

Always assignment. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

work_order_id string required

The id of the work order this assignment fills, as POST /v1/allocation/work_orders returned it, starting with match_geo_aware_workforce_orchestrator_work_order_. Pass it as {work_order_id} to read the order.

worker_id string required

The worker doing the work, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens. Every arrival and completion signal on this record is about this one person.

offer_id string optional

The id of the accepted offer this assignment came from, as listed at GET /v1/allocation/work_orders/{work_order_id}/offers, starting with match_geo_aware_workforce_orchestrator_offer_. Absent when you created it directly for work agreed elsewhere.

status string required

Work that has started resolves through completion or through exception. It is never cancelled, so the evidence of what happened survives.

accepteden_routearrivedin_progresscompletedexceptioncancelled
geofence_entry one of optional

The worker's entry into the geofence around the work location: when, where and the radius, one of the two signals arrival rests on. Null until arrival is confirmed.

GeofenceEntryor
start_passcode_verified_at string · date-time · nullable optional

When the start passcode the requester gave the worker on site was verified, as an RFC 3339 timestamp in UTC: the second of the two arrival signals. Null until arrival is confirmed.

completion_passcode_verified_at string · date-time · nullable optional

When the completion passcode was verified, as an RFC 3339 timestamp in UTC: one of the three signals completion rests on. Null until you confirm completion.

dwell one of optional

How long the worker was at the location, as you sent it on completion: when it started, when it ended and the whole seconds between. null until you confirm completion.

DwellRecordor
telemetry_continuity one of optional

Whether the heartbeat stream held across the working window, as you sent it on completion: continuous or not, how many gaps, and the longest in seconds. null until you confirm completion.

TelemetryContinuityor
proof_of_service_id string · nullable optional

The id of the sealed proof for this assignment, starting with match_geo_aware_workforce_orchestrator_proof_, as GET /v1/allocation/assignments/{assignment_id}/proof returns it. null until the bundle is sealed.

exception_id string · nullable optional

The id of the exception raised on this assignment, starting with match_geo_aware_workforce_orchestrator_exception_, as listed at GET /v1/allocation/exceptions. null unless the signals disagreed and a person was asked.

cancelled_reason string · nullable optional

Why the assignment was cancelled: the reason sent with the cancellation, or worker_removed_from_cascade when ANCHOR or RAIL removed the worker. null unless status is cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X POST "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/depart" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationAssignmentsDepart({ assignmentId: 'match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/depart', {
  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.MATCHApi(client)

result = api.allocation_assignments_depart(assignment_id='match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/depart',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationAssignmentsDepart(assignment_id: 'match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/depart');
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.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationAssignmentsDepart("match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/depart"))
    .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 MATCHApi(config);

var result = api.AllocationAssignmentsDepart(assignmentId: "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/depart");
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.MATCHAPI.AllocationAssignmentsDepart(ctx, "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/depart", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "assignment",
  "livemode": false,
  "mocked": true,
  "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "status": "en_route",
  "offer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "geofence_entry": {
    "entered_at": "2026-09-01T09:00:00Z",
    "position": {
      "latitude": -90,
      "longitude": -180,
      "accuracy_metres": 0
    },
    "radius_metres": 0
  },
  "start_passcode_verified_at": "2026-09-01T09:00:00Z",
  "completion_passcode_verified_at": "2026-09-01T09:00:00Z",
  "dwell": {
    "started_at": "2026-09-01T09:00:00Z",
    "ended_at": "2026-09-01T09:00:00Z",
    "seconds": 0
  },
  "telemetry_continuity": {
    "continuous": true,
    "gap_count": 0,
    "longest_gap_seconds": 0
  },
  "proof_of_service_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "exception_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "cancelled_reason": "example",
  "created_at": "2026-09-01T09:00:00Z"
}
POST/v1/allocation/assignments/{assignment_id}/complete#

Finish a job and seal its proof

allocation.assignments.complete

This takes a job out of your work in progress and puts a sealed proof of service behind it. From here a product can treat the assignment as finished work and read the bundle that stands for what happened on it.

Send the assignment's identifier in the path and no request body. You get the assignment back with its status now completed, and a proof of service is sealed for the job as assignment.completed is published.

Only an assignment in in_progress can complete, which POST /v1/allocation/assignments/{assignment_id}/start writes. One in any other state answers conflict.

Read the proof at GET /v1/allocation/assignments/{assignment_id}/proof, and find it among the rest at GET /v1/allocation/proofs.

Path parameters

assignment_id string required

The assignment's identifier: the id returned by POST /v1/allocation/assignments or POST /v1/allocation/offers/{offer_id}/accept, or listed at GET /v1/allocation/assignments. It starts with match_geo_aware_workforce_orchestrator_assignment_.

Headers

Idempotency-Key string required

A 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 assignment, now completed, with a sealed proof of service standing behind it.

id string required

The assignment's identifier. It starts with match_geo_aware_workforce_orchestrator_assignment_ and never changes; pass it as {assignment_id} to GET /v1/allocation/assignments/{assignment_id} and its arrival, completion, cancel and proof calls.

object always "assignment" required

Always assignment. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

work_order_id string required

The id of the work order this assignment fills, as POST /v1/allocation/work_orders returned it, starting with match_geo_aware_workforce_orchestrator_work_order_. Pass it as {work_order_id} to read the order.

worker_id string required

The worker doing the work, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens. Every arrival and completion signal on this record is about this one person.

offer_id string optional

The id of the accepted offer this assignment came from, as listed at GET /v1/allocation/work_orders/{work_order_id}/offers, starting with match_geo_aware_workforce_orchestrator_offer_. Absent when you created it directly for work agreed elsewhere.

status string required

Work that has started resolves through completion or through exception. It is never cancelled, so the evidence of what happened survives.

accepteden_routearrivedin_progresscompletedexceptioncancelled
geofence_entry one of optional

The worker's entry into the geofence around the work location: when, where and the radius, one of the two signals arrival rests on. Null until arrival is confirmed.

GeofenceEntryor
start_passcode_verified_at string · date-time · nullable optional

When the start passcode the requester gave the worker on site was verified, as an RFC 3339 timestamp in UTC: the second of the two arrival signals. Null until arrival is confirmed.

completion_passcode_verified_at string · date-time · nullable optional

When the completion passcode was verified, as an RFC 3339 timestamp in UTC: one of the three signals completion rests on. Null until you confirm completion.

dwell one of optional

How long the worker was at the location, as you sent it on completion: when it started, when it ended and the whole seconds between. null until you confirm completion.

DwellRecordor
telemetry_continuity one of optional

Whether the heartbeat stream held across the working window, as you sent it on completion: continuous or not, how many gaps, and the longest in seconds. null until you confirm completion.

TelemetryContinuityor
proof_of_service_id string · nullable optional

The id of the sealed proof for this assignment, starting with match_geo_aware_workforce_orchestrator_proof_, as GET /v1/allocation/assignments/{assignment_id}/proof returns it. null until the bundle is sealed.

exception_id string · nullable optional

The id of the exception raised on this assignment, starting with match_geo_aware_workforce_orchestrator_exception_, as listed at GET /v1/allocation/exceptions. null unless the signals disagreed and a person was asked.

cancelled_reason string · nullable optional

Why the assignment was cancelled: the reason sent with the cancellation, or worker_removed_from_cascade when ANCHOR or RAIL removed the worker. null unless status is cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X POST "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/complete" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationAssignmentsComplete({ assignmentId: 'match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/complete', {
  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.MATCHApi(client)

result = api.allocation_assignments_complete(assignment_id='match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/complete',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationAssignmentsComplete(assignment_id: 'match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/complete');
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.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationAssignmentsComplete("match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/complete"))
    .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 MATCHApi(config);

var result = api.AllocationAssignmentsComplete(assignmentId: "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/complete");
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.MATCHAPI.AllocationAssignmentsComplete(ctx, "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/complete", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "assignment",
  "livemode": false,
  "mocked": true,
  "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "status": "completed",
  "offer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "geofence_entry": {
    "entered_at": "2026-09-01T09:00:00Z",
    "position": {
      "latitude": -90,
      "longitude": -180,
      "accuracy_metres": 0
    },
    "radius_metres": 0
  },
  "start_passcode_verified_at": "2026-09-01T09:00:00Z",
  "completion_passcode_verified_at": "2026-09-01T09:00:00Z",
  "dwell": {
    "started_at": "2026-09-01T09:00:00Z",
    "ended_at": "2026-09-01T09:00:00Z",
    "seconds": 0
  },
  "telemetry_continuity": {
    "continuous": true,
    "gap_count": 0,
    "longest_gap_seconds": 0
  },
  "proof_of_service_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "exception_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "cancelled_reason": "example",
  "created_at": "2026-09-01T09:00:00Z"
}
POST/v1/allocation/assignments/{assignment_id}/raise_exception#

Send a job in doubt to a person

allocation.assignments.raise_exception

Some jobs cannot be closed on the evidence collected against them, and this is the state for those. It keeps a job that is in doubt out of your finished work and in front of somebody who can decide it, instead of letting a weak record pass as a good one.

Send the assignment's identifier in the path and no request body. You get the assignment back with its status now exception, which is outside the states a proof of service is sealed from.

Only an assignment in in_progress or arrived can be sent this way. One in any other state answers conflict.

GET /v1/allocation/assignments/{assignment_id}/proof answers conflict while the assignment sits in exception, so nothing downstream reads a proof that was never sealed.

Path parameters

assignment_id string required

The assignment's identifier: the id returned by POST /v1/allocation/assignments or POST /v1/allocation/offers/{offer_id}/accept, or listed at GET /v1/allocation/assignments. It starts with match_geo_aware_workforce_orchestrator_assignment_.

Headers

Idempotency-Key string required

A 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 assignment, now in exception, outside the states a proof is sealed from.

id string required

The assignment's identifier. It starts with match_geo_aware_workforce_orchestrator_assignment_ and never changes; pass it as {assignment_id} to GET /v1/allocation/assignments/{assignment_id} and its arrival, completion, cancel and proof calls.

object always "assignment" required

Always assignment. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

work_order_id string required

The id of the work order this assignment fills, as POST /v1/allocation/work_orders returned it, starting with match_geo_aware_workforce_orchestrator_work_order_. Pass it as {work_order_id} to read the order.

worker_id string required

The worker doing the work, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens. Every arrival and completion signal on this record is about this one person.

offer_id string optional

The id of the accepted offer this assignment came from, as listed at GET /v1/allocation/work_orders/{work_order_id}/offers, starting with match_geo_aware_workforce_orchestrator_offer_. Absent when you created it directly for work agreed elsewhere.

status string required

Work that has started resolves through completion or through exception. It is never cancelled, so the evidence of what happened survives.

accepteden_routearrivedin_progresscompletedexceptioncancelled
geofence_entry one of optional

The worker's entry into the geofence around the work location: when, where and the radius, one of the two signals arrival rests on. Null until arrival is confirmed.

GeofenceEntryor
start_passcode_verified_at string · date-time · nullable optional

When the start passcode the requester gave the worker on site was verified, as an RFC 3339 timestamp in UTC: the second of the two arrival signals. Null until arrival is confirmed.

completion_passcode_verified_at string · date-time · nullable optional

When the completion passcode was verified, as an RFC 3339 timestamp in UTC: one of the three signals completion rests on. Null until you confirm completion.

dwell one of optional

How long the worker was at the location, as you sent it on completion: when it started, when it ended and the whole seconds between. null until you confirm completion.

DwellRecordor
telemetry_continuity one of optional

Whether the heartbeat stream held across the working window, as you sent it on completion: continuous or not, how many gaps, and the longest in seconds. null until you confirm completion.

TelemetryContinuityor
proof_of_service_id string · nullable optional

The id of the sealed proof for this assignment, starting with match_geo_aware_workforce_orchestrator_proof_, as GET /v1/allocation/assignments/{assignment_id}/proof returns it. null until the bundle is sealed.

exception_id string · nullable optional

The id of the exception raised on this assignment, starting with match_geo_aware_workforce_orchestrator_exception_, as listed at GET /v1/allocation/exceptions. null unless the signals disagreed and a person was asked.

cancelled_reason string · nullable optional

Why the assignment was cancelled: the reason sent with the cancellation, or worker_removed_from_cascade when ANCHOR or RAIL removed the worker. null unless status is cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X POST "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/raise_exception" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationAssignmentsRaiseException({ assignmentId: 'match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/raise_exception', {
  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.MATCHApi(client)

result = api.allocation_assignments_raise_exception(assignment_id='match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/raise_exception',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationAssignmentsRaiseException(assignment_id: 'match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/raise_exception');
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.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationAssignmentsRaiseException("match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/raise_exception"))
    .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 MATCHApi(config);

var result = api.AllocationAssignmentsRaiseException(assignmentId: "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/raise_exception");
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.MATCHAPI.AllocationAssignmentsRaiseException(ctx, "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/allocation/assignments/match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/raise_exception", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_assignment_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "assignment",
  "livemode": false,
  "mocked": true,
  "work_order_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "worker_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "status": "exception",
  "offer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "geofence_entry": {
    "entered_at": "2026-09-01T09:00:00Z",
    "position": {
      "latitude": -90,
      "longitude": -180,
      "accuracy_metres": 0
    },
    "radius_metres": 0
  },
  "start_passcode_verified_at": "2026-09-01T09:00:00Z",
  "completion_passcode_verified_at": "2026-09-01T09:00:00Z",
  "dwell": {
    "started_at": "2026-09-01T09:00:00Z",
    "ended_at": "2026-09-01T09:00:00Z",
    "seconds": 0
  },
  "telemetry_continuity": {
    "continuous": true,
    "gap_count": 0,
    "longest_gap_seconds": 0
  },
  "proof_of_service_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "exception_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "cancelled_reason": "example",
  "created_at": "2026-09-01T09:00:00Z"
}
POST/v1/allocation/work_orders/{work_order_id}/offer#

Take the order out to offer

allocation.work_orders.offer

This is the call that puts an order into offering, which is the state it has to reach before you can record it filled. Your own system decides when demand goes out, and the moment it went out is on the event stream afterwards.

Send no request body. The call takes the order from open to offered, and offer.made is appended to the order's own event stream.

The order has to be open, which is the state POST /v1/allocation/work_orders leaves it in.

Read the stream back at GET /v1/allocation/events with stream set to the order's id. An order that already reads offered is refused with conflict here, and from offered it can be filled or declared unfilled.

Path parameters

work_order_id string required

The order's identifier, from id on the order POST /v1/allocation/work_orders returned when you opened it, or on any order at GET /v1/allocation/work_orders. It starts with match_geo_aware_workforce_orchestrator_work_order_.

Headers

Idempotency-Key string required

A 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 order in its new state, which is the state a fill is accepted from.

id string required

The order's identifier, returned in id by POST /v1/allocation/work_orders when you opened it. It starts with match_geo_aware_workforce_orchestrator_work_order_, never changes, and is the work_order_id every call on the order takes.

object always "work_order" required

Always work_order. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

An order is open until it is offered, filled, cancelled or declared unfilled. Nothing moves it back.

openofferedfilledcancelledunfilled
requester_id string required

The requester the work is for, as you sent it in requester_id at POST /v1/allocation/work_orders: their sub_ subject reference, the subject_ref ANCHOR verified at POST /v1/identity/verifications before the order opened.

service_category string required

The kind of service the work is, as you named it when you opened the order, such as field_maintenance. It names the service; occupation_code names who can do it.

occupation_code string required

The occupation the work needs, as a code from the occupation taxonomy, such as ng-7412. Any supply gap declared for the order carries it, so sourcing demand names the occupation.

headcount integer · minimum 1 required

The order stays open until this many acceptances are held.

headcount_filled integer · minimum 0 optional

How many acceptances the order holds so far, counted from 0. Read it against headcount to see how far the order is from filling.

urgency_class string required

How hard the cascade pushes, and how quickly an offer expires.

standardpriorityemergency
location GeoPoint required

A position reading. Accuracy is carried because a reading without it cannot be judged.

3 fields of GeoPoint
latitude number · minimum -90 · maximum 90 required

Latitude in decimal degrees, from -90 to 90. Negative values are south of the equator.

longitude number · minimum -180 · maximum 180 required

Longitude in decimal degrees, from -180 to 180. Negative values are west of the Greenwich meridian.

accuracy_metres integer · minimum 0 required

The radius the device reports as its confidence. A large value weakens every inference drawn from the point.

location_description string optional

The place in words a worker would recognise, such as Ikeja industrial estate, gate 3. Optional; location is the position the ranking uses.

time_window TimeWindow required

When the work may be done. A window, never a single instant.

2 fields of TimeWindow
starts_at string · date-time required

When the window opens: the earliest the work may start, as an RFC 3339 timestamp in UTC.

ends_at string · date-time required

When the window closes: the latest the work may finish, as an RFC 3339 timestamp in UTC.

constraints array of Constraint optional

Hard constraints and soft preferences in one list, each naming which it is.

3 fields of Constraint
code string required

What the requirement is, as a code such as licence_electrical. A relaxation names the soft preference it dropped by this code, as its preference_code.

kind string required

hard for a requirement that is never relaxed and gates who can be ranked at all; soft for a preference the cascade may drop to widen the pool, with the drop recorded in the order's relaxations.

hardsoft
detail string optional

What the requirement means in words, such as Current electrical licence. Optional; the code is what a worker has to meet, and the detail is for people reading the order.

relaxations array of Relaxation optional

Every soft preference the cascade dropped, with the round it happened in.

4 fields of Relaxation
preference_code string required

The code of the soft preference that was dropped, as it appears in the order's constraints.

round integer · minimum 1 required

The cascade round the preference was dropped in, counted from 1. Match it to the same round in the ranking to see who the wider pool brought in.

relaxed_at string · date-time required

When the preference was dropped, as an RFC 3339 timestamp in UTC.

reason string required

Why the preference was dropped: pool_exhausted when the eligible pool ran out, expiry_approaching when the order's expires_at was near, headcount_unmet when acceptances fell short of headcount.

pool_exhaustedexpiry_approachingheadcount_unmet
escalation_bounds EscalationBounds optional

How far the search may widen. It's published on the order, so you can read the escalation policy.

3 fields of EscalationBounds
initial_radius_metres integer · minimum 0 required

The radius the first round searches within, in metres from location.

maximum_radius_metres integer · minimum 0 required

The furthest the radius may widen to, in metres from location. No round searches beyond it.

step_metres integer · minimum 1 required

How much the radius widens by on each escalation, in metres, at least 1. A round that widened is marked escalated in the ranking.

budget_ceiling Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

expires_at string · date-time required

On expiry the order is declared unfilled with a reason, never left open.

cancelled_reason string · nullable optional

Why the order was cancelled, as stated when it was cancelled. null while the order has not been cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X POST "https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/offer" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationWorkOrdersOffer({ workOrderId: 'match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/offer', {
  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.MATCHApi(client)

result = api.allocation_work_orders_offer(work_order_id='match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/offer',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationWorkOrdersOffer(work_order_id: 'match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/offer');
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.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationWorkOrdersOffer("match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/offer"))
    .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 MATCHApi(config);

var result = api.AllocationWorkOrdersOffer(workOrderId: "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/offer");
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.MATCHAPI.AllocationWorkOrdersOffer(ctx, "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/offer", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "work_order",
  "livemode": false,
  "mocked": true,
  "status": "offered",
  "requester_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "service_category": "field_maintenance",
  "occupation_code": "ng-7412",
  "headcount": 1,
  "urgency_class": "standard",
  "location": {
    "latitude": -90,
    "longitude": -180,
    "accuracy_metres": 0
  },
  "time_window": {
    "starts_at": "2026-09-01T09:00:00Z",
    "ends_at": "2026-09-01T09:00:00Z"
  },
  "expires_at": "2026-09-01T09:00:00Z",
  "headcount_filled": 0,
  "location_description": "example",
  "constraints": [
    {
      "code": "licence_electrical",
      "kind": "hard",
      "detail": "The payee has no verified destination, so this line cannot be paid."
    }
  ],
  "relaxations": [
    {
      "preference_code": "example",
      "round": 1,
      "relaxed_at": "2026-09-01T09:00:00Z",
      "reason": "pool_exhausted"
    }
  ],
  "escalation_bounds": {
    "initial_radius_metres": 0,
    "maximum_radius_metres": 0,
    "step_metres": 1
  },
  "budget_ceiling": {
    "amount": 100000000,
    "currency": "NGN"
  },
  "cancelled_reason": "example",
  "created_at": "2026-09-01T09:00:00Z"
}
POST/v1/allocation/work_orders/{work_order_id}/fill#

Fill a work order

allocation.work_orders.fill

Filling is how you say the order has the people it needed and the hunt is over. An order reading filled is always a thing somebody recorded on purpose.

Send the order's id in the path and no request body. You get the whole order back, now reading filled, with the headcount and the window it was opened for unchanged.

The order has to be reading offered, which is where POST /v1/allocation/work_orders/{work_order_id}/offer left it.

Filling an order that is not reading offered is refused with conflict, and so is offering or expiring one that already reads filled.

Path parameters

work_order_id string required

The order's identifier, from id on the order POST /v1/allocation/work_orders returned when you opened it, or on any order at GET /v1/allocation/work_orders. It starts with match_geo_aware_workforce_orchestrator_work_order_.

Headers

Idempotency-Key string required

A 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 order in its new state, which no further offer or expiry will move.

id string required

The order's identifier, returned in id by POST /v1/allocation/work_orders when you opened it. It starts with match_geo_aware_workforce_orchestrator_work_order_, never changes, and is the work_order_id every call on the order takes.

object always "work_order" required

Always work_order. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

An order is open until it is offered, filled, cancelled or declared unfilled. Nothing moves it back.

openofferedfilledcancelledunfilled
requester_id string required

The requester the work is for, as you sent it in requester_id at POST /v1/allocation/work_orders: their sub_ subject reference, the subject_ref ANCHOR verified at POST /v1/identity/verifications before the order opened.

service_category string required

The kind of service the work is, as you named it when you opened the order, such as field_maintenance. It names the service; occupation_code names who can do it.

occupation_code string required

The occupation the work needs, as a code from the occupation taxonomy, such as ng-7412. Any supply gap declared for the order carries it, so sourcing demand names the occupation.

headcount integer · minimum 1 required

The order stays open until this many acceptances are held.

headcount_filled integer · minimum 0 optional

How many acceptances the order holds so far, counted from 0. Read it against headcount to see how far the order is from filling.

urgency_class string required

How hard the cascade pushes, and how quickly an offer expires.

standardpriorityemergency
location GeoPoint required

A position reading. Accuracy is carried because a reading without it cannot be judged.

3 fields of GeoPoint
latitude number · minimum -90 · maximum 90 required

Latitude in decimal degrees, from -90 to 90. Negative values are south of the equator.

longitude number · minimum -180 · maximum 180 required

Longitude in decimal degrees, from -180 to 180. Negative values are west of the Greenwich meridian.

accuracy_metres integer · minimum 0 required

The radius the device reports as its confidence. A large value weakens every inference drawn from the point.

location_description string optional

The place in words a worker would recognise, such as Ikeja industrial estate, gate 3. Optional; location is the position the ranking uses.

time_window TimeWindow required

When the work may be done. A window, never a single instant.

2 fields of TimeWindow
starts_at string · date-time required

When the window opens: the earliest the work may start, as an RFC 3339 timestamp in UTC.

ends_at string · date-time required

When the window closes: the latest the work may finish, as an RFC 3339 timestamp in UTC.

constraints array of Constraint optional

Hard constraints and soft preferences in one list, each naming which it is.

3 fields of Constraint
code string required

What the requirement is, as a code such as licence_electrical. A relaxation names the soft preference it dropped by this code, as its preference_code.

kind string required

hard for a requirement that is never relaxed and gates who can be ranked at all; soft for a preference the cascade may drop to widen the pool, with the drop recorded in the order's relaxations.

hardsoft
detail string optional

What the requirement means in words, such as Current electrical licence. Optional; the code is what a worker has to meet, and the detail is for people reading the order.

relaxations array of Relaxation optional

Every soft preference the cascade dropped, with the round it happened in.

4 fields of Relaxation
preference_code string required

The code of the soft preference that was dropped, as it appears in the order's constraints.

round integer · minimum 1 required

The cascade round the preference was dropped in, counted from 1. Match it to the same round in the ranking to see who the wider pool brought in.

relaxed_at string · date-time required

When the preference was dropped, as an RFC 3339 timestamp in UTC.

reason string required

Why the preference was dropped: pool_exhausted when the eligible pool ran out, expiry_approaching when the order's expires_at was near, headcount_unmet when acceptances fell short of headcount.

pool_exhaustedexpiry_approachingheadcount_unmet
escalation_bounds EscalationBounds optional

How far the search may widen. It's published on the order, so you can read the escalation policy.

3 fields of EscalationBounds
initial_radius_metres integer · minimum 0 required

The radius the first round searches within, in metres from location.

maximum_radius_metres integer · minimum 0 required

The furthest the radius may widen to, in metres from location. No round searches beyond it.

step_metres integer · minimum 1 required

How much the radius widens by on each escalation, in metres, at least 1. A round that widened is marked escalated in the ranking.

budget_ceiling Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

expires_at string · date-time required

On expiry the order is declared unfilled with a reason, never left open.

cancelled_reason string · nullable optional

Why the order was cancelled, as stated when it was cancelled. null while the order has not been cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X POST "https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/fill" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationWorkOrdersFill({ workOrderId: 'match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/fill', {
  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.MATCHApi(client)

result = api.allocation_work_orders_fill(work_order_id='match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/fill',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationWorkOrdersFill(work_order_id: 'match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/fill');
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.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationWorkOrdersFill("match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/fill"))
    .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 MATCHApi(config);

var result = api.AllocationWorkOrdersFill(workOrderId: "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/fill");
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.MATCHAPI.AllocationWorkOrdersFill(ctx, "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/fill", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "work_order",
  "livemode": false,
  "mocked": true,
  "status": "filled",
  "requester_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "service_category": "field_maintenance",
  "occupation_code": "ng-7412",
  "headcount": 1,
  "urgency_class": "standard",
  "location": {
    "latitude": -90,
    "longitude": -180,
    "accuracy_metres": 0
  },
  "time_window": {
    "starts_at": "2026-09-01T09:00:00Z",
    "ends_at": "2026-09-01T09:00:00Z"
  },
  "expires_at": "2026-09-01T09:00:00Z",
  "headcount_filled": 0,
  "location_description": "example",
  "constraints": [
    {
      "code": "licence_electrical",
      "kind": "hard",
      "detail": "The payee has no verified destination, so this line cannot be paid."
    }
  ],
  "relaxations": [
    {
      "preference_code": "example",
      "round": 1,
      "relaxed_at": "2026-09-01T09:00:00Z",
      "reason": "pool_exhausted"
    }
  ],
  "escalation_bounds": {
    "initial_radius_metres": 0,
    "maximum_radius_metres": 0,
    "step_metres": 1
  },
  "budget_ceiling": {
    "amount": 100000000,
    "currency": "NGN"
  },
  "cancelled_reason": "example",
  "created_at": "2026-09-01T09:00:00Z"
}
POST/v1/allocation/work_orders/{work_order_id}/expire#

Expire a work order

allocation.work_orders.expire

Expiring is how an order whose time ran out is declared instead of left hanging. An order sitting open forever reads the same as one still being worked, and this call is what separates the two for anybody looking at your product.

Send no request body. The call takes the order from open or offered to unfilled, and work_order.unfilled is appended to the order's own event stream.

The order has to be reading open or offered; nothing else has to exist first.

Expiring an order that already reads filled, cancelled or unfilled is refused with conflict. Nothing takes an order back to open, so demand you still have is a new order from POST /v1/allocation/work_orders.

Path parameters

work_order_id string required

The order's identifier, from id on the order POST /v1/allocation/work_orders returned when you opened it, or on any order at GET /v1/allocation/work_orders. It starts with match_geo_aware_workforce_orchestrator_work_order_.

Headers

Idempotency-Key string required

A 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 order in its new state, which nothing takes back to open.

id string required

The order's identifier, returned in id by POST /v1/allocation/work_orders when you opened it. It starts with match_geo_aware_workforce_orchestrator_work_order_, never changes, and is the work_order_id every call on the order takes.

object always "work_order" required

Always work_order. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

An order is open until it is offered, filled, cancelled or declared unfilled. Nothing moves it back.

openofferedfilledcancelledunfilled
requester_id string required

The requester the work is for, as you sent it in requester_id at POST /v1/allocation/work_orders: their sub_ subject reference, the subject_ref ANCHOR verified at POST /v1/identity/verifications before the order opened.

service_category string required

The kind of service the work is, as you named it when you opened the order, such as field_maintenance. It names the service; occupation_code names who can do it.

occupation_code string required

The occupation the work needs, as a code from the occupation taxonomy, such as ng-7412. Any supply gap declared for the order carries it, so sourcing demand names the occupation.

headcount integer · minimum 1 required

The order stays open until this many acceptances are held.

headcount_filled integer · minimum 0 optional

How many acceptances the order holds so far, counted from 0. Read it against headcount to see how far the order is from filling.

urgency_class string required

How hard the cascade pushes, and how quickly an offer expires.

standardpriorityemergency
location GeoPoint required

A position reading. Accuracy is carried because a reading without it cannot be judged.

3 fields of GeoPoint
latitude number · minimum -90 · maximum 90 required

Latitude in decimal degrees, from -90 to 90. Negative values are south of the equator.

longitude number · minimum -180 · maximum 180 required

Longitude in decimal degrees, from -180 to 180. Negative values are west of the Greenwich meridian.

accuracy_metres integer · minimum 0 required

The radius the device reports as its confidence. A large value weakens every inference drawn from the point.

location_description string optional

The place in words a worker would recognise, such as Ikeja industrial estate, gate 3. Optional; location is the position the ranking uses.

time_window TimeWindow required

When the work may be done. A window, never a single instant.

2 fields of TimeWindow
starts_at string · date-time required

When the window opens: the earliest the work may start, as an RFC 3339 timestamp in UTC.

ends_at string · date-time required

When the window closes: the latest the work may finish, as an RFC 3339 timestamp in UTC.

constraints array of Constraint optional

Hard constraints and soft preferences in one list, each naming which it is.

3 fields of Constraint
code string required

What the requirement is, as a code such as licence_electrical. A relaxation names the soft preference it dropped by this code, as its preference_code.

kind string required

hard for a requirement that is never relaxed and gates who can be ranked at all; soft for a preference the cascade may drop to widen the pool, with the drop recorded in the order's relaxations.

hardsoft
detail string optional

What the requirement means in words, such as Current electrical licence. Optional; the code is what a worker has to meet, and the detail is for people reading the order.

relaxations array of Relaxation optional

Every soft preference the cascade dropped, with the round it happened in.

4 fields of Relaxation
preference_code string required

The code of the soft preference that was dropped, as it appears in the order's constraints.

round integer · minimum 1 required

The cascade round the preference was dropped in, counted from 1. Match it to the same round in the ranking to see who the wider pool brought in.

relaxed_at string · date-time required

When the preference was dropped, as an RFC 3339 timestamp in UTC.

reason string required

Why the preference was dropped: pool_exhausted when the eligible pool ran out, expiry_approaching when the order's expires_at was near, headcount_unmet when acceptances fell short of headcount.

pool_exhaustedexpiry_approachingheadcount_unmet
escalation_bounds EscalationBounds optional

How far the search may widen. It's published on the order, so you can read the escalation policy.

3 fields of EscalationBounds
initial_radius_metres integer · minimum 0 required

The radius the first round searches within, in metres from location.

maximum_radius_metres integer · minimum 0 required

The furthest the radius may widen to, in metres from location. No round searches beyond it.

step_metres integer · minimum 1 required

How much the radius widens by on each escalation, in metres, at least 1. A round that widened is marked escalated in the ranking.

budget_ceiling Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

expires_at string · date-time required

On expiry the order is declared unfilled with a reason, never left open.

cancelled_reason string · nullable optional

Why the order was cancelled, as stated when it was cancelled. null while the order has not been cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X POST "https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/expire" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationWorkOrdersExpire({ workOrderId: 'match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/expire', {
  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.MATCHApi(client)

result = api.allocation_work_orders_expire(work_order_id='match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/expire',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationWorkOrdersExpire(work_order_id: 'match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/expire');
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.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationWorkOrdersExpire("match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/expire"))
    .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 MATCHApi(config);

var result = api.AllocationWorkOrdersExpire(workOrderId: "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/expire");
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.MATCHAPI.AllocationWorkOrdersExpire(ctx, "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/allocation/work_orders/match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/expire", 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)
Response
{
  "id": "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "work_order",
  "livemode": false,
  "mocked": true,
  "status": "unfilled",
  "requester_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "service_category": "field_maintenance",
  "occupation_code": "ng-7412",
  "headcount": 1,
  "urgency_class": "standard",
  "location": {
    "latitude": -90,
    "longitude": -180,
    "accuracy_metres": 0
  },
  "time_window": {
    "starts_at": "2026-09-01T09:00:00Z",
    "ends_at": "2026-09-01T09:00:00Z"
  },
  "expires_at": "2026-09-01T09:00:00Z",
  "headcount_filled": 0,
  "location_description": "example",
  "constraints": [
    {
      "code": "licence_electrical",
      "kind": "hard",
      "detail": "The payee has no verified destination, so this line cannot be paid."
    }
  ],
  "relaxations": [
    {
      "preference_code": "example",
      "round": 1,
      "relaxed_at": "2026-09-01T09:00:00Z",
      "reason": "pool_exhausted"
    }
  ],
  "escalation_bounds": {
    "initial_radius_metres": 0,
    "maximum_radius_metres": 0,
    "step_metres": 1
  },
  "budget_ceiling": {
    "amount": 100000000,
    "currency": "NGN"
  },
  "cancelled_reason": "example",
  "created_at": "2026-09-01T09:00:00Z"
}
GET/v1/allocation/events#

Replay a work order's own history

allocation.events.list

MATCH records what happens on the work orders and assignments it holds, and this reads those events back. Every event type here is one the webhook catalogue declares, so the handler you wrote for a delivery can read a replay too.

Pass stream with the id of the work order or assignment whose history you want, and after the sequence you last handled; you get that record's later events in order. Leave stream out and events from every stream come back together, oldest first.

A sequence counts inside one stream, so a number kept from one record means nothing against another. Send after only with the stream you read it from, and page across streams with starting_after instead.

Read any one of them in full at GET /v1/allocation/events/{event_id}; each entry's id starts with evt_.

Query parameters

stream string optional

The id of the record whose history you want: a work order's from POST /v1/allocation/work_orders or an assignment's from POST /v1/allocation/assignments, with after beside it. Leave it out to read across every stream, paged with starting_after.

after integer optional

The sequence of the last event you handled on this stream, as sequence reads on each event; you get the events after it, and 0 reads the stream from its first event. Needs stream beside it, because a sequence counts within one stream.

limit integer optional

How many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.

starting_after string optional

The id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.

Returns

A page of MATCH's events, oldest first, each naming the record it happened to.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of StoredEvent required

The records on this page, in the order the list promises. Empty when nothing matched.

13 fields of StoredEvent
id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
object always "event" required

Always event. Tells you which kind of record you are looking at, so one handler can read any response.

stream string required

The id of the record this event is about, such as a work order's from POST /v1/allocation/work_orders (match_geo_aware_workforce_orchestrator_work_order_). sequence counts per organisation and per stream, so it means nothing without this beside it.

data object required

The record the event is about, in the shape its type names: a work order, an offer, an assignment, an exception, a proof of service, a supply gap or a cascade removal. The same body a webhook delivery carries.

has_more boolean required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
curl -X GET "https://sandbox.droomwork.io/v1/allocation/events?stream=match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&limit=25" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
const result = await api.allocationEventsList({ stream: 'match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', after: 0, limit: 25 });
// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/events?stream=match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&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.MATCHApi(client)

# query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
result = api.allocation_events_list(stream='match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', after=0, limit=25)
import os

import requests

# query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/allocation/events?stream=match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&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\MATCHApi(new GuzzleHttp\Client(), $config);

# query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
$result = $api->allocationEventsList(stream: 'match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', after: 0, limit: 25);
<?php
// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/events?stream=match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&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.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
var result = api.allocationEventsList("match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", 0, 25, null);
// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/events?stream=match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&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 MATCHApi(config);

// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
var result = api.AllocationEventsList(stream: "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", after: 0, limit: 25);
// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/allocation/events?stream=match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&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: stream (optional), after (optional), limit (optional), starting_after (optional)
result, _, err := client.MATCHAPI.AllocationEventsList(ctx).Stream("match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").After(0).Limit(25).Execute()
// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/allocation/events?stream=match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&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)
Response
{
  "object": "list",
  "data": [
    {
      "id": "evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "type": "run.payslip.calculated",
      "schema_version": 1,
      "org_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "sequence": 0,
      "occurred_at": "2026-09-01T09:00:00Z",
      "livemode": false,
      "mocked": true,
      "source": "anchor",
      "request_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "event",
      "stream": "example",
      "data": {}
    }
  ],
  "has_more": true
}
GET/v1/allocation/events/{event_id}#

Confirm what one event recorded

allocation.events.retrieve

An event is how MATCH records one thing that happened on a work order or an assignment, and this opens one of them on its own. Nothing rewrites an event once it is stored, so an identifier kept in your own log shows the same thing months from now, after the record it was about has moved on.

Send the event's identifier in the path; it starts with evt_. You get that one event, with stream naming the record it belongs to, sequence saying where in that record's history it sits, and data carrying the record as it stood.

To read what followed on that record, send its stream to GET /v1/allocation/events with this event's sequence as after. An identifier nothing holds answers not_found.

Path parameters

event_id string required

The event's identifier, from the id of an event you listed at GET /v1/allocation/events, received on a webhook, or read as settlement_event_id on a proof of service. It starts with evt_.

Returns

The event as it was stored, with the record it was about at that moment.

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
object always "event" required

Always event. Tells you which kind of record you are looking at, so one handler can read any response.

stream string required

The id of the record this event is about, such as a work order's from POST /v1/allocation/work_orders (match_geo_aware_workforce_orchestrator_work_order_). sequence counts per organisation and per stream, so it means nothing without this beside it.

data object required

The record the event is about, in the shape its type names: a work order, an offer, an assignment, an exception, a proof of service, a supply gap or a cascade removal. The same body a webhook delivery carries.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X GET "https://sandbox.droomwork.io/v1/allocation/events/%7Bevent_id%7D" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationEventsRetrieve({ eventId: '{event_id}' });
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/events/%7Bevent_id%7D', {
  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.MATCHApi(client)

result = api.allocation_events_retrieve(event_id='{event_id}')
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/allocation/events/%7Bevent_id%7D',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationEventsRetrieve(event_id: '{event_id}');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/events/%7Bevent_id%7D');
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.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationEventsRetrieve("{event_id}");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/events/%7Bevent_id%7D"))
    .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 MATCHApi(config);

var result = api.AllocationEventsRetrieve(eventId: "{event_id}");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/allocation/events/%7Bevent_id%7D");
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.MATCHAPI.AllocationEventsRetrieve(ctx, "{event_id}").Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/allocation/events/%7Bevent_id%7D", 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)
Response
{
  "id": "evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "type": "run.payslip.calculated",
  "schema_version": 1,
  "org_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "sequence": 0,
  "occurred_at": "2026-09-01T09:00:00Z",
  "livemode": false,
  "mocked": true,
  "source": "anchor",
  "request_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "event",
  "stream": "example",
  "data": {}
}
GET/v1/allocation/audit_entries#

Who dispatched the work, and what we refused

allocation.audit_entries.list

This names who opened a work order, who filled it, who cancelled an assignment, and who we turned down. One row per attempt, so who changed the work is answerable from our own record.

Filter by actor_id to follow one caller, by resource_id to follow one work order or assignment, by outcome for the refusals alone, or by a window of time. Entries come back newest first.

action has to match an entry's stored value exactly. Copy one off an entry you hold; an approximation answers with an empty page.

A read of yours that succeeded leaves no entry. A read that was refused does, and so does every attempt to change something, whether it went through or was turned down.

Read one in full at GET /v1/allocation/audit_entries/{audit_entry_id}.

Query parameters

action string optional

Return only attempts at one method and route pattern, such as POST /v1/allocation/work_orders or POST /v1/allocation/assignments/{assignment_id}/completion, as action reads on each entry. Leave it out to get every action.

actor_id string optional

Return only one actor's attempts, by the id of the API key (key_, from GET /v1/api_keys), person (usr_) or staff account, as actor_id reads on each entry. Pair it with outcome=refused to watch one credential; leave it out for every actor.

resource string optional

Return only attempts on one kind of record, by the collection segment of the route as resource reads on each entry, such as work_orders, assignments or exceptions. Pair it with resource_id to narrow to one record; leave it out to get every kind.

resource_id string optional

Return only attempts on one record, by the id that was in the route, such as a work order's from POST /v1/allocation/work_orders (match_geo_aware_workforce_orchestrator_work_order_). Pair it with resource; leave it out for every record.

outcome string optional

Return only one outcome: succeeded (a 2xx answer), refused (a 4xx, the request was turned down) or failed (a 5xx, it went wrong on our side). Leave it out to get all three.

succeededrefusedfailed
recorded_after string optional

Return only entries recorded after this moment, as an RFC 3339 timestamp in UTC, matched against at on each entry; an entry at exactly this instant is left out. Leave it out to set no lower bound.

recorded_before string optional

Return only entries recorded before this moment, as an RFC 3339 timestamp in UTC, matched against at on each entry; an entry at exactly this instant is left out. Pair it with recorded_after to bound a window, or leave it out to set no upper bound.

limit integer optional

How many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.

starting_after string optional

The id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.

Returns

One page of attempts, newest first, with who acted and how each ended.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of AuditEntry required

The records on this page, in the order the list promises. Empty when nothing matched.

13 fields of AuditEntry
id string required

The entry's identifier. It starts with audit_entry_ and never changes; pass it to GET /v1/allocation/audit_entries/{audit_entry_id} to read this one entry, or as starting_after to page past it.

object always "audit_entry" required

Always audit_entry. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm the action happened in. False is the sandbox.

mocked boolean required

Whether the route this attempt was aimed at is served by a mock. It describes the route and not the answer: a refusal and a replayed idempotent request are both attempts against a mocked route, and the log says so.

at string · date-time required

When the attempt was made, as an RFC 3339 timestamp in UTC. The list is ordered by it, newest first, and recorded_after and recorded_before bound it.

request_id string required

The identifier of the call that made this attempt. It starts with req_ and is the same request_id an error response carries, so a refusal you were shown can be matched to its entry here.

actor_type string required

What kind of caller made the attempt: client for one of your API keys or OAuth clients, user for a signed-in person, staff for a Droomwork staff member on a support case, service for Droomwork acting for you.

actor_id string required

Who made the attempt, as the id of whatever actor_type names: your API key (key_, listed at GET /v1/api_keys) or the OAuth client_id, the person (usr_), the staff member, or the service acting for you. Filter the list by it with actor_id.

action string required

What was attempted, as the method and the route pattern.

resource string · nullable optional

The kind of record the attempt was aimed at, as the collection in the route, such as work_orders or assignments. null when the route names none.

resource_id string · nullable optional

The id of the record the route named, such as a work order's from POST /v1/allocation/work_orders (match_geo_aware_workforce_orchestrator_work_order_). null for an attempt on a collection, such as creating or listing.

outcome string required

How the attempt ended: succeeded for a 2xx answer, refused for a 4xx, failed for a 5xx on our side. Refusals are recorded too: filter GET /v1/allocation/audit_entries by actor_id and outcome=refused to watch one credential.

succeededrefusedfailed
status integer required

The HTTP status the caller was given, such as 201 or 403. outcome is read from it: 2xx is succeeded, 4xx refused, 5xx failed.

has_more boolean required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
curl -X GET "https://sandbox.droomwork.io/v1/allocation/audit_entries?action=POST%20%2Fv1%2Fallocation%2Fwork_orders&actor_id=usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=work_orders&resource_id=match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&limit=25" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
const result = await api.allocationAuditEntriesList({ action: 'POST /v1/allocation/work_orders', actorId: 'usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', resource: 'work_orders', resourceId: 'match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', outcome: 'succeeded', recordedAfter: '2026-01-01T00:00:00Z', recordedBefore: '2027-01-01T00:00:00Z', limit: 25 });
// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/audit_entries?action=POST%20%2Fv1%2Fallocation%2Fwork_orders&actor_id=usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=work_orders&resource_id=match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&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.MATCHApi(client)

# query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
result = api.allocation_audit_entries_list(action='POST /v1/allocation/work_orders', actor_id='usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', resource='work_orders', resource_id='match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', outcome='succeeded', recorded_after='2026-01-01T00:00:00Z', recorded_before='2027-01-01T00:00:00Z', limit=25)
import os

import requests

# query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/allocation/audit_entries?action=POST%20%2Fv1%2Fallocation%2Fwork_orders&actor_id=usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=work_orders&resource_id=match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&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\MATCHApi(new GuzzleHttp\Client(), $config);

# query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
$result = $api->allocationAuditEntriesList(action: 'POST /v1/allocation/work_orders', actor_id: 'usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', resource: 'work_orders', resource_id: 'match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', outcome: 'succeeded', recorded_after: '2026-01-01T00:00:00Z', recorded_before: '2027-01-01T00:00:00Z', limit: 25);
<?php
// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/audit_entries?action=POST%20%2Fv1%2Fallocation%2Fwork_orders&actor_id=usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=work_orders&resource_id=match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&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.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
var result = api.allocationAuditEntriesList("POST /v1/allocation/work_orders", "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "work_orders", "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "succeeded", "2026-01-01T00:00:00Z", "2027-01-01T00:00:00Z", 25, null);
// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/audit_entries?action=POST%20%2Fv1%2Fallocation%2Fwork_orders&actor_id=usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=work_orders&resource_id=match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&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 MATCHApi(config);

// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
var result = api.AllocationAuditEntriesList(action: "POST /v1/allocation/work_orders", actorId: "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", resource: "work_orders", resourceId: "match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", outcome: "succeeded", recordedAfter: "2026-01-01T00:00:00Z", recordedBefore: "2027-01-01T00:00:00Z", limit: 25);
// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/allocation/audit_entries?action=POST%20%2Fv1%2Fallocation%2Fwork_orders&actor_id=usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=work_orders&resource_id=match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&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: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
result, _, err := client.MATCHAPI.AllocationAuditEntriesList(ctx).Action("POST /v1/allocation/work_orders").ActorId("usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Resource("work_orders").ResourceId("match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Outcome("succeeded").RecordedAfter("2026-01-01T00:00:00Z").RecordedBefore("2027-01-01T00:00:00Z").Limit(25).Execute()
// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/allocation/audit_entries?action=POST%20%2Fv1%2Fallocation%2Fwork_orders&actor_id=usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=work_orders&resource_id=match_geo_aware_workforce_orchestrator_work_order_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&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)
Response
{
  "object": "list",
  "data": [
    {
      "id": "audit_entry_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "audit_entry",
      "livemode": false,
      "mocked": true,
      "at": "2026-09-01T09:00:00Z",
      "request_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "actor_type": "example",
      "actor_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "action": "example",
      "outcome": "succeeded",
      "status": 1,
      "resource": "work_orders",
      "resource_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
    }
  ],
  "has_more": true
}
GET/v1/allocation/audit_entries/{audit_entry_id}#

Trace one attempt on the work

allocation.audit_entries.retrieve

One entry is one attempt on one record, and this opens it in full. The identifier never changes, so your own copy of an assignment can carry it and who cancelled that assignment stays answerable long afterwards.

Send the audit_entry_id in the path and one entry comes back: the moment, the caller behind it, what they attempted, the record it was aimed at, and the status they were answered with.

An attempt aimed at a collection, as opening a work order is, leaves resource_id null. Check it before you follow it.

The reason behind a refusal is not on an entry, and neither is anything the change did; read GET /v1/allocation/events for that.

Where resource_id names an assignment, open it at GET /v1/allocation/assignments/{assignment_id}. An identifier with no entry behind it answers not_found.

Path parameters

audit_entry_id string required

The entry's identifier, from the id of an entry you listed at GET /v1/allocation/audit_entries. It starts with audit_entry_, and you never create one: an entry is recorded for every attempt to change something and every refusal.

Returns

One attempt in full, naming who made it, what they aimed at, and how it ended.

id string required

The entry's identifier. It starts with audit_entry_ and never changes; pass it to GET /v1/allocation/audit_entries/{audit_entry_id} to read this one entry, or as starting_after to page past it.

object always "audit_entry" required

Always audit_entry. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm the action happened in. False is the sandbox.

mocked boolean required

Whether the route this attempt was aimed at is served by a mock. It describes the route and not the answer: a refusal and a replayed idempotent request are both attempts against a mocked route, and the log says so.

at string · date-time required

When the attempt was made, as an RFC 3339 timestamp in UTC. The list is ordered by it, newest first, and recorded_after and recorded_before bound it.

request_id string required

The identifier of the call that made this attempt. It starts with req_ and is the same request_id an error response carries, so a refusal you were shown can be matched to its entry here.

actor_type string required

What kind of caller made the attempt: client for one of your API keys or OAuth clients, user for a signed-in person, staff for a Droomwork staff member on a support case, service for Droomwork acting for you.

actor_id string required

Who made the attempt, as the id of whatever actor_type names: your API key (key_, listed at GET /v1/api_keys) or the OAuth client_id, the person (usr_), the staff member, or the service acting for you. Filter the list by it with actor_id.

action string required

What was attempted, as the method and the route pattern.

resource string · nullable optional

The kind of record the attempt was aimed at, as the collection in the route, such as work_orders or assignments. null when the route names none.

resource_id string · nullable optional

The id of the record the route named, such as a work order's from POST /v1/allocation/work_orders (match_geo_aware_workforce_orchestrator_work_order_). null for an attempt on a collection, such as creating or listing.

outcome string required

How the attempt ended: succeeded for a 2xx answer, refused for a 4xx, failed for a 5xx on our side. Refusals are recorded too: filter GET /v1/allocation/audit_entries by actor_id and outcome=refused to watch one credential.

succeededrefusedfailed
status integer required

The HTTP status the caller was given, such as 201 or 403. outcome is read from it: 2xx is succeeded, 4xx refused, 5xx failed.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X GET "https://sandbox.droomwork.io/v1/allocation/audit_entries/%7Baudit_entry_id%7D" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, MATCHApi } from '@droomwork/sdk';

const api = new MATCHApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));

const result = await api.allocationAuditEntriesRetrieve({ auditEntryId: '{audit_entry_id}' });
const response = await fetch('https://sandbox.droomwork.io/v1/allocation/audit_entries/%7Baudit_entry_id%7D', {
  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.MATCHApi(client)

result = api.allocation_audit_entries_retrieve(audit_entry_id='{audit_entry_id}')
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/allocation/audit_entries/%7Baudit_entry_id%7D',
    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\MATCHApi(new GuzzleHttp\Client(), $config);

$result = $api->allocationAuditEntriesRetrieve(audit_entry_id: '{audit_entry_id}');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/allocation/audit_entries/%7Baudit_entry_id%7D');
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.MatchApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
MatchApi api = new MatchApi(client);

var result = api.allocationAuditEntriesRetrieve("{audit_entry_id}");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/allocation/audit_entries/%7Baudit_entry_id%7D"))
    .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 MATCHApi(config);

var result = api.AllocationAuditEntriesRetrieve(auditEntryId: "{audit_entry_id}");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/allocation/audit_entries/%7Baudit_entry_id%7D");
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.MATCHAPI.AllocationAuditEntriesRetrieve(ctx, "{audit_entry_id}").Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/allocation/audit_entries/%7Baudit_entry_id%7D", 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)
Response
{
  "id": "audit_entry_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "audit_entry",
  "livemode": false,
  "mocked": true,
  "at": "2026-09-01T09:00:00Z",
  "request_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "actor_type": "example",
  "actor_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "action": "example",
  "outcome": "succeeded",
  "status": 1,
  "resource": "work_orders",
  "resource_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}