HHeros Developers

Version 1.0.0

Heros FLOW ENTERPRISE

Sourcing and screened leads across Nigerian hiring channels.

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

FLOW gets one vacancy in front of candidates: send us a requisition, we broadcast it to every channel you've configured, and you get back leads who have already answered your knockout questions.

What you should know before you start

This is not a job board. There is no destination candidates visit. Your requisition goes out into other people's channels, and the Heros talent pool is one of those channels, not a special case.

Nothing broadcasts on a guess. Where a field was inferred and not stated, it comes back for you to confirm. Compensation, headcount, location and start date never go out on inference alone, and publishing is refused while any of the four is unconfirmed.

Channel copy cannot invent a fact. Pay figures, employer legal names, statutory claims and requirements in your channel copy are compared against the requisition, exactly. A mismatch fails the render and is logged. Nothing reaches a channel saying something the requisition did not.

Screening is a conversation. The outcome is a rule. Candidates answer your knockout questions conversationally, but pass and fail come from versioned deterministic rules. The screening record shows what was extracted from each answer and what the rules decided as two separate things, so you can see which one you disagree with.

A spend ceiling blocks before anything is computed. When a broadcast would exceed a ceiling, it is refused, and no charge is calculated or debited.

A requisition is versioned, and every lead remembers which version made it. An edit after first broadcast creates a new version. A lead stays bound to the version that produced it, so you can reproduce a shortlist.

Getting started

Create a requisition. Confirm anything that was inferred. Publish it. Read the leads.

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?

GET/v1/sourcing/requisitions#

Every role you are hiring for

sourcing.requisitions.list

This is every requisition your organisation holds, whatever state each one has reached. A hiring board in your product is drawn from this single read, so a screen showing what is open, what is waiting on confirmation and what has closed keeps no record of its own.

Send no request body and the requisitions come back, each one whole: the role, the headcount, where it stands and which version it is on.

Read one in full at GET /v1/sourcing/requisitions/{requisition_id}, passing a row's id as requisition_id.

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

Only requisitions in one state: draft, pending_confirmation (an inferred field awaits confirmation), under_review (a contested case, with a reviewer), quarantined, published or closed. Leave it out to get every state.

pending_confirmationdraftunder_reviewquarantinedpublishedclosed

Returns

Your requisitions, each one whole, enough to draw a hiring board from.

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 Requisition required

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

16 fields of Requisition
id string required

The requisition's identifier, starting with flow_enterprise_requisition_; it never changes. You get it from POST /v1/sourcing/requisitions (or in requisition_ids on a bulk upload) and pass it as requisition_id on every call about it.

object always "requisition" required

Always requisition. 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

A requisition sits in pending_confirmation while an inferred pay figure, headcount, location or start date is unconfirmed. It cannot leave that state until you confirm.

pending_confirmationdraftunder_reviewquarantinedpublishedclosed
version integer · minimum 1 required

An edit after first broadcast makes a new version. Leads stay on theirs.

job_title string required

The title of the role as you gave it. The occupation code is inferred from it when you don't send one, so a local or colloquial title is fine.

occupation_code string optional

The occupation this role maps to in the taxonomy, such as ng-7412. Inferred from the title when you didn't send one, and listed in inferred_fields until you confirm it.

employment_type string optional
permanentfixed_termcontractcasualapprenticeship
headcount integer · minimum 1 required

How many people you are hiring for this role, at least 1. It never broadcasts on inference alone; confirm it if it appears in inferred_fields.

compensation Compensation optional
3 fields of Compensation
amount Money required
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.

period string required

The span the amount pays for: monthly, annual, daily or per_task (one payment for each task done). Required whenever you send compensation.

monthlyannualdailyper_task
negotiable boolean optional

Whether the figure is open to negotiation: true when it is, false when it is fixed.

location Location optional
3 fields of Location
description string required

Where the work is, in words a candidate would recognise, such as Ikeja, Lagos. The one part of a location you must give.

state string optional

The Nigerian state the work is in, such as Lagos. Optional; the description alone is enough to create the requisition.

geofence_radius_metres integer · nullable optional

A radius around the location, in whole metres, or null when there is none. If it had to be inferred it comes back in inferred_fields for you to confirm.

starts_on string · date · nullable optional

The date the role starts, as YYYY-MM-DD, or null when none is set. It never broadcasts on inference alone; confirm it if it appears in inferred_fields.

inferred_fields array of string optional

Fields that were inferred rather than read from what you sent. Each has to be confirmed, and compensation, headcount, location and start date can never broadcast while they are here.

quarantine one of optional

Why the requisition was held: the rule it tripped, the rule pack version, the detail and the offending text. null when nothing held it; fix what it names before you publish.

QuarantineFindingor
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.

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)
curl -X GET "https://sandbox.droomwork.io/v1/sourcing/requisitions?limit=25&status=pending_confirmation" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

const api = new FLOWApi(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.sourcingRequisitionsList({ limit: 25, status: 'pending_confirmation' });
// query parameters: limit (optional), starting_after (optional), status (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/requisitions?limit=25&status=pending_confirmation', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

# query parameters: limit (optional), starting_after (optional), status (optional)
result = api.sourcing_requisitions_list(limit=25, status='pending_confirmation')
import os

import requests

# query parameters: limit (optional), starting_after (optional), status (optional)
response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/requisitions?limit=25&status=pending_confirmation',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

# query parameters: limit (optional), starting_after (optional), status (optional)
$result = $api->sourcingRequisitionsList(limit: 25, status: 'pending_confirmation');
<?php
// query parameters: limit (optional), starting_after (optional), status (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/requisitions?limit=25&status=pending_confirmation');
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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;
import com.useheros.sdk.model.*;

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

// query parameters: limit (optional), starting_after (optional), status (optional)
var result = api.sourcingRequisitionsList(25, null, FlowRequisitionStatus.fromValue("pending_confirmation"));
// query parameters: limit (optional), starting_after (optional), status (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/requisitions?limit=25&status=pending_confirmation"))
    .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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

// query parameters: limit (optional), starting_after (optional), status (optional)
var result = api.SourcingRequisitionsList(limit: 25, status: FlowRequisitionStatus.PendingConfirmation);
// 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/sourcing/requisitions?limit=25&status=pending_confirmation");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();
import heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

// query parameters: limit (optional), starting_after (optional), status (optional)
result, _, err := client.FLOWAPI.SourcingRequisitionsList(ctx).Limit(25).Status(heros.FlowRequisitionStatus("pending_confirmation")).Execute()
// query parameters: limit (optional), starting_after (optional), status (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/requisitions?limit=25&status=pending_confirmation", 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": "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "requisition",
      "livemode": false,
      "mocked": true,
      "status": "pending_confirmation",
      "version": 1,
      "job_title": "example",
      "headcount": 1,
      "occupation_code": "ng-7412",
      "employment_type": "permanent",
      "compensation": {
        "amount": {
          "amount": 7500000,
          "currency": "NGN"
        },
        "period": "monthly",
        "negotiable": true
      },
      "location": {
        "description": "example",
        "state": "Lagos",
        "geofence_radius_metres": 1
      },
      "starts_on": "2026-09-01",
      "inferred_fields": [
        "example"
      ],
      "quarantine": {
        "rule_code": "discriminatory_criteria",
        "rule_pack_version": "2026.08.1",
        "detail": "The payee has no verified destination, so this line cannot be paid.",
        "contested": true,
        "offending_text": "example"
      },
      "created_at": "2026-09-01T09:00:00Z"
    }
  ],
  "has_more": true
}
POST/v1/sourcing/requisitions#

Open a role you want to fill

sourcing.requisitions.create

This opens a role you are hiring for. Everything that follows for that hire hangs off the identifier it hands back: the adverts, the broadcasts, the screening questions and every lead. So your own system keeps one string per vacancy and can ask us about anything that happened to it.

A job title and a headcount are enough to open one. You get the whole requisition back, with an id starting flow_enterprise_requisition_ to keep.

Nothing has to exist first. One requisition is one role, so four hires of the same job are one call with headcount set to four and not four calls.

Carry the id forward as requisition_id, to POST /v1/sourcing/requisitions/{requisition_id}/confirm_inferences and then POST /v1/sourcing/requisitions/{requisition_id}/publish.

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

job_title string required

The title of the role. Any title people use locally works: it is mapped to an occupation code, and the code comes back for you to confirm if you didn't send one.

occupation_code string optional

Omit and it is inferred from the title, then returned for confirmation.

employment_type string optional
permanentfixed_termcontractcasualapprenticeship
headcount integer · minimum 1 required

How many people you are hiring for this role, at least 1. One of the four fields that never broadcast on inference alone.

compensation Compensation optional
3 fields of Compensation
amount Money required
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.

period string required

The span the amount pays for: monthly, annual, daily or per_task (one payment for each task done). Required whenever you send compensation.

monthlyannualdailyper_task
negotiable boolean optional

Whether the figure is open to negotiation: true when it is, false when it is fixed.

location Location optional
3 fields of Location
description string required

Where the work is, in words a candidate would recognise, such as Ikeja, Lagos. The one part of a location you must give.

state string optional

The Nigerian state the work is in, such as Lagos. Optional; the description alone is enough to create the requisition.

geofence_radius_metres integer · nullable optional

A radius around the location, in whole metres, or null when there is none. If it had to be inferred it comes back in inferred_fields for you to confirm.

starts_on string · date optional

The date the role starts, as YYYY-MM-DD. If it has to be inferred it comes back in inferred_fields, and it can't broadcast until you confirm it.

knockout_criteria array of KnockoutRuleInput optional

The questions every candidate must answer and the rule that decides pass or fail for each. Asked conversationally; judged by the rule exactly as you wrote it.

2 fields of KnockoutRuleInput
question string required

What the candidate is asked. It may be phrased differently in the conversation.

rule string required

The deterministic test applied to the extracted answer.

description string optional

The role in your own words: duties, requirements, anything a candidate should know. Channel copy is drawn from it, and no copy may claim what the requisition doesn't.

Returns

The new requisition, carrying the identifier every later call about this role needs.

id string required

The requisition's identifier, starting with flow_enterprise_requisition_; it never changes. You get it from POST /v1/sourcing/requisitions (or in requisition_ids on a bulk upload) and pass it as requisition_id on every call about it.

object always "requisition" required

Always requisition. 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

A requisition sits in pending_confirmation while an inferred pay figure, headcount, location or start date is unconfirmed. It cannot leave that state until you confirm.

pending_confirmationdraftunder_reviewquarantinedpublishedclosed
version integer · minimum 1 required

An edit after first broadcast makes a new version. Leads stay on theirs.

job_title string required

The title of the role as you gave it. The occupation code is inferred from it when you don't send one, so a local or colloquial title is fine.

occupation_code string optional

The occupation this role maps to in the taxonomy, such as ng-7412. Inferred from the title when you didn't send one, and listed in inferred_fields until you confirm it.

employment_type string optional
permanentfixed_termcontractcasualapprenticeship
headcount integer · minimum 1 required

How many people you are hiring for this role, at least 1. It never broadcasts on inference alone; confirm it if it appears in inferred_fields.

compensation Compensation optional
3 fields of Compensation
amount Money required
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.

period string required

The span the amount pays for: monthly, annual, daily or per_task (one payment for each task done). Required whenever you send compensation.

monthlyannualdailyper_task
negotiable boolean optional

Whether the figure is open to negotiation: true when it is, false when it is fixed.

location Location optional
3 fields of Location
description string required

Where the work is, in words a candidate would recognise, such as Ikeja, Lagos. The one part of a location you must give.

state string optional

The Nigerian state the work is in, such as Lagos. Optional; the description alone is enough to create the requisition.

geofence_radius_metres integer · nullable optional

A radius around the location, in whole metres, or null when there is none. If it had to be inferred it comes back in inferred_fields for you to confirm.

starts_on string · date · nullable optional

The date the role starts, as YYYY-MM-DD, or null when none is set. It never broadcasts on inference alone; confirm it if it appears in inferred_fields.

inferred_fields array of string optional

Fields that were inferred rather than read from what you sent. Each has to be confirmed, and compensation, headcount, location and start date can never broadcast while they are here.

quarantine one of optional

Why the requisition was held: the rule it tripped, the rule pack version, the detail and the offending text. null when nothing held it; fix what it names before you publish.

QuarantineFindingor
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/sourcing/requisitions" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"job_title":"example","headcount":1,"occupation_code":"ng-7412","employment_type":"permanent","compensation":{"amount":{"amount":7500000,"currency":"NGN"},"period":"monthly","negotiable":true},"location":{"description":"example","state":"Lagos","geofence_radius_metres":1},"starts_on":"2026-09-01","knockout_criteria":[{"question":"example","rule":"years_experience >= 3"}],"description":"example"}'
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingRequisitionsCreate({
  idempotencyKey: crypto.randomUUID(),
  flowRequisitionCreateRequest: {"jobTitle":"example","headcount":1,"occupationCode":"ng-7412","employmentType":"permanent","compensation":{"amount":{"amount":7500000,"currency":"NGN"},"period":"monthly","negotiable":true},"location":{"description":"example","state":"Lagos","geofenceRadiusMetres":1},"startsOn":"2026-09-01","knockoutCriteria":[{"question":"example","rule":"years_experience >= 3"}],"description":"example"},
});
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/requisitions', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    "job_title": "example",
    "headcount": 1,
    "occupation_code": "ng-7412",
    "employment_type": "permanent",
    "compensation": {
      "amount": {
        "amount": 7500000,
        "currency": "NGN"
      },
      "period": "monthly",
      "negotiable": true
    },
    "location": {
      "description": "example",
      "state": "Lagos",
      "geofence_radius_metres": 1
    },
    "starts_on": "2026-09-01",
    "knockout_criteria": [
      {
        "question": "example",
        "rule": "years_experience >= 3"
      }
    ],
    "description": "example"
  }),
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_requisitions_create(body={"job_title": "example", "headcount": 1, "occupation_code": "ng-7412", "employment_type": "permanent", "compensation": {"amount": {"amount": 7500000, "currency": "NGN"}, "period": "monthly", "negotiable": True}, "location": {"description": "example", "state": "Lagos", "geofence_radius_metres": 1}, "starts_on": "2026-09-01", "knockout_criteria": [{"question": "example", "rule": "years_experience >= 3"}], "description": "example"})
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/sourcing/requisitions',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
        'Idempotency-Key': str(uuid.uuid4()),
    },
    json={"job_title": "example", "headcount": 1, "occupation_code": "ng-7412", "employment_type": "permanent", "compensation": {"amount": {"amount": 7500000, "currency": "NGN"}, "period": "monthly", "negotiable": True}, "location": {"description": "example", "state": "Lagos", "geofence_radius_metres": 1}, "starts_on": "2026-09-01", "knockout_criteria": [{"question": "example", "rule": "years_experience >= 3"}], "description": "example"},
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingRequisitionsCreate($idempotencyKey, json_decode('{"job_title":"example","headcount":1,"occupation_code":"ng-7412","employment_type":"permanent","compensation":{"amount":{"amount":7500000,"currency":"NGN"},"period":"monthly","negotiable":true},"location":{"description":"example","state":"Lagos","geofence_radius_metres":1},"starts_on":"2026-09-01","knockout_criteria":[{"question":"example","rule":"years_experience >= 3"}],"description":"example"}', true));
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/requisitions');
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 => '{"job_title":"example","headcount":1,"occupation_code":"ng-7412","employment_type":"permanent","compensation":{"amount":{"amount":7500000,"currency":"NGN"},"period":"monthly","negotiable":true},"location":{"description":"example","state":"Lagos","geofence_radius_metres":1},"starts_on":"2026-09-01","knockout_criteria":[{"question":"example","rule":"years_experience >= 3"}],"description":"example"}',
]);
$result = json_decode(curl_exec($ch), true);
import com.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingRequisitionsCreate(idempotencyKey, body);
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/requisitions"))
    .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("""
        {
          "job_title": "example",
          "headcount": 1,
          "occupation_code": "ng-7412",
          "employment_type": "permanent",
          "compensation": {
            "amount": {
              "amount": 7500000,
              "currency": "NGN"
            },
            "period": "monthly",
            "negotiable": true
          },
          "location": {
            "description": "example",
            "state": "Lagos",
            "geofence_radius_metres": 1
          },
          "starts_on": "2026-09-01",
          "knockout_criteria": [
            {
              "question": "example",
              "rule": "years_experience >= 3"
            }
          ],
          "description": "example"
        }
        """))
    .build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();
using Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingRequisitionsCreate(idempotencyKey, body);
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/sourcing/requisitions");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
    {
      "job_title": "example",
      "headcount": 1,
      "occupation_code": "ng-7412",
      "employment_type": "permanent",
      "compensation": {
        "amount": {
          "amount": 7500000,
          "currency": "NGN"
        },
        "period": "monthly",
        "negotiable": true
      },
      "location": {
        "description": "example",
        "state": "Lagos",
        "geofence_radius_metres": 1
      },
      "starts_on": "2026-09-01",
      "knockout_criteria": [
        {
          "question": "example",
          "rule": "years_experience >= 3"
        }
      ],
      "description": "example"
    }
    """, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();
import heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingRequisitionsCreate(ctx).IdempotencyKey(key).FlowRequisitionCreateRequest(body).Execute()
body := strings.NewReader(`{
  "job_title": "example",
  "headcount": 1,
  "occupation_code": "ng-7412",
  "employment_type": "permanent",
  "compensation": {
    "amount": {
      "amount": 7500000,
      "currency": "NGN"
    },
    "period": "monthly",
    "negotiable": true
  },
  "location": {
    "description": "example",
    "state": "Lagos",
    "geofence_radius_metres": 1
  },
  "starts_on": "2026-09-01",
  "knockout_criteria": [
    {
      "question": "example",
      "rule": "years_experience >= 3"
    }
  ],
  "description": "example"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/sourcing/requisitions", 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": "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "requisition",
  "livemode": false,
  "mocked": true,
  "status": "pending_confirmation",
  "version": 1,
  "job_title": "example",
  "headcount": 1,
  "occupation_code": "ng-7412",
  "employment_type": "permanent",
  "compensation": {
    "amount": {
      "amount": 7500000,
      "currency": "NGN"
    },
    "period": "monthly",
    "negotiable": true
  },
  "location": {
    "description": "example",
    "state": "Lagos",
    "geofence_radius_metres": 1
  },
  "starts_on": "2026-09-01",
  "inferred_fields": [
    "example"
  ],
  "quarantine": {
    "rule_code": "discriminatory_criteria",
    "rule_pack_version": "2026.08.1",
    "detail": "The payee has no verified destination, so this line cannot be paid.",
    "contested": true,
    "offending_text": "example"
  },
  "created_at": "2026-09-01T09:00:00Z"
}
GET/v1/sourcing/requisitions/{requisition_id}#

Check one role as it stands

sourcing.requisitions.retrieve

This answers with one role in full as Heros holds it: what it pays, where the work is, and what stage it has reached. So a page in your product that opens a single vacancy needs this call and nothing else, and code about to act on a role can check where it stands first.

Send the requisition's identifier in the path. The whole record comes back, the same object a page of the list is made of.

Path parameters

requisition_id string required

The requisition's identifier, starting with flow_enterprise_requisition_: the id returned by POST /v1/sourcing/requisitions or listed at GET /v1/sourcing/requisitions, or one of the requisition_ids on a bulk upload.

Query parameters

version integer optional

Which version of the requisition to read, from 1 upward. Leave it out for the current one; pass a lead's requisition_version (from GET /v1/sourcing/leads/{lead_id}) or a broadcast's to read the version that produced it.

Returns

The role as we hold it, in full, with the stage it has reached.

id string required

The requisition's identifier, starting with flow_enterprise_requisition_; it never changes. You get it from POST /v1/sourcing/requisitions (or in requisition_ids on a bulk upload) and pass it as requisition_id on every call about it.

object always "requisition" required

Always requisition. 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

A requisition sits in pending_confirmation while an inferred pay figure, headcount, location or start date is unconfirmed. It cannot leave that state until you confirm.

pending_confirmationdraftunder_reviewquarantinedpublishedclosed
version integer · minimum 1 required

An edit after first broadcast makes a new version. Leads stay on theirs.

job_title string required

The title of the role as you gave it. The occupation code is inferred from it when you don't send one, so a local or colloquial title is fine.

occupation_code string optional

The occupation this role maps to in the taxonomy, such as ng-7412. Inferred from the title when you didn't send one, and listed in inferred_fields until you confirm it.

employment_type string optional
permanentfixed_termcontractcasualapprenticeship
headcount integer · minimum 1 required

How many people you are hiring for this role, at least 1. It never broadcasts on inference alone; confirm it if it appears in inferred_fields.

compensation Compensation optional
3 fields of Compensation
amount Money required
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.

period string required

The span the amount pays for: monthly, annual, daily or per_task (one payment for each task done). Required whenever you send compensation.

monthlyannualdailyper_task
negotiable boolean optional

Whether the figure is open to negotiation: true when it is, false when it is fixed.

location Location optional
3 fields of Location
description string required

Where the work is, in words a candidate would recognise, such as Ikeja, Lagos. The one part of a location you must give.

state string optional

The Nigerian state the work is in, such as Lagos. Optional; the description alone is enough to create the requisition.

geofence_radius_metres integer · nullable optional

A radius around the location, in whole metres, or null when there is none. If it had to be inferred it comes back in inferred_fields for you to confirm.

starts_on string · date · nullable optional

The date the role starts, as YYYY-MM-DD, or null when none is set. It never broadcasts on inference alone; confirm it if it appears in inferred_fields.

inferred_fields array of string optional

Fields that were inferred rather than read from what you sent. Each has to be confirmed, and compensation, headcount, location and start date can never broadcast while they are here.

quarantine one of optional

Why the requisition was held: the rule it tripped, the rule pack version, the detail and the offending text. null when nothing held it; fix what it names before you publish.

QuarantineFindingor
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?
# query parameters: version (optional)
curl -X GET "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z?version=1" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

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

// query parameters: version (optional)
const result = await api.sourcingRequisitionsRetrieve({ requisitionId: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', version: 1 });
// query parameters: version (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z?version=1', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

# query parameters: version (optional)
result = api.sourcing_requisitions_retrieve(requisition_id='flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', version=1)
import os

import requests

# query parameters: version (optional)
response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z?version=1',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

# query parameters: version (optional)
$result = $api->sourcingRequisitionsRetrieve(requisition_id: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', version: 1);
<?php
// query parameters: version (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z?version=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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

// query parameters: version (optional)
var result = api.sourcingRequisitionsRetrieve("flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", 1);
// query parameters: version (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z?version=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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

// query parameters: version (optional)
var result = api.SourcingRequisitionsRetrieve(requisitionId: "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", version: 1);
// query parameters: version (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z?version=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 heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

// query parameters: version (optional)
result, _, err := client.FLOWAPI.SourcingRequisitionsRetrieve(ctx, "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Version(1).Execute()
// query parameters: version (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z?version=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
{
  "id": "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "requisition",
  "livemode": false,
  "mocked": true,
  "status": "pending_confirmation",
  "version": 1,
  "job_title": "example",
  "headcount": 1,
  "occupation_code": "ng-7412",
  "employment_type": "permanent",
  "compensation": {
    "amount": {
      "amount": 7500000,
      "currency": "NGN"
    },
    "period": "monthly",
    "negotiable": true
  },
  "location": {
    "description": "example",
    "state": "Lagos",
    "geofence_radius_metres": 1
  },
  "starts_on": "2026-09-01",
  "inferred_fields": [
    "example"
  ],
  "quarantine": {
    "rule_code": "discriminatory_criteria",
    "rule_pack_version": "2026.08.1",
    "detail": "The payee has no verified destination, so this line cannot be paid.",
    "contested": true,
    "offending_text": "example"
  },
  "created_at": "2026-09-01T09:00:00Z"
}
POST/v1/sourcing/requisitions/{requisition_id}/confirm_inferences#

Confirm the fields that were inferred

sourcing.requisitions.confirm_inferences

This is the sign-off on everything FLOW worked out for itself on a role, and it is the step that turns a new requisition into one you can publish. So your product can put one approval screen in front of a hiring manager and take every answer they give in a single request.

Send the requisition's id in the path and your confirmations in the body. The requisition comes back in draft, no longer pending_confirmation.

The role has to be one still waiting on this, from POST /v1/sourcing/requisitions. Answer every outstanding field in this one call: confirmations takes as many entries as you need, and a second call on the same role answers conflict.

Then publish at POST /v1/sourcing/requisitions/{requisition_id}/publish.

Path parameters

requisition_id string required

The requisition's identifier, starting with flow_enterprise_requisition_: the id returned by POST /v1/sourcing/requisitions or listed at GET /v1/sourcing/requisitions, or one of the requisition_ids on a bulk upload.

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

confirmations array of object required

One entry for each field you are confirming: the field's name, whether you accept the inferred value, and the corrected value when you don't. At least one entry.

3 fields
field string required

The name of the inferred field you are answering for, exactly as it appears in the requisition's inferred_fields.

accepted boolean required

true to accept the inferred value as it stands, false to reject it. When you reject, send the right value in corrected_value.

corrected_value string optional

Supply this when the inference was wrong.

Returns

The requisition as it now stands, ready for you to publish.

id string required

The requisition's identifier, starting with flow_enterprise_requisition_; it never changes. You get it from POST /v1/sourcing/requisitions (or in requisition_ids on a bulk upload) and pass it as requisition_id on every call about it.

object always "requisition" required

Always requisition. 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

A requisition sits in pending_confirmation while an inferred pay figure, headcount, location or start date is unconfirmed. It cannot leave that state until you confirm.

pending_confirmationdraftunder_reviewquarantinedpublishedclosed
version integer · minimum 1 required

An edit after first broadcast makes a new version. Leads stay on theirs.

job_title string required

The title of the role as you gave it. The occupation code is inferred from it when you don't send one, so a local or colloquial title is fine.

occupation_code string optional

The occupation this role maps to in the taxonomy, such as ng-7412. Inferred from the title when you didn't send one, and listed in inferred_fields until you confirm it.

employment_type string optional
permanentfixed_termcontractcasualapprenticeship
headcount integer · minimum 1 required

How many people you are hiring for this role, at least 1. It never broadcasts on inference alone; confirm it if it appears in inferred_fields.

compensation Compensation optional
3 fields of Compensation
amount Money required
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.

period string required

The span the amount pays for: monthly, annual, daily or per_task (one payment for each task done). Required whenever you send compensation.

monthlyannualdailyper_task
negotiable boolean optional

Whether the figure is open to negotiation: true when it is, false when it is fixed.

location Location optional
3 fields of Location
description string required

Where the work is, in words a candidate would recognise, such as Ikeja, Lagos. The one part of a location you must give.

state string optional

The Nigerian state the work is in, such as Lagos. Optional; the description alone is enough to create the requisition.

geofence_radius_metres integer · nullable optional

A radius around the location, in whole metres, or null when there is none. If it had to be inferred it comes back in inferred_fields for you to confirm.

starts_on string · date · nullable optional

The date the role starts, as YYYY-MM-DD, or null when none is set. It never broadcasts on inference alone; confirm it if it appears in inferred_fields.

inferred_fields array of string optional

Fields that were inferred rather than read from what you sent. Each has to be confirmed, and compensation, headcount, location and start date can never broadcast while they are here.

quarantine one of optional

Why the requisition was held: the rule it tripped, the rule pack version, the detail and the offending text. null when nothing held it; fix what it names before you publish.

QuarantineFindingor
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/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/confirm_inferences" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"confirmations":[{"field":"gross.amount","accepted":true,"corrected_value":"example"}]}'
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingRequisitionsConfirmInferences({
  requisitionId: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
  idempotencyKey: crypto.randomUUID(),
  flowConfirmInferencesRequest: {"confirmations":[{"field":"gross.amount","accepted":true,"correctedValue":"example"}]},
});
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/confirm_inferences', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    "confirmations": [
      {
        "field": "gross.amount",
        "accepted": true,
        "corrected_value": "example"
      }
    ]
  }),
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_requisitions_confirm_inferences(requisition_id='flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', body={"confirmations": [{"field": "gross.amount", "accepted": True, "corrected_value": "example"}]})
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/confirm_inferences',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
        'Idempotency-Key': str(uuid.uuid4()),
    },
    json={"confirmations": [{"field": "gross.amount", "accepted": True, "corrected_value": "example"}]},
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingRequisitionsConfirmInferences($idempotencyKey, json_decode('{"confirmations":[{"field":"gross.amount","accepted":true,"corrected_value":"example"}]}', true));
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/confirm_inferences');
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 => '{"confirmations":[{"field":"gross.amount","accepted":true,"corrected_value":"example"}]}',
]);
$result = json_decode(curl_exec($ch), true);
import com.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingRequisitionsConfirmInferences("flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/confirm_inferences"))
    .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("""
        {
          "confirmations": [
            {
              "field": "gross.amount",
              "accepted": true,
              "corrected_value": "example"
            }
          ]
        }
        """))
    .build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();
using Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingRequisitionsConfirmInferences(requisitionId: "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/confirm_inferences");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
    {
      "confirmations": [
        {
          "field": "gross.amount",
          "accepted": true,
          "corrected_value": "example"
        }
      ]
    }
    """, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();
import heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingRequisitionsConfirmInferences(ctx, "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").IdempotencyKey(key).FlowConfirmInferencesRequest(body).Execute()
body := strings.NewReader(`{
  "confirmations": [
    {
      "field": "gross.amount",
      "accepted": true,
      "corrected_value": "example"
    }
  ]
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/confirm_inferences", 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": "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "requisition",
  "livemode": false,
  "mocked": true,
  "status": "draft",
  "version": 1,
  "job_title": "example",
  "headcount": 1,
  "occupation_code": "ng-7412",
  "employment_type": "permanent",
  "compensation": {
    "amount": {
      "amount": 7500000,
      "currency": "NGN"
    },
    "period": "monthly",
    "negotiable": true
  },
  "location": {
    "description": "example",
    "state": "Lagos",
    "geofence_radius_metres": 1
  },
  "starts_on": "2026-09-01",
  "inferred_fields": [
    "example"
  ],
  "quarantine": {
    "rule_code": "discriminatory_criteria",
    "rule_pack_version": "2026.08.1",
    "detail": "The payee has no verified destination, so this line cannot be paid.",
    "contested": true,
    "offending_text": "example"
  },
  "created_at": "2026-09-01T09:00:00Z"
}
POST/v1/sourcing/requisitions/{requisition_id}/publish#

Release a role for broadcast

sourcing.requisitions.publish

This is the release step for a role: afterwards it is one you are willing to show candidates. So your product can keep the writing and the approving to itself, and the release itself becomes one event your other systems can act on.

Send the requisition's id in the path and no request body. The requisition comes back in published, and a requisition.published event goes to your webhook endpoints.

A role has to have been through POST /v1/sourcing/requisitions/{requisition_id}/confirm_inferences first. One still waiting on that, or one being held, answers conflict.

Broadcasting is its own call: POST /v1/sourcing/requisitions/{requisition_id}/broadcasts, where you pick the channels. The leads that come of it are at GET /v1/sourcing/leads.

Path parameters

requisition_id string required

The requisition's identifier, starting with flow_enterprise_requisition_: the id returned by POST /v1/sourcing/requisitions or listed at GET /v1/sourcing/requisitions, or one of the requisition_ids on a bulk upload.

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 requisition, now one you can hand to your channels.

id string required

The requisition's identifier, starting with flow_enterprise_requisition_; it never changes. You get it from POST /v1/sourcing/requisitions (or in requisition_ids on a bulk upload) and pass it as requisition_id on every call about it.

object always "requisition" required

Always requisition. 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

A requisition sits in pending_confirmation while an inferred pay figure, headcount, location or start date is unconfirmed. It cannot leave that state until you confirm.

pending_confirmationdraftunder_reviewquarantinedpublishedclosed
version integer · minimum 1 required

An edit after first broadcast makes a new version. Leads stay on theirs.

job_title string required

The title of the role as you gave it. The occupation code is inferred from it when you don't send one, so a local or colloquial title is fine.

occupation_code string optional

The occupation this role maps to in the taxonomy, such as ng-7412. Inferred from the title when you didn't send one, and listed in inferred_fields until you confirm it.

employment_type string optional
permanentfixed_termcontractcasualapprenticeship
headcount integer · minimum 1 required

How many people you are hiring for this role, at least 1. It never broadcasts on inference alone; confirm it if it appears in inferred_fields.

compensation Compensation optional
3 fields of Compensation
amount Money required
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.

period string required

The span the amount pays for: monthly, annual, daily or per_task (one payment for each task done). Required whenever you send compensation.

monthlyannualdailyper_task
negotiable boolean optional

Whether the figure is open to negotiation: true when it is, false when it is fixed.

location Location optional
3 fields of Location
description string required

Where the work is, in words a candidate would recognise, such as Ikeja, Lagos. The one part of a location you must give.

state string optional

The Nigerian state the work is in, such as Lagos. Optional; the description alone is enough to create the requisition.

geofence_radius_metres integer · nullable optional

A radius around the location, in whole metres, or null when there is none. If it had to be inferred it comes back in inferred_fields for you to confirm.

starts_on string · date · nullable optional

The date the role starts, as YYYY-MM-DD, or null when none is set. It never broadcasts on inference alone; confirm it if it appears in inferred_fields.

inferred_fields array of string optional

Fields that were inferred rather than read from what you sent. Each has to be confirmed, and compensation, headcount, location and start date can never broadcast while they are here.

quarantine one of optional

Why the requisition was held: the rule it tripped, the rule pack version, the detail and the offending text. null when nothing held it; fix what it names before you publish.

QuarantineFindingor
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.
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/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/publish" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingRequisitionsPublish({ requisitionId: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/publish', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Idempotency-Key': crypto.randomUUID(),
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_requisitions_publish(requisition_id='flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/publish',
    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 = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingRequisitionsPublish(requisition_id: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/publish');
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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingRequisitionsPublish("flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/publish"))
    .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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingRequisitionsPublish(requisitionId: "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/publish");
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 heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingRequisitionsPublish(ctx, "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/publish", 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": "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "requisition",
  "livemode": false,
  "mocked": true,
  "status": "published",
  "version": 1,
  "job_title": "example",
  "headcount": 1,
  "occupation_code": "ng-7412",
  "employment_type": "permanent",
  "compensation": {
    "amount": {
      "amount": 7500000,
      "currency": "NGN"
    },
    "period": "monthly",
    "negotiable": true
  },
  "location": {
    "description": "example",
    "state": "Lagos",
    "geofence_radius_metres": 1
  },
  "starts_on": "2026-09-01",
  "inferred_fields": [
    "example"
  ],
  "quarantine": {
    "rule_code": "discriminatory_criteria",
    "rule_pack_version": "2026.08.1",
    "detail": "The payee has no verified destination, so this line cannot be paid.",
    "contested": true,
    "offending_text": "example"
  },
  "created_at": "2026-09-01T09:00:00Z"
}
POST/v1/sourcing/requisitions/{requisition_id}/close#

Record that the hiring is over

sourcing.requisitions.close

Closing is how you record that a role is no longer being hired for. What the requisition produced stays where it is, so a role you filled in March still reads back as the role it was.

Send the requisition's id in the path and no request body. You get the requisition back in closed, and no call takes it out of that state.

The requisition has to be published. One still in draft, or waiting on its inferred fields being confirmed, answers conflict.

Nothing is removed by closing. The broadcasts and the capture links it made read back at their own addresses afterwards.

Path parameters

requisition_id string required

The requisition's identifier, starting with flow_enterprise_requisition_: the id returned by POST /v1/sourcing/requisitions or listed at GET /v1/sourcing/requisitions, or one of the requisition_ids on a bulk upload.

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 requisition, with every lead and advert it produced still readable.

id string required

The requisition's identifier, starting with flow_enterprise_requisition_; it never changes. You get it from POST /v1/sourcing/requisitions (or in requisition_ids on a bulk upload) and pass it as requisition_id on every call about it.

object always "requisition" required

Always requisition. 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

A requisition sits in pending_confirmation while an inferred pay figure, headcount, location or start date is unconfirmed. It cannot leave that state until you confirm.

pending_confirmationdraftunder_reviewquarantinedpublishedclosed
version integer · minimum 1 required

An edit after first broadcast makes a new version. Leads stay on theirs.

job_title string required

The title of the role as you gave it. The occupation code is inferred from it when you don't send one, so a local or colloquial title is fine.

occupation_code string optional

The occupation this role maps to in the taxonomy, such as ng-7412. Inferred from the title when you didn't send one, and listed in inferred_fields until you confirm it.

employment_type string optional
permanentfixed_termcontractcasualapprenticeship
headcount integer · minimum 1 required

How many people you are hiring for this role, at least 1. It never broadcasts on inference alone; confirm it if it appears in inferred_fields.

compensation Compensation optional
3 fields of Compensation
amount Money required
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.

period string required

The span the amount pays for: monthly, annual, daily or per_task (one payment for each task done). Required whenever you send compensation.

monthlyannualdailyper_task
negotiable boolean optional

Whether the figure is open to negotiation: true when it is, false when it is fixed.

location Location optional
3 fields of Location
description string required

Where the work is, in words a candidate would recognise, such as Ikeja, Lagos. The one part of a location you must give.

state string optional

The Nigerian state the work is in, such as Lagos. Optional; the description alone is enough to create the requisition.

geofence_radius_metres integer · nullable optional

A radius around the location, in whole metres, or null when there is none. If it had to be inferred it comes back in inferred_fields for you to confirm.

starts_on string · date · nullable optional

The date the role starts, as YYYY-MM-DD, or null when none is set. It never broadcasts on inference alone; confirm it if it appears in inferred_fields.

inferred_fields array of string optional

Fields that were inferred rather than read from what you sent. Each has to be confirmed, and compensation, headcount, location and start date can never broadcast while they are here.

quarantine one of optional

Why the requisition was held: the rule it tripped, the rule pack version, the detail and the offending text. null when nothing held it; fix what it names before you publish.

QuarantineFindingor
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.
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/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/close" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingRequisitionsClose({ requisitionId: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/close', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Idempotency-Key': crypto.randomUUID(),
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_requisitions_close(requisition_id='flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/close',
    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 = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingRequisitionsClose(requisition_id: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/close');
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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingRequisitionsClose("flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/close"))
    .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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingRequisitionsClose(requisitionId: "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/close");
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 heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingRequisitionsClose(ctx, "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/close", 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": "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "requisition",
  "livemode": false,
  "mocked": true,
  "status": "closed",
  "version": 1,
  "job_title": "example",
  "headcount": 1,
  "occupation_code": "ng-7412",
  "employment_type": "permanent",
  "compensation": {
    "amount": {
      "amount": 7500000,
      "currency": "NGN"
    },
    "period": "monthly",
    "negotiable": true
  },
  "location": {
    "description": "example",
    "state": "Lagos",
    "geofence_radius_metres": 1
  },
  "starts_on": "2026-09-01",
  "inferred_fields": [
    "example"
  ],
  "quarantine": {
    "rule_code": "discriminatory_criteria",
    "rule_pack_version": "2026.08.1",
    "detail": "The payee has no verified destination, so this line cannot be paid.",
    "contested": true,
    "offending_text": "example"
  },
  "created_at": "2026-09-01T09:00:00Z"
}
POST/v1/sourcing/imports#

Upload requisitions in bulk

sourcing.imports.create

This takes a file of roles instead of one request for each, for a recruiter who already keeps their vacancies in a spreadsheet. One upload opens every requisition the file holds and names the rows it could not read.

Send the file as the file part of a multipart/form-data body. You get the upload back with its own id, its validation_report, and requisition_ids for every requisition the file opened.

Nothing has to exist first.

An upload's id and a requisition's id both start flow_enterprise_requisition_, so the value on its own will not say which of the two you are holding.

Read the upload again at GET /v1/sourcing/imports/{import_id}, where its report outlives this response.

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 new upload, with the report the corrections come from.

id string required

The upload's identifier, starting with flow_enterprise_requisition_; it never changes. You get it from POST /v1/sourcing/imports and pass it as import_id to read the upload back; the requisitions it created are in requisition_ids.

object always "requisition_import" required

Always requisition_import. 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

Where the upload is: validating while its rows are checked, pending_acceptance once the report is ready for you, then accepted or rejected as the outcome. Read validation_report for every row that failed and why.

validatingpending_acceptanceacceptedrejected
requisition_ids array of string optional

The identifier of every requisition the upload created. Each behaves like one you created on its own, and reads at GET /v1/sourcing/requisitions/{requisition_id}.

validation_report 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.

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?
curl -X POST "https://sandbox.droomwork.io/v1/sourcing/imports" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingImportsCreate({});
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/imports', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Idempotency-Key': crypto.randomUUID(),
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_imports_create()
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/sourcing/imports',
    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 = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingImportsCreate();
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/imports');
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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingImportsCreate();
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/imports"))
    .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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingImportsCreate();
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/sourcing/imports");
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 heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingImportsCreate(ctx).Execute()
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/sourcing/imports", 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": "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "requisition_import",
  "livemode": false,
  "mocked": true,
  "status": "validating",
  "validation_report": {
    "entry_count": 0,
    "error_count": 0,
    "warning_count": 0,
    "entries": [
      {
        "index": 1,
        "severity": "error",
        "code": "invalid_money_amount",
        "detail": "The payee has no verified destination, so this line cannot be paid."
      }
    ]
  },
  "requisition_ids": [
    "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
  ]
}
GET/v1/sourcing/imports/{import_id}#

Read back a file you uploaded

sourcing.imports.retrieve

A bulk upload is one file of requisitions together with the check that was run over it. Reading it back is how your product tells somebody which rows went through and which did not, long after the upload itself.

Send the upload's id in the path. You get the upload with its validation_report and requisition_ids, one identifier for every requisition the file created.

An upload's id and a requisition's id both start flow_enterprise_requisition_, so the value on its own will not tell you which of the two you are holding.

Path parameters

import_id string required

The upload's identifier, starting with flow_enterprise_requisition_: the id returned by POST /v1/sourcing/imports when you uploaded the file.

Returns

The same upload, with its report, as it stands now.

id string required

The upload's identifier, starting with flow_enterprise_requisition_; it never changes. You get it from POST /v1/sourcing/imports and pass it as import_id to read the upload back; the requisitions it created are in requisition_ids.

object always "requisition_import" required

Always requisition_import. 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

Where the upload is: validating while its rows are checked, pending_acceptance once the report is ready for you, then accepted or rejected as the outcome. Read validation_report for every row that failed and why.

validatingpending_acceptanceacceptedrejected
requisition_ids array of string optional

The identifier of every requisition the upload created. Each behaves like one you created on its own, and reads at GET /v1/sourcing/requisitions/{requisition_id}.

validation_report 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.

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/sourcing/imports/%7Bimport_id%7D" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingImportsRetrieve({ importId: '{import_id}' });
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/imports/%7Bimport_id%7D', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_imports_retrieve(import_id='{import_id}')
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/imports/%7Bimport_id%7D',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingImportsRetrieve(import_id: '{import_id}');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/imports/%7Bimport_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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingImportsRetrieve("{import_id}");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/imports/%7Bimport_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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingImportsRetrieve(importId: "{import_id}");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/sourcing/imports/%7Bimport_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 heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingImportsRetrieve(ctx, "{import_id}").Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/imports/%7Bimport_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": "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "requisition_import",
  "livemode": false,
  "mocked": true,
  "status": "validating",
  "validation_report": {
    "entry_count": 0,
    "error_count": 0,
    "warning_count": 0,
    "entries": [
      {
        "index": 1,
        "severity": "error",
        "code": "invalid_money_amount",
        "detail": "The payee has no verified destination, so this line cannot be paid."
      }
    ]
  },
  "requisition_ids": [
    "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
  ]
}
GET/v1/sourcing/taxonomies/{taxonomy_id}#

Read an occupation taxonomy

sourcing.taxonomies.retrieve

An occupation taxonomy is the set of codes a job title can resolve to. Reading one back is how your product shows a recruiter the code their title landed on, and which other titles would have reached the same one.

Send the taxonomy's id in the path. You get its version and one entry per occupation, each with the code, the canonical title and the local titles that resolve to it.

Store the version beside any code you keep. A title resolves within one version and not across them, so a code kept without its version cannot be reproduced later.

Path parameters

taxonomy_id string required

The taxonomy's identifier, starting with flow_enterprise_requisition_: the id on the taxonomy at GET /v1/sourcing/taxonomies/{taxonomy_id}, where your requisitions' occupation codes, such as ng-7412, come from. No call lists taxonomies.

Returns

The occupation codes with the titles that resolve to each, and the version you read.

id string required

The taxonomy's identifier, starting with flow_enterprise_requisition_; it never changes. Pass it as taxonomy_id at GET /v1/sourcing/taxonomies/{taxonomy_id} to read this taxonomy again; version says which version of it you are reading.

object always "occupation_taxonomy" required

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

version string required

Which version of the taxonomy this is, such as 2026.08.1. What a title means changes between versions, and your requisition keeps meaning what it meant.

entries array of object required

One entry per occupation: its code, such as ng-7412, the canonical title, and the local and colloquial titles that mean the same thing. This is how a job title becomes an occupation code.

3 fields
occupation_code string required

The occupation's code, such as ng-7412. Send it as occupation_code on a requisition and you reach the people who know the role by any title in aliases.

canonical_title string required

The one standard title for this occupation, which every alias resolves to. Use it where you need a single name for a role candidates call many things.

aliases array of string required

Local and colloquial titles that mean this occupation.

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/sourcing/taxonomies/%7Btaxonomy_id%7D" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingTaxonomiesRetrieve({ taxonomyId: '{taxonomy_id}' });
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/taxonomies/%7Btaxonomy_id%7D', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_taxonomies_retrieve(taxonomy_id='{taxonomy_id}')
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/taxonomies/%7Btaxonomy_id%7D',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingTaxonomiesRetrieve(taxonomy_id: '{taxonomy_id}');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/taxonomies/%7Btaxonomy_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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingTaxonomiesRetrieve("{taxonomy_id}");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/taxonomies/%7Btaxonomy_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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingTaxonomiesRetrieve(taxonomyId: "{taxonomy_id}");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/sourcing/taxonomies/%7Btaxonomy_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 heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingTaxonomiesRetrieve(ctx, "{taxonomy_id}").Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/taxonomies/%7Btaxonomy_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": "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "occupation_taxonomy",
  "version": "2026.08.1",
  "entries": [
    {
      "occupation_code": "ng-7412",
      "canonical_title": "example",
      "aliases": [
        "example"
      ]
    }
  ]
}
GET/v1/sourcing/channels#

Choose where a vacancy goes

sourcing.channels.list

A channel is one place a requisition can be posted, and this is the set your organisation can post to. So your product can offer a recruiter the choice of where a vacancy goes, with what each one costs and what is left of today's cap beside it.

Send nothing and every category comes back, paid and free together. category and paid narrow it. Each entry is one channel, and its id is the part you keep.

A channel's id begins flow_enterprise_broadcast_, the same prefix a broadcast carries. Keep the two apart by where you stored them; do not read the kind of record off the prefix.

Name an id in channel_ids at POST /v1/sourcing/requisitions/{requisition_id}/broadcasts, or read one channel on its own at GET /v1/sourcing/channels/{channel_id}.

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.

category string optional

Only channels of one category: formal_board, professional_network, alumni_portal, campus_node or talent_pool (the Heros talent pool); the category, never the operator behind it. Leave it out to get every category.

formal_boardprofessional_networkalumni_portalcampus_nodetalent_pool
paid boolean optional

Pass true to list only channels that charge and count against your spend ceilings, false to list only free ones. Leave it out to get both.

Returns

The channels you can post to, each with its cost model and today's remaining cap.

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 Channel required

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

10 fields of Channel
id string required

The channel's identifier, starting with flow_enterprise_broadcast_; it never changes. You get it from GET /v1/sourcing/channels, and it is what you pass as channel_id and name in channel_ids when you broadcast.

object always "channel" required

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

category string required

The category, not the operator. Which platforms sit behind a category is not disclosed.

formal_boardprofessional_networkalumni_portalcampus_nodetalent_pool
label string optional

A name for your own reporting. Not the operator behind the channel.

paid boolean required

true when posting here is charged and counts against your spend ceilings, false when it is free. cost_model says how it charges.

cost_model object optional

How this channel charges, so a spend ceiling can be reasoned about.

2 fields
basis string optional

What you are charged for: per_post (each posting), per_click (each click), per_qualified_lead (each lead that passes your knockout rules) or free on a channel that never charges. indicative_unit_cost is roughly what one costs.

per_postper_clickper_qualified_leadfree
indicative_unit_cost 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.

quota object required

How many posts this channel takes from you in a day, and how many of those are left today. Either figure is null where no daily cap applies.

2 fields
posts_per_day integer · nullable optional

The most posts this channel accepts from you in one day. null where no daily cap applies.

remaining_today integer · nullable optional

How many posts you can still make on this channel today, counting down from posts_per_day. null where no daily cap applies.

rolling_yield object optional

Your own yield on this channel. Never anyone else's.

2 fields
qualified_lead_rate number · nullable optional

The share of leads from this channel that passed your knockout rules over the last window_days days. Your own figure only, and null when there is nothing to measure yet.

window_days integer optional

How many days the rolling yield is measured over, counted back from today. qualified_lead_rate covers exactly this window.

credentials_held boolean optional

true when credentials for posting on this channel are on file for your organisation. Without them nothing can post here, and GET /v1/sourcing/readiness lists the channels missing them.

available boolean optional

true when a broadcast can go to this channel right now, false when it can't. Read it before you name the channel in channel_ids on a broadcast.

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

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), category (optional), paid (optional)
curl -X GET "https://sandbox.droomwork.io/v1/sourcing/channels?limit=25&category=formal_board&paid=true" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

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

// query parameters: limit (optional), category (optional), paid (optional)
const result = await api.sourcingChannelsList({ limit: 25, category: 'formal_board', paid: true });
// query parameters: limit (optional), category (optional), paid (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/channels?limit=25&category=formal_board&paid=true', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

# query parameters: limit (optional), category (optional), paid (optional)
result = api.sourcing_channels_list(limit=25, category='formal_board', paid=true)
import os

import requests

# query parameters: limit (optional), category (optional), paid (optional)
response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/channels?limit=25&category=formal_board&paid=true',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

# query parameters: limit (optional), category (optional), paid (optional)
$result = $api->sourcingChannelsList(limit: 25, category: 'formal_board', paid: true);
<?php
// query parameters: limit (optional), category (optional), paid (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/channels?limit=25&category=formal_board&paid=true');
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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;
import com.useheros.sdk.model.*;

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

// query parameters: limit (optional), category (optional), paid (optional)
var result = api.sourcingChannelsList(25, FlowChannelCategory.fromValue("formal_board"), true);
// query parameters: limit (optional), category (optional), paid (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/channels?limit=25&category=formal_board&paid=true"))
    .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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

// query parameters: limit (optional), category (optional), paid (optional)
var result = api.SourcingChannelsList(limit: 25, category: FlowChannelCategory.FormalBoard, paid: true);
// query parameters: limit (optional), category (optional), paid (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/sourcing/channels?limit=25&category=formal_board&paid=true");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();
import heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

// query parameters: limit (optional), category (optional), paid (optional)
result, _, err := client.FLOWAPI.SourcingChannelsList(ctx).Limit(25).Category(heros.FlowChannelCategory("formal_board")).Paid(true).Execute()
// query parameters: limit (optional), category (optional), paid (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/channels?limit=25&category=formal_board&paid=true", 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": "flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "channel",
      "category": "formal_board",
      "paid": true,
      "quota": {
        "posts_per_day": 1,
        "remaining_today": 1
      },
      "label": "Formal board A",
      "cost_model": {
        "basis": "per_post",
        "indicative_unit_cost": {
          "amount": 500000,
          "currency": "NGN"
        }
      },
      "rolling_yield": {
        "qualified_lead_rate": 1,
        "window_days": 1
      },
      "credentials_held": true,
      "available": true
    }
  ],
  "has_more": true
}
GET/v1/sourcing/channels/{channel_id}#

Check a channel before you send to it

sourcing.channels.retrieve

A channel is somewhere a requisition can be advertised. Reading one back is how your product decides whether to name it in a broadcast at all, before the send instead of after it has failed.

Send the channel's id in the path. You get the channel with what it charges, how much of today's quota is left, and your own yield on it.

Read available before you name a channel in a broadcast. credentials_held says whether a false answer is because your organisation has nothing on file for posting there.

Path parameters

channel_id string required

The channel's identifier, starting with flow_enterprise_broadcast_: the id of a channel listed at GET /v1/sourcing/channels, and the channel_id you see on artefacts, deliveries and leads.

Returns

One channel, to check what it charges and whether a broadcast can go to it now.

id string required

The channel's identifier, starting with flow_enterprise_broadcast_; it never changes. You get it from GET /v1/sourcing/channels, and it is what you pass as channel_id and name in channel_ids when you broadcast.

object always "channel" required

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

category string required

The category, not the operator. Which platforms sit behind a category is not disclosed.

formal_boardprofessional_networkalumni_portalcampus_nodetalent_pool
label string optional

A name for your own reporting. Not the operator behind the channel.

paid boolean required

true when posting here is charged and counts against your spend ceilings, false when it is free. cost_model says how it charges.

cost_model object optional

How this channel charges, so a spend ceiling can be reasoned about.

2 fields
basis string optional

What you are charged for: per_post (each posting), per_click (each click), per_qualified_lead (each lead that passes your knockout rules) or free on a channel that never charges. indicative_unit_cost is roughly what one costs.

per_postper_clickper_qualified_leadfree
indicative_unit_cost 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.

quota object required

How many posts this channel takes from you in a day, and how many of those are left today. Either figure is null where no daily cap applies.

2 fields
posts_per_day integer · nullable optional

The most posts this channel accepts from you in one day. null where no daily cap applies.

remaining_today integer · nullable optional

How many posts you can still make on this channel today, counting down from posts_per_day. null where no daily cap applies.

rolling_yield object optional

Your own yield on this channel. Never anyone else's.

2 fields
qualified_lead_rate number · nullable optional

The share of leads from this channel that passed your knockout rules over the last window_days days. Your own figure only, and null when there is nothing to measure yet.

window_days integer optional

How many days the rolling yield is measured over, counted back from today. qualified_lead_rate covers exactly this window.

credentials_held boolean optional

true when credentials for posting on this channel are on file for your organisation. Without them nothing can post here, and GET /v1/sourcing/readiness lists the channels missing them.

available boolean optional

true when a broadcast can go to this channel right now, false when it can't. Read it before you name the channel in channel_ids on a broadcast.

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/sourcing/channels/%7Bchannel_id%7D" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingChannelsRetrieve({ channelId: '{channel_id}' });
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/channels/%7Bchannel_id%7D', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_channels_retrieve(channel_id='{channel_id}')
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/channels/%7Bchannel_id%7D',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingChannelsRetrieve(channel_id: '{channel_id}');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/channels/%7Bchannel_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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingChannelsRetrieve("{channel_id}");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/channels/%7Bchannel_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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingChannelsRetrieve(channelId: "{channel_id}");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/sourcing/channels/%7Bchannel_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 heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingChannelsRetrieve(ctx, "{channel_id}").Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/channels/%7Bchannel_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": "flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "channel",
  "category": "formal_board",
  "paid": true,
  "quota": {
    "posts_per_day": 1,
    "remaining_today": 1
  },
  "label": "Formal board A",
  "cost_model": {
    "basis": "per_post",
    "indicative_unit_cost": {
      "amount": 500000,
      "currency": "NGN"
    }
  },
  "rolling_yield": {
    "qualified_lead_rate": 1,
    "window_days": 1
  },
  "credentials_held": true,
  "available": true
}
GET/v1/sourcing/requisitions/{requisition_id}/artefacts#

Read the adverts before they go out

sourcing.artefacts.list

An artefact is the advert written for one channel. This lists the ones drafted for a requisition, so a recruiter reads what will go out channel by channel while there is still time to change it.

Send the requisition's id in the path. Each artefact comes back with the channel_id it was written for, its headline and body, and the fact_check comparing it against the requisition.

Read fact_check before you broadcast. It is where a claim in the copy that the requisition does not support is named, and it is the one thing on this list that can stop a send being worth making.

Path parameters

requisition_id string required

The requisition's identifier, starting with flow_enterprise_requisition_: the id returned by POST /v1/sourcing/requisitions or listed at GET /v1/sourcing/requisitions, or one of the requisition_ids on a bulk upload.

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.

Returns

The adverts for this requisition, each with its comparison result, ready for review before you broadcast.

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 Artefact required

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

7 fields of Artefact
id string required

The artefact's identifier; it never changes. You get it from GET /v1/sourcing/requisitions/{requisition_id}/artefacts and pass it as artefact_id to retrieve or edit this copy under the same requisition.

object always "channel_artefact" required

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

channel_id string required

The channel this copy was written for: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. Each artefact is sized and shaped for one destination, and a broadcast to that channel sends this copy.

headline string optional

The title line of the copy as it will appear on this channel. You can change it with PATCH while editable is true.

body string required

The text of the advert as it will go out on this channel. Every pay figure, employer legal name, statutory claim and requirement in it is compared with your requisition; see fact_check.

fact_check FactCheck required

The result of comparing this copy against the requisition. Pay figures, employer legal names, statutory claims and requirements must match exactly. A mismatch fails the render and is logged rather than corrected quietly.

3 fields of FactCheck
passed boolean required

true when every claim in checks agrees with the requisition. false when any one differs, and that copy does not reach the channel.

checks array of object required

One entry per claim compared, giving what the requisition says, what the copy says and whether they agree. Read it to see exactly which claim failed.

4 fields
claim string required

Which kind of fact was compared: pay_figure, employer_legal_name, statutory_claim (a claim about what the law provides) or requirement. All four must match the requisition exactly.

pay_figureemployer_legal_namestatutory_claimrequirement
in_requisition string required

The value as your requisition states it. This is the side that wins: the copy has to say this, never the other way round.

in_content string required

The value as the copy says it. Where it differs from in_requisition, agrees is false and the copy fails.

agrees boolean required

true when in_content matches in_requisition exactly. One false here fails the whole check, and the copy with it.

failed_at string · date-time · nullable optional

When this copy last failed the check, as an RFC 3339 timestamp in UTC. null when it has never failed.

editable boolean optional

False once the broadcast has gone out on this channel.

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

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)
curl -X GET "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/artefacts?limit=25" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

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

// query parameters: limit (optional)
const result = await api.sourcingArtefactsList({ requisitionId: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', limit: 25 });
// query parameters: limit (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/artefacts?limit=25', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

# query parameters: limit (optional)
result = api.sourcing_artefacts_list(requisition_id='flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', limit=25)
import os

import requests

# query parameters: limit (optional)
response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/artefacts?limit=25',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

# query parameters: limit (optional)
$result = $api->sourcingArtefactsList(requisition_id: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', limit: 25);
<?php
// query parameters: limit (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/artefacts?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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

// query parameters: limit (optional)
var result = api.sourcingArtefactsList("flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", 25);
// query parameters: limit (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/artefacts?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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

// query parameters: limit (optional)
var result = api.SourcingArtefactsList(requisitionId: "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", limit: 25);
// query parameters: limit (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/artefacts?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 heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

// query parameters: limit (optional)
result, _, err := client.FLOWAPI.SourcingArtefactsList(ctx, "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Limit(25).Execute()
// query parameters: limit (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/artefacts?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": "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "channel_artefact",
      "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "body": "example",
      "fact_check": {
        "passed": true,
        "checks": [
          {
            "claim": "pay_figure",
            "in_requisition": "example",
            "in_content": "example",
            "agrees": true
          }
        ],
        "failed_at": "2026-09-01T09:00:00Z"
      },
      "headline": "example",
      "editable": true
    }
  ],
  "has_more": true
}
GET/v1/sourcing/requisitions/{requisition_id}/artefacts/{artefact_id}#

Read one channel's advert in full

sourcing.artefacts.retrieve

This reads one channel's advert back as it stands now. So a page in your product can refresh an advert it saved earlier, instead of showing wording that a later edit has replaced.

Send requisition_id and artefact_id in the path. You get that one artefact in full, with its headline, body and fact_check, and editable saying whether the wording can still change.

Change the wording at PATCH /v1/sourcing/requisitions/{requisition_id}/artefacts/{artefact_id}. An identifier in the path that is not there answers not_found.

Path parameters

requisition_id string required

The requisition's identifier, starting with flow_enterprise_requisition_: the id returned by POST /v1/sourcing/requisitions or listed at GET /v1/sourcing/requisitions, or one of the requisition_ids on a bulk upload.

artefact_id string required

The artefact's identifier: the id of an artefact listed at GET /v1/sourcing/requisitions/{requisition_id}/artefacts, under the same requisition you name in this path.

Returns

One channel's advert as it now stands, with the comparison that was run on it.

id string required

The artefact's identifier; it never changes. You get it from GET /v1/sourcing/requisitions/{requisition_id}/artefacts and pass it as artefact_id to retrieve or edit this copy under the same requisition.

object always "channel_artefact" required

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

channel_id string required

The channel this copy was written for: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. Each artefact is sized and shaped for one destination, and a broadcast to that channel sends this copy.

headline string optional

The title line of the copy as it will appear on this channel. You can change it with PATCH while editable is true.

body string required

The text of the advert as it will go out on this channel. Every pay figure, employer legal name, statutory claim and requirement in it is compared with your requisition; see fact_check.

fact_check FactCheck required

The result of comparing this copy against the requisition. Pay figures, employer legal names, statutory claims and requirements must match exactly. A mismatch fails the render and is logged rather than corrected quietly.

3 fields of FactCheck
passed boolean required

true when every claim in checks agrees with the requisition. false when any one differs, and that copy does not reach the channel.

checks array of object required

One entry per claim compared, giving what the requisition says, what the copy says and whether they agree. Read it to see exactly which claim failed.

4 fields
claim string required

Which kind of fact was compared: pay_figure, employer_legal_name, statutory_claim (a claim about what the law provides) or requirement. All four must match the requisition exactly.

pay_figureemployer_legal_namestatutory_claimrequirement
in_requisition string required

The value as your requisition states it. This is the side that wins: the copy has to say this, never the other way round.

in_content string required

The value as the copy says it. Where it differs from in_requisition, agrees is false and the copy fails.

agrees boolean required

true when in_content matches in_requisition exactly. One false here fails the whole check, and the copy with it.

failed_at string · date-time · nullable optional

When this copy last failed the check, as an RFC 3339 timestamp in UTC. null when it has never failed.

editable boolean optional

False once the broadcast has gone out on this channel.

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/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/artefacts/%7Bartefact_id%7D" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingArtefactsRetrieve({ requisitionId: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', artefactId: '{artefact_id}' });
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/artefacts/%7Bartefact_id%7D', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_artefacts_retrieve(requisition_id='flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', artefact_id='{artefact_id}')
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/artefacts/%7Bartefact_id%7D',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingArtefactsRetrieve(requisition_id: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', artefact_id: '{artefact_id}');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/artefacts/%7Bartefact_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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingArtefactsRetrieve("flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "{artefact_id}");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/artefacts/%7Bartefact_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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingArtefactsRetrieve(requisitionId: "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", artefactId: "{artefact_id}");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/artefacts/%7Bartefact_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 heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingArtefactsRetrieve(ctx, "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "{artefact_id}").Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/artefacts/%7Bartefact_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": "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "channel_artefact",
  "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "body": "example",
  "fact_check": {
    "passed": true,
    "checks": [
      {
        "claim": "pay_figure",
        "in_requisition": "example",
        "in_content": "example",
        "agrees": true
      }
    ],
    "failed_at": "2026-09-01T09:00:00Z"
  },
  "headline": "example",
  "editable": true
}
PATCH/v1/sourcing/requisitions/{requisition_id}/artefacts/{artefact_id}#

Put your own words on an advert

sourcing.artefacts.update

The copy is generated, and this is where you make it yours. Send your own wording for one channel, so your product can let a hiring manager fix a line that reads badly on one destination and leave every other channel alone.

Send headline, body, or both. You get the whole artefact back, so one response is enough to redraw the advert on your own screen.

You need the artefact's id, which comes from GET /v1/sourcing/requisitions/{requisition_id}/artefacts.

Broadcast at POST /v1/sourcing/requisitions/{requisition_id}/broadcasts once the wording is what you want. An identifier in the path that is not there answers not_found.

Path parameters

requisition_id string required

The requisition's identifier, starting with flow_enterprise_requisition_: the id returned by POST /v1/sourcing/requisitions or listed at GET /v1/sourcing/requisitions, or one of the requisition_ids on a bulk upload.

artefact_id string required

The artefact's identifier: the id of an artefact listed at GET /v1/sourcing/requisitions/{requisition_id}/artefacts, under the same requisition you name in this path.

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

headline string optional

The new title line for this channel's copy; leave it out to keep the current one. It is checked against the requisition the same way generated copy is.

body string optional

The new text of the advert for this channel; leave it out to keep the current one. A pay figure, employer legal name, statutory claim or requirement the requisition doesn't carry fails the check.

Returns

The advert as it now reads, so you can confirm the wording you sent was stored.

id string required

The artefact's identifier; it never changes. You get it from GET /v1/sourcing/requisitions/{requisition_id}/artefacts and pass it as artefact_id to retrieve or edit this copy under the same requisition.

object always "channel_artefact" required

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

channel_id string required

The channel this copy was written for: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. Each artefact is sized and shaped for one destination, and a broadcast to that channel sends this copy.

headline string optional

The title line of the copy as it will appear on this channel. You can change it with PATCH while editable is true.

body string required

The text of the advert as it will go out on this channel. Every pay figure, employer legal name, statutory claim and requirement in it is compared with your requisition; see fact_check.

fact_check FactCheck required

The result of comparing this copy against the requisition. Pay figures, employer legal names, statutory claims and requirements must match exactly. A mismatch fails the render and is logged rather than corrected quietly.

3 fields of FactCheck
passed boolean required

true when every claim in checks agrees with the requisition. false when any one differs, and that copy does not reach the channel.

checks array of object required

One entry per claim compared, giving what the requisition says, what the copy says and whether they agree. Read it to see exactly which claim failed.

4 fields
claim string required

Which kind of fact was compared: pay_figure, employer_legal_name, statutory_claim (a claim about what the law provides) or requirement. All four must match the requisition exactly.

pay_figureemployer_legal_namestatutory_claimrequirement
in_requisition string required

The value as your requisition states it. This is the side that wins: the copy has to say this, never the other way round.

in_content string required

The value as the copy says it. Where it differs from in_requisition, agrees is false and the copy fails.

agrees boolean required

true when in_content matches in_requisition exactly. One false here fails the whole check, and the copy with it.

failed_at string · date-time · nullable optional

When this copy last failed the check, as an RFC 3339 timestamp in UTC. null when it has never failed.

editable boolean optional

False once the broadcast has gone out on this channel.

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.
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 PATCH "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/artefacts/%7Bartefact_id%7D" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"headline":"example","body":"example"}'
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingArtefactsUpdate({
  requisitionId: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
  artefactId: '{artefact_id}',
  idempotencyKey: crypto.randomUUID(),
  flowArtefactUpdateRequest: {"headline":"example","body":"example"},
});
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/artefacts/%7Bartefact_id%7D', {
  method: 'PATCH',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    "headline": "example",
    "body": "example"
  }),
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_artefacts_update(requisition_id='flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', artefact_id='{artefact_id}', body={"headline": "example", "body": "example"})
import os
import uuid

import requests

response = requests.request(
    'PATCH',
    'https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/artefacts/%7Bartefact_id%7D',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
        'Idempotency-Key': str(uuid.uuid4()),
    },
    json={"headline": "example", "body": "example"},
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingArtefactsUpdate($idempotencyKey, json_decode('{"headline":"example","body":"example"}', true));
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/artefacts/%7Bartefact_id%7D');
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_CUSTOMREQUEST => 'PATCH',
  CURLOPT_HTTPHEADER => [
    'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
    'Content-Type: application/json',
    'Idempotency-Key: ' . bin2hex(random_bytes(16)),
  ],
  CURLOPT_POSTFIELDS => '{"headline":"example","body":"example"}',
]);
$result = json_decode(curl_exec($ch), true);
import com.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingArtefactsUpdate("flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "{artefact_id}", idempotencyKey, body);
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/artefacts/%7Bartefact_id%7D"))
    .header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
    .header("Content-Type", "application/json")
    .header("Idempotency-Key", UUID.randomUUID().toString())
    .method("PATCH", HttpRequest.BodyPublishers.ofString("""
        {
          "headline": "example",
          "body": "example"
        }
        """))
    .build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();
using Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingArtefactsUpdate(requisitionId: "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", artefactId: "{artefact_id}", idempotencyKey, body);
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Patch, "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/artefacts/%7Bartefact_id%7D");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
    {
      "headline": "example",
      "body": "example"
    }
    """, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();
import heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingArtefactsUpdate(ctx, "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "{artefact_id}").IdempotencyKey(key).FlowArtefactUpdateRequest(body).Execute()
body := strings.NewReader(`{
  "headline": "example",
  "body": "example"
}`)
req, _ := http.NewRequest("PATCH", "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/artefacts/%7Bartefact_id%7D", 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": "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "channel_artefact",
  "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "body": "example",
  "fact_check": {
    "passed": true,
    "checks": [
      {
        "claim": "pay_figure",
        "in_requisition": "example",
        "in_content": "example",
        "agrees": true
      }
    ],
    "failed_at": "2026-09-01T09:00:00Z"
  },
  "headline": "example",
  "editable": true
}
POST/v1/sourcing/requisitions/{requisition_id}/broadcasts#

Put a requisition out on your channels

sourcing.broadcasts.create

A broadcast is one send of one requisition to a set of channels. This is the call that puts the vacancy in front of candidates, and it comes back with a record for each channel, so your product can follow them one at a time.

Name the channels in channel_ids, or send no request body. You get the broadcast's id and its deliveries, one for each channel.

The requisition in the path has to exist, from POST /v1/sourcing/requisitions.

Accepted is not posted. Each channel runs on its own, so read the deliveries before you tell anybody the vacancy is live.

Carry the id as broadcast_id to GET /v1/sourcing/broadcasts/{broadcast_id}.

Path parameters

requisition_id string required

The requisition's identifier, starting with flow_enterprise_requisition_: the id returned by POST /v1/sourcing/requisitions or listed at GET /v1/sourcing/requisitions, or one of the requisition_ids on a bulk upload.

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

channel_ids array of string optional

Defaults to every channel configured for your organisation.

Returns

The accepted broadcast, with one delivery per channel so you can follow each one.

id string required

The broadcast's identifier, starting with flow_enterprise_broadcast_; it never changes. You get it from POST /v1/sourcing/requisitions/{requisition_id}/broadcasts and pass it as broadcast_id to read it, retry a delivery or filter deliveries.

object always "broadcast" required

Always broadcast. 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.

requisition_id string required

The requisition this broadcast sent out: its id, starting with flow_enterprise_requisition_, from POST /v1/sourcing/requisitions. It is the one you named in the path when you created the broadcast, returned so the record stands on its own.

requisition_version integer required

Which version of the requisition went out: its version at the moment you broadcast. Leads from this broadcast stay bound to it, so you can reproduce a shortlist.

deliveries array of Delivery required

One delivery per channel the broadcast went to, each with its own state, attempt count and fault. A channel that failed is listed here as failed, never dropped.

10 fields of Delivery
id string required

The delivery's identifier, starting with flow_enterprise_delivery_; it never changes. It is in a broadcast's deliveries, at GET /v1/sourcing/deliveries and from POST /v1/sourcing/deliveries; pass it as delivery_id to read or retry it.

object always "channel_delivery" required

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

broadcast_id string required

The broadcast this delivery is part of: the id, starting with flow_enterprise_broadcast_, from POST /v1/sourcing/requisitions/{requisition_id}/broadcasts, or the one you named as broadcast_id at POST /v1/sourcing/deliveries.

requisition_id string optional

The requisition this delivery carried: the id of the requisition behind the broadcast, starting with flow_enterprise_requisition_, from POST /v1/sourcing/requisitions. Filter GET /v1/sourcing/deliveries by it to see every channel it went to.

channel_id string required

The channel this delivery went to: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. One delivery covers one channel, so a broadcast has one delivery per channel it went to.

state string required

failed is terminal and always reported. A channel you selected is never dropped silently.

queuedsendingdeliveredretryingfailed
attempts integer · minimum 0 required

How many times posting to this channel has been tried so far, 0 before the first try. Read it with state and last_fault to see how a channel is doing.

next_attempt_at string · date-time · nullable optional

Exponential backoff. Absent once the state is terminal.

last_fault string · nullable optional

Why the most recent attempt at this channel failed, in plain words; null while nothing has failed. Read it before you retry.

external_reference string · nullable optional

The channel's own reference for this posting, where the channel gives one. null otherwise, so name the delivery by id, never by this.

started_at string · date-time optional

When the broadcast started running, as an RFC 3339 timestamp in UTC. Each channel then runs on its own, so read deliveries for how far each one got.

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/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/broadcasts" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"channel_ids":["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]}'
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingBroadcastsCreate({
  requisitionId: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
  idempotencyKey: crypto.randomUUID(),
  flowBroadcastCreateRequest: {"channelIds":["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]},
});
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/broadcasts', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    "channel_ids": [
      "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
    ]
  }),
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_broadcasts_create(requisition_id='flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', body={"channel_ids": ["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]})
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/broadcasts',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
        'Idempotency-Key': str(uuid.uuid4()),
    },
    json={"channel_ids": ["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]},
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingBroadcastsCreate($idempotencyKey, json_decode('{"channel_ids":["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]}', true));
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/broadcasts');
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 => '{"channel_ids":["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]}',
]);
$result = json_decode(curl_exec($ch), true);
import com.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingBroadcastsCreate("flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/broadcasts"))
    .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("""
        {
          "channel_ids": [
            "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
          ]
        }
        """))
    .build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();
using Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingBroadcastsCreate(requisitionId: "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/broadcasts");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
    {
      "channel_ids": [
        "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
      ]
    }
    """, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();
import heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingBroadcastsCreate(ctx, "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").IdempotencyKey(key).FlowBroadcastCreateRequest(body).Execute()
body := strings.NewReader(`{
  "channel_ids": [
    "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
  ]
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/broadcasts", 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": "flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "broadcast",
  "livemode": false,
  "mocked": true,
  "requisition_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "requisition_version": 1,
  "deliveries": [
    {
      "id": "flow_enterprise_delivery_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "channel_delivery",
      "broadcast_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "state": "queued",
      "attempts": 0,
      "requisition_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "next_attempt_at": "2026-09-01T09:00:00Z",
      "last_fault": "The register did not answer within the timeout.",
      "external_reference": "paye-2026-09-rivers"
    }
  ],
  "started_at": "2026-09-01T09:00:00Z"
}
GET/v1/sourcing/broadcasts/{broadcast_id}#

See how far each channel got

sourcing.broadcasts.retrieve

A broadcast is one send of one requisition to several channels at once. Reading it back is how your product shows how far each channel got, because they do not finish together and one of them stalling says nothing about the rest.

Send the broadcast's id in the path. You get the broadcast with requisition_version, which is the version that went out, and one delivery for each channel it went to.

Each channel runs on its own, so read each delivery's state and attempts and never the broadcast as a single outcome.

A delivery you need tried again goes on its own at POST /v1/sourcing/broadcasts/{broadcast_id}/deliveries/{delivery_id}/retry.

Path parameters

broadcast_id string required

The broadcast's identifier, starting with flow_enterprise_broadcast_: the id returned by POST /v1/sourcing/requisitions/{requisition_id}/broadcasts, also carried as broadcast_id on every delivery.

Returns

One send with every channel's delivery on it, for seeing how far each got.

id string required

The broadcast's identifier, starting with flow_enterprise_broadcast_; it never changes. You get it from POST /v1/sourcing/requisitions/{requisition_id}/broadcasts and pass it as broadcast_id to read it, retry a delivery or filter deliveries.

object always "broadcast" required

Always broadcast. 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.

requisition_id string required

The requisition this broadcast sent out: its id, starting with flow_enterprise_requisition_, from POST /v1/sourcing/requisitions. It is the one you named in the path when you created the broadcast, returned so the record stands on its own.

requisition_version integer required

Which version of the requisition went out: its version at the moment you broadcast. Leads from this broadcast stay bound to it, so you can reproduce a shortlist.

deliveries array of Delivery required

One delivery per channel the broadcast went to, each with its own state, attempt count and fault. A channel that failed is listed here as failed, never dropped.

10 fields of Delivery
id string required

The delivery's identifier, starting with flow_enterprise_delivery_; it never changes. It is in a broadcast's deliveries, at GET /v1/sourcing/deliveries and from POST /v1/sourcing/deliveries; pass it as delivery_id to read or retry it.

object always "channel_delivery" required

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

broadcast_id string required

The broadcast this delivery is part of: the id, starting with flow_enterprise_broadcast_, from POST /v1/sourcing/requisitions/{requisition_id}/broadcasts, or the one you named as broadcast_id at POST /v1/sourcing/deliveries.

requisition_id string optional

The requisition this delivery carried: the id of the requisition behind the broadcast, starting with flow_enterprise_requisition_, from POST /v1/sourcing/requisitions. Filter GET /v1/sourcing/deliveries by it to see every channel it went to.

channel_id string required

The channel this delivery went to: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. One delivery covers one channel, so a broadcast has one delivery per channel it went to.

state string required

failed is terminal and always reported. A channel you selected is never dropped silently.

queuedsendingdeliveredretryingfailed
attempts integer · minimum 0 required

How many times posting to this channel has been tried so far, 0 before the first try. Read it with state and last_fault to see how a channel is doing.

next_attempt_at string · date-time · nullable optional

Exponential backoff. Absent once the state is terminal.

last_fault string · nullable optional

Why the most recent attempt at this channel failed, in plain words; null while nothing has failed. Read it before you retry.

external_reference string · nullable optional

The channel's own reference for this posting, where the channel gives one. null otherwise, so name the delivery by id, never by this.

started_at string · date-time optional

When the broadcast started running, as an RFC 3339 timestamp in UTC. Each channel then runs on its own, so read deliveries for how far each one got.

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/sourcing/broadcasts/flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingBroadcastsRetrieve({ broadcastId: 'flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/broadcasts/flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_broadcasts_retrieve(broadcast_id='flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/broadcasts/flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingBroadcastsRetrieve(broadcast_id: 'flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/broadcasts/flow_enterprise_broadcast_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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingBroadcastsRetrieve("flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/broadcasts/flow_enterprise_broadcast_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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingBroadcastsRetrieve(broadcastId: "flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/sourcing/broadcasts/flow_enterprise_broadcast_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 heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingBroadcastsRetrieve(ctx, "flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/broadcasts/flow_enterprise_broadcast_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": "flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "broadcast",
  "livemode": false,
  "mocked": true,
  "requisition_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "requisition_version": 1,
  "deliveries": [
    {
      "id": "flow_enterprise_delivery_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "channel_delivery",
      "broadcast_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "state": "queued",
      "attempts": 0,
      "requisition_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "next_attempt_at": "2026-09-01T09:00:00Z",
      "last_fault": "The register did not answer within the timeout.",
      "external_reference": "paye-2026-09-rivers"
    }
  ],
  "started_at": "2026-09-01T09:00:00Z"
}
POST/v1/sourcing/broadcasts/{broadcast_id}/deliveries/{delivery_id}/retry#

Try one channel again on its own

sourcing.deliveries.retry

One channel going wrong is not the broadcast going wrong, and this is the call that acts on the difference. It puts one channel's delivery back in the queue and leaves every other channel on that send exactly as it was.

Send the broadcast's id and the delivery's id in the path, and no request body. You get the delivery back in queued.

The delivery is one of those listed on a broadcast at GET /v1/sourcing/broadcasts/{broadcast_id}.

Path parameters

broadcast_id string required

The broadcast's identifier, starting with flow_enterprise_broadcast_: the id returned by POST /v1/sourcing/requisitions/{requisition_id}/broadcasts, also carried as broadcast_id on every delivery.

delivery_id string required

The delivery's identifier, starting with flow_enterprise_delivery_: the id of an entry in deliveries on a broadcast, of a delivery listed at GET /v1/sourcing/deliveries, or of one you recorded at POST /v1/sourcing/deliveries.

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 delivery back in the queue for another attempt at that one channel.

id string required

The delivery's identifier, starting with flow_enterprise_delivery_; it never changes. It is in a broadcast's deliveries, at GET /v1/sourcing/deliveries and from POST /v1/sourcing/deliveries; pass it as delivery_id to read or retry it.

object always "channel_delivery" required

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

broadcast_id string required

The broadcast this delivery is part of: the id, starting with flow_enterprise_broadcast_, from POST /v1/sourcing/requisitions/{requisition_id}/broadcasts, or the one you named as broadcast_id at POST /v1/sourcing/deliveries.

requisition_id string optional

The requisition this delivery carried: the id of the requisition behind the broadcast, starting with flow_enterprise_requisition_, from POST /v1/sourcing/requisitions. Filter GET /v1/sourcing/deliveries by it to see every channel it went to.

channel_id string required

The channel this delivery went to: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. One delivery covers one channel, so a broadcast has one delivery per channel it went to.

state string required

failed is terminal and always reported. A channel you selected is never dropped silently.

queuedsendingdeliveredretryingfailed
attempts integer · minimum 0 required

How many times posting to this channel has been tried so far, 0 before the first try. Read it with state and last_fault to see how a channel is doing.

next_attempt_at string · date-time · nullable optional

Exponential backoff. Absent once the state is terminal.

last_fault string · nullable optional

Why the most recent attempt at this channel failed, in plain words; null while nothing has failed. Read it before you retry.

external_reference string · nullable optional

The channel's own reference for this posting, where the channel gives one. null otherwise, so name the delivery by id, never by this.

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 POST "https://sandbox.droomwork.io/v1/sourcing/broadcasts/flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/deliveries/%7Bdelivery_id%7D/retry" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingDeliveriesRetry({ broadcastId: 'flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', deliveryId: '{delivery_id}' });
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/broadcasts/flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/deliveries/%7Bdelivery_id%7D/retry', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Idempotency-Key': crypto.randomUUID(),
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_deliveries_retry(broadcast_id='flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', delivery_id='{delivery_id}')
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/sourcing/broadcasts/flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/deliveries/%7Bdelivery_id%7D/retry',
    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 = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingDeliveriesRetry(broadcast_id: 'flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', delivery_id: '{delivery_id}');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/broadcasts/flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/deliveries/%7Bdelivery_id%7D/retry');
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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingDeliveriesRetry("flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "{delivery_id}");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/broadcasts/flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/deliveries/%7Bdelivery_id%7D/retry"))
    .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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingDeliveriesRetry(broadcastId: "flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", deliveryId: "{delivery_id}");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/sourcing/broadcasts/flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/deliveries/%7Bdelivery_id%7D/retry");
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 heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingDeliveriesRetry(ctx, "flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "{delivery_id}").Execute()
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/sourcing/broadcasts/flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/deliveries/%7Bdelivery_id%7D/retry", 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": "flow_enterprise_delivery_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "channel_delivery",
  "broadcast_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "state": "queued",
  "attempts": 0,
  "requisition_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "next_attempt_at": "2026-09-01T09:00:00Z",
  "last_fault": "The register did not answer within the timeout.",
  "external_reference": "paye-2026-09-rivers"
}
GET/v1/sourcing/deliveries#

See which channels a vacancy reached

sourcing.deliveries.list

A delivery is one channel's record of one broadcast. This is every delivery your organisation holds, so a screen shows which channels a vacancy actually reached without opening each broadcast in turn.

Send nothing and the deliveries come back, each naming the broadcast and the channel it belongs to, the state it reached and how many attempts have been made.

Each channel runs on its own, so a delivery is read on its own and never as the broadcast's single outcome.

Try one again at POST /v1/sourcing/broadcasts/{broadcast_id}/deliveries/{delivery_id}/retry, passing that row's broadcast_id and its own id.

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.

broadcast_id string optional

One broadcast's deliveries, one per channel it went to: pass the broadcast's id, starting with flow_enterprise_broadcast_, from POST /v1/sourcing/requisitions/{requisition_id}/broadcasts. Leave it out to get deliveries from every broadcast.

requisition_id string optional

Only deliveries for one requisition, across every broadcast of it: pass the requisition's id, starting with flow_enterprise_requisition_, from POST /v1/sourcing/requisitions. Leave it out to get deliveries for every requisition.

state string optional

Restricts the list to one state: queued (not yet tried), sending, delivered, retrying (failed, another try is scheduled) or failed (terminal, the channel was never reached). Leave it out to get every delivery, whatever its state.

queuedsendingdeliveredretryingfailed

Returns

One record per channel per send, each with its own state and attempt count.

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 Delivery required

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

10 fields of Delivery
id string required

The delivery's identifier, starting with flow_enterprise_delivery_; it never changes. It is in a broadcast's deliveries, at GET /v1/sourcing/deliveries and from POST /v1/sourcing/deliveries; pass it as delivery_id to read or retry it.

object always "channel_delivery" required

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

broadcast_id string required

The broadcast this delivery is part of: the id, starting with flow_enterprise_broadcast_, from POST /v1/sourcing/requisitions/{requisition_id}/broadcasts, or the one you named as broadcast_id at POST /v1/sourcing/deliveries.

requisition_id string optional

The requisition this delivery carried: the id of the requisition behind the broadcast, starting with flow_enterprise_requisition_, from POST /v1/sourcing/requisitions. Filter GET /v1/sourcing/deliveries by it to see every channel it went to.

channel_id string required

The channel this delivery went to: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. One delivery covers one channel, so a broadcast has one delivery per channel it went to.

state string required

failed is terminal and always reported. A channel you selected is never dropped silently.

queuedsendingdeliveredretryingfailed
attempts integer · minimum 0 required

How many times posting to this channel has been tried so far, 0 before the first try. Read it with state and last_fault to see how a channel is doing.

next_attempt_at string · date-time · nullable optional

Exponential backoff. Absent once the state is terminal.

last_fault string · nullable optional

Why the most recent attempt at this channel failed, in plain words; null while nothing has failed. Read it before you retry.

external_reference string · nullable optional

The channel's own reference for this posting, where the channel gives one. null otherwise, so name the delivery by id, never by this.

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

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), broadcast_id (optional), requisition_id (optional), state (optional)
curl -X GET "https://sandbox.droomwork.io/v1/sourcing/deliveries?limit=25&broadcast_id=flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&requisition_id=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&state=queued" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

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

// query parameters: limit (optional), starting_after (optional), broadcast_id (optional), requisition_id (optional), state (optional)
const result = await api.sourcingDeliveriesList({ limit: 25, broadcastId: 'flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', requisitionId: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', state: 'queued' });
// query parameters: limit (optional), starting_after (optional), broadcast_id (optional), requisition_id (optional), state (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/deliveries?limit=25&broadcast_id=flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&requisition_id=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&state=queued', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

# query parameters: limit (optional), starting_after (optional), broadcast_id (optional), requisition_id (optional), state (optional)
result = api.sourcing_deliveries_list(limit=25, broadcast_id='flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', requisition_id='flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', state='queued')
import os

import requests

# query parameters: limit (optional), starting_after (optional), broadcast_id (optional), requisition_id (optional), state (optional)
response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/deliveries?limit=25&broadcast_id=flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&requisition_id=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&state=queued',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

# query parameters: limit (optional), starting_after (optional), broadcast_id (optional), requisition_id (optional), state (optional)
$result = $api->sourcingDeliveriesList(limit: 25, broadcast_id: 'flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', requisition_id: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', state: 'queued');
<?php
// query parameters: limit (optional), starting_after (optional), broadcast_id (optional), requisition_id (optional), state (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/deliveries?limit=25&broadcast_id=flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&requisition_id=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&state=queued');
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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;
import com.useheros.sdk.model.*;

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

// query parameters: limit (optional), starting_after (optional), broadcast_id (optional), requisition_id (optional), state (optional)
var result = api.sourcingDeliveriesList(25, null, "flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", FlowDeliveryState.fromValue("queued"));
// query parameters: limit (optional), starting_after (optional), broadcast_id (optional), requisition_id (optional), state (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/deliveries?limit=25&broadcast_id=flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&requisition_id=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&state=queued"))
    .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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

// query parameters: limit (optional), starting_after (optional), broadcast_id (optional), requisition_id (optional), state (optional)
var result = api.SourcingDeliveriesList(limit: 25, broadcastId: "flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", requisitionId: "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", state: FlowDeliveryState.Queued);
// query parameters: limit (optional), starting_after (optional), broadcast_id (optional), requisition_id (optional), state (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/sourcing/deliveries?limit=25&broadcast_id=flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&requisition_id=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&state=queued");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();
import heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

// query parameters: limit (optional), starting_after (optional), broadcast_id (optional), requisition_id (optional), state (optional)
result, _, err := client.FLOWAPI.SourcingDeliveriesList(ctx).Limit(25).BroadcastId("flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").RequisitionId("flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").State(heros.FlowDeliveryState("queued")).Execute()
// query parameters: limit (optional), starting_after (optional), broadcast_id (optional), requisition_id (optional), state (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/deliveries?limit=25&broadcast_id=flow_enterprise_broadcast_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&requisition_id=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&state=queued", 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": "flow_enterprise_delivery_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "channel_delivery",
      "broadcast_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "state": "queued",
      "attempts": 0,
      "requisition_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "next_attempt_at": "2026-09-01T09:00:00Z",
      "last_fault": "The register did not answer within the timeout.",
      "external_reference": "paye-2026-09-rivers"
    }
  ],
  "has_more": true
}
POST/v1/sourcing/deliveries#

Log a posting you made by hand

sourcing.deliveries.create

Not every channel is one we can post to. When you placed the advert there yourself, record it here, and that posting becomes a delivery like the ones a broadcast makes. So your record of where a vacancy went covers everywhere it went.

The body takes broadcast_id and channel_id, and neither is required. You get the whole delivery back, with an id of its own starting flow_enterprise_delivery_, and that is the identifier to keep.

The broadcast has to exist, from POST /v1/sourcing/requisitions/{requisition_id}/broadcasts, and the channel has to be one of yours, from GET /v1/sourcing/channels.

Send both fields on every call. The schema requires neither, and a delivery naming the wrong broadcast is a record you cannot trace.

Read it back at GET /v1/sourcing/deliveries/{delivery_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 optional

broadcast_id string optional

The broadcast this delivery belongs to: the id of a broadcast you created at POST /v1/sourcing/requisitions/{requisition_id}/broadcasts or read at GET /v1/sourcing/broadcasts/{broadcast_id}. It starts with flow_enterprise_broadcast_.

channel_id string optional

The channel you posted on by hand: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. The posting then counts in attribution like every other delivery.

Returns

The posting you recorded, with the identifier to read it back by.

id string required

The delivery's identifier, starting with flow_enterprise_delivery_; it never changes. It is in a broadcast's deliveries, at GET /v1/sourcing/deliveries and from POST /v1/sourcing/deliveries; pass it as delivery_id to read or retry it.

object always "channel_delivery" required

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

broadcast_id string required

The broadcast this delivery is part of: the id, starting with flow_enterprise_broadcast_, from POST /v1/sourcing/requisitions/{requisition_id}/broadcasts, or the one you named as broadcast_id at POST /v1/sourcing/deliveries.

requisition_id string optional

The requisition this delivery carried: the id of the requisition behind the broadcast, starting with flow_enterprise_requisition_, from POST /v1/sourcing/requisitions. Filter GET /v1/sourcing/deliveries by it to see every channel it went to.

channel_id string required

The channel this delivery went to: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. One delivery covers one channel, so a broadcast has one delivery per channel it went to.

state string required

failed is terminal and always reported. A channel you selected is never dropped silently.

queuedsendingdeliveredretryingfailed
attempts integer · minimum 0 required

How many times posting to this channel has been tried so far, 0 before the first try. Read it with state and last_fault to see how a channel is doing.

next_attempt_at string · date-time · nullable optional

Exponential backoff. Absent once the state is terminal.

last_fault string · nullable optional

Why the most recent attempt at this channel failed, in plain words; null while nothing has failed. Read it before you retry.

external_reference string · nullable optional

The channel's own reference for this posting, where the channel gives one. null otherwise, so name the delivery by id, never by this.

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/sourcing/deliveries" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"broadcast_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","channel_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}'
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingDeliveriesCreate({});
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/deliveries', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    "broadcast_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
    "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
  }),
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_deliveries_create()
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/sourcing/deliveries',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
        'Idempotency-Key': str(uuid.uuid4()),
    },
    json={"broadcast_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingDeliveriesCreate();
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/deliveries');
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 => '{"broadcast_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","channel_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}',
]);
$result = json_decode(curl_exec($ch), true);
import com.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingDeliveriesCreate();
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/deliveries"))
    .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("""
        {
          "broadcast_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
          "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
        }
        """))
    .build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();
using Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingDeliveriesCreate();
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/sourcing/deliveries");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
    {
      "broadcast_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
    }
    """, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();
import heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingDeliveriesCreate(ctx).Execute()
body := strings.NewReader(`{
  "broadcast_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/sourcing/deliveries", 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": "flow_enterprise_delivery_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "channel_delivery",
  "broadcast_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "state": "queued",
  "attempts": 0,
  "requisition_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "next_attempt_at": "2026-09-01T09:00:00Z",
  "last_fault": "The register did not answer within the timeout.",
  "external_reference": "paye-2026-09-rivers"
}
GET/v1/sourcing/spend_ceilings#

What is left to spend on channels

sourcing.spend_ceilings.retrieve

Posting on a paid channel spends against a ceiling, and this is where you read how much of it is gone. So your product can put the headroom in front of a recruiter before they pick paid channels, instead of leaving them to add up unit costs.

You send nothing, and there is no identifier to pass: there is one of these for your organisation. You get the organisation's ceiling with what is consumed against it, and the same pair for each requisition that has a ceiling of its own.

A requisition with no entry in per_requisition has no ceiling of its own. Where it has one, the smaller headroom is the figure to plan against.

Read which channels charge at GET /v1/sourcing/channels, then send at POST /v1/sourcing/requisitions/{requisition_id}/broadcasts.

Returns

Your organisation's ceiling and each requisition's own, with what is consumed against them.

object always "spend_ceilings" required

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

organisation object required

Your organisation's ceiling on paid channel spend across every requisition, and what you've consumed against it. Checked before any paid channel runs.

2 fields
ceiling Money required
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.

consumed Money required
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.

per_requisition array of object optional

One entry per requisition with its own ceiling, and what's been consumed against it. A broadcast is checked against the requisition's ceiling and your organisation's, and either can block it.

3 fields
requisition_id string required

The requisition this ceiling applies to: the id of one of your requisitions, from POST /v1/sourcing/requisitions or GET /v1/sourcing/requisitions. It starts with flow_enterprise_requisition_.

ceiling Money required
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.

consumed Money required
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.

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/sourcing/spend_ceilings" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingSpendCeilingsRetrieve({});
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/spend_ceilings', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_spend_ceilings_retrieve()
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/spend_ceilings',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingSpendCeilingsRetrieve();
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/spend_ceilings');
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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingSpendCeilingsRetrieve();
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/spend_ceilings"))
    .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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingSpendCeilingsRetrieve();
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/sourcing/spend_ceilings");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();
import heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingSpendCeilingsRetrieve(ctx).Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/spend_ceilings", 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": "spend_ceilings",
  "organisation": {
    "ceiling": {
      "amount": 100000000,
      "currency": "NGN"
    },
    "consumed": {
      "amount": 52500000,
      "currency": "NGN"
    }
  },
  "per_requisition": [
    {
      "requisition_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "ceiling": {
        "amount": 100000000,
        "currency": "NGN"
      },
      "consumed": {
        "amount": 52500000,
        "currency": "NGN"
      }
    }
  ]
}
POST/v1/sourcing/requisitions/{requisition_id}/capture_links#

Give one channel its own link

sourcing.capture_links.create

This makes the link candidates follow to answer one requisition, bound to the one channel you name. So you can hand a channel one link and have your knockout questions asked on it, instead of building a page of your own.

Send the requisition's id in the path and the channel's id in the body. You get back the url to hand out and the token inside it.

The requisition has to exist first, from POST /v1/sourcing/requisitions, and the channel has to be one from GET /v1/sourcing/channels.

A capture link's id starts with flow_enterprise_lead_, which is also a lead's prefix. Keep the two kinds apart in your own storage, because the prefix will not.

Create one link per channel; no call moves a link to another channel afterwards. Carry the id as capture_link_id to check the link at GET /v1/sourcing/capture_links/{capture_link_id}.

Path parameters

requisition_id string required

The requisition's identifier, starting with flow_enterprise_requisition_: the id returned by POST /v1/sourcing/requisitions or listed at GET /v1/sourcing/requisitions, or one of the requisition_ids on a bulk upload.

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

channel_id string required

The channel this link is for: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. Every lead who follows the link is attributed to this channel.

expires_at string · date-time optional

When the link stops working, as an RFC 3339 timestamp in UTC. Omit it for a link with no expiry.

Returns

The link to hand out, with the channel it is bound to and its opaque token.

id string required

The capture link's identifier. It starts with flow_enterprise_lead_ and never changes; pass it as capture_link_id at GET /v1/sourcing/capture_links/{capture_link_id}, and read it back on a lead's attribution.

object always "capture_link" required

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

requisition_id string required

The requisition the link captures leads for: the requisition_id you put in the path at POST /v1/sourcing/requisitions/{requisition_id}/capture_links, that requisition's id starting with flow_enterprise_requisition_.

channel_id string optional

The channel the link was created for: the channel_id you sent at POST /v1/sourcing/requisitions/{requisition_id}/capture_links, starting with flow_enterprise_broadcast_. Every lead who follows the link is attributed to it.

url string · uri required

Carries the token and nothing else. No name, no phone number and no identifier ever appears in a query parameter, because a link gets pasted where you cannot see it.

token string required

Opaque and not enumerable. Guessing a neighbouring one finds nothing.

leads_captured integer · minimum 0 optional

How many leads have come in through this link so far. 0 for a link nobody has followed yet.

expires_at string · date-time · nullable optional

When the link stops working, as an RFC 3339 timestamp in UTC. null when the link has no expiry.

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.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
curl -X POST "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/capture_links" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"channel_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","expires_at":"2026-09-01T09:00:00Z"}'
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingCaptureLinksCreate({
  requisitionId: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
  idempotencyKey: crypto.randomUUID(),
  flowCaptureLinkCreateRequest: {"channelId":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","expiresAt":"2026-09-01T09:00:00Z"},
});
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/capture_links', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
    "expires_at": "2026-09-01T09:00:00Z"
  }),
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_capture_links_create(requisition_id='flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', body={"channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "expires_at": "2026-09-01T09:00:00Z"})
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/capture_links',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
        'Idempotency-Key': str(uuid.uuid4()),
    },
    json={"channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "expires_at": "2026-09-01T09:00:00Z"},
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingCaptureLinksCreate($idempotencyKey, json_decode('{"channel_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","expires_at":"2026-09-01T09:00:00Z"}', true));
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/capture_links');
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 => '{"channel_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","expires_at":"2026-09-01T09:00:00Z"}',
]);
$result = json_decode(curl_exec($ch), true);
import com.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingCaptureLinksCreate("flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/capture_links"))
    .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("""
        {
          "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
          "expires_at": "2026-09-01T09:00:00Z"
        }
        """))
    .build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();
using Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingCaptureLinksCreate(requisitionId: "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", idempotencyKey, body);
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/capture_links");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
    {
      "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "expires_at": "2026-09-01T09:00:00Z"
    }
    """, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();
import heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingCaptureLinksCreate(ctx, "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").IdempotencyKey(key).FlowCaptureLinkCreateRequest(body).Execute()
body := strings.NewReader(`{
  "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "expires_at": "2026-09-01T09:00:00Z"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/capture_links", 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": "flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "capture_link",
  "url": "https://files.sandbox.droomwork.com/example",
  "token": "example",
  "requisition_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "leads_captured": 0,
  "expires_at": "2026-09-01T09:00:00Z"
}
GET/v1/sourcing/capture_links/{capture_link_id}#

Check one link and what it brought in

sourcing.capture_links.retrieve

A capture link is the address a candidate follows to put themselves forward for one requisition. Reading it back is how your product shows what a link has brought in, without keeping a count of its own beside it.

Send the link's id in the path. You get the link with its url, the requisition it captures for, how many leads have come through it so far, and when it stops working.

expires_at is null on a link that has no expiry, so check for that before you treat a date as the one it runs out on.

Path parameters

capture_link_id string required

The capture link's identifier: the id in the response to POST /v1/sourcing/requisitions/{requisition_id}/capture_links, or the capture_link_id on a lead's attribution. It starts with flow_enterprise_lead_.

Returns

The link as it stands, with the number of leads captured through it so far.

id string required

The capture link's identifier. It starts with flow_enterprise_lead_ and never changes; pass it as capture_link_id at GET /v1/sourcing/capture_links/{capture_link_id}, and read it back on a lead's attribution.

object always "capture_link" required

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

requisition_id string required

The requisition the link captures leads for: the requisition_id you put in the path at POST /v1/sourcing/requisitions/{requisition_id}/capture_links, that requisition's id starting with flow_enterprise_requisition_.

channel_id string optional

The channel the link was created for: the channel_id you sent at POST /v1/sourcing/requisitions/{requisition_id}/capture_links, starting with flow_enterprise_broadcast_. Every lead who follows the link is attributed to it.

url string · uri required

Carries the token and nothing else. No name, no phone number and no identifier ever appears in a query parameter, because a link gets pasted where you cannot see it.

token string required

Opaque and not enumerable. Guessing a neighbouring one finds nothing.

leads_captured integer · minimum 0 optional

How many leads have come in through this link so far. 0 for a link nobody has followed yet.

expires_at string · date-time · nullable optional

When the link stops working, as an RFC 3339 timestamp in UTC. null when the link has no expiry.

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/sourcing/capture_links/%7Bcapture_link_id%7D" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingCaptureLinksRetrieve({ captureLinkId: '{capture_link_id}' });
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/capture_links/%7Bcapture_link_id%7D', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_capture_links_retrieve(capture_link_id='{capture_link_id}')
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/capture_links/%7Bcapture_link_id%7D',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingCaptureLinksRetrieve(capture_link_id: '{capture_link_id}');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/capture_links/%7Bcapture_link_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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingCaptureLinksRetrieve("{capture_link_id}");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/capture_links/%7Bcapture_link_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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingCaptureLinksRetrieve(captureLinkId: "{capture_link_id}");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/sourcing/capture_links/%7Bcapture_link_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 heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingCaptureLinksRetrieve(ctx, "{capture_link_id}").Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/capture_links/%7Bcapture_link_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": "flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "capture_link",
  "url": "https://files.sandbox.droomwork.com/example",
  "token": "example",
  "requisition_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "leads_captured": 0,
  "expires_at": "2026-09-01T09:00:00Z"
}
GET/v1/sourcing/requisitions/{requisition_id}/knockout_rules#

See what a requisition screens on

sourcing.knockout_rules.list

A knockout rule is a question a candidate is asked together with the test their answer is judged by. This lists the ones a requisition screens on, so a recruiter can see what is being asked of people before anybody answers it.

Send the requisition's id in the path. Each rule comes back with its code, the question as it is put, the rule the answer is tested against, and the rule_pack_version that judges it.

What one candidate answered is read against their lead, at GET /v1/sourcing/leads/{lead_id}/screening.

Path parameters

requisition_id string required

The requisition's identifier, starting with flow_enterprise_requisition_: the id returned by POST /v1/sourcing/requisitions or listed at GET /v1/sourcing/requisitions, or one of the requisition_ids on a bulk upload.

Returns

Every rule this requisition screens on, with the rule pack version that will judge each.

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 KnockoutRule required

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

5 fields of KnockoutRule
code string required

A short code that names this rule. Use it in your own reporting to refer to the rule without quoting the question or the test.

question string required

What the candidate is asked. The conversation may phrase it differently, but the answer is judged by rule, never by the phrasing.

rule string required

The deterministic test applied to what was extracted from the answer, such as years_experience >= 3. It is the only thing that decides pass or fail.

rule_pack_version string required

The version of the rule pack this rule belongs to, such as 2026.08.1. Rules are versioned, so you can reproduce an outcome against the version that decided it.

mandatory boolean optional

true when every candidate for this requisition is asked this question and judged by this rule. false when the conversation may leave it out.

question_cap integer · defaults to 5 required

Five by default. A screening conversation that runs longer than this stops being screening and starts being an interview.

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/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/knockout_rules" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingKnockoutRulesList({ requisitionId: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/knockout_rules', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_knockout_rules_list(requisition_id='flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/knockout_rules',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingKnockoutRulesList(requisition_id: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/knockout_rules');
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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingKnockoutRulesList("flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/knockout_rules"))
    .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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingKnockoutRulesList(requisitionId: "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/knockout_rules");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();
import heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingKnockoutRulesList(ctx, "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/knockout_rules", 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": [
    {
      "code": "invalid_money_amount",
      "question": "example",
      "rule": "years_experience >= 3",
      "rule_pack_version": "2026.08.1",
      "mandatory": true
    }
  ],
  "question_cap": 1
}
GET/v1/sourcing/leads#

See who has answered your requisitions

sourcing.leads.list

A lead is one candidate's answer to one of your requisitions, and this gives you the ones your organisation holds. Each row is the whole record, the fit score included, so you can rank a review queue in your product from one call.

Send nothing to get every lead you hold, or narrow it by requisition_id to one vacancy and by outcome to one stage of screening.

A lead can be on this list before your rules have decided about it. Read screening_outcome on each row, and read it again before you act on a shortlist you saved earlier.

Carry a row's id as lead_id to open that candidate at GET /v1/sourcing/leads/{lead_id}.

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.

requisition_id string optional

Restricts the list to the leads of one requisition: its id from POST /v1/sourcing/requisitions or GET /v1/sourcing/requisitions, starting with flow_enterprise_requisition_. Leave it out to get leads across all your requisitions.

outcome string optional

Restricts the list to one screening outcome: in_progress (still answering), passed or failed (your knockout rules decided), or abandoned (stopped before the end). Pass passed for your shortlist; leave it out to get every lead.

in_progresspassedfailedabandoned

Returns

A page of whole lead records, enough to fill a review queue without opening each one.

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 Lead required

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

14 fields of Lead
id string required

The lead's identifier. It starts with flow_enterprise_lead_ and never changes; pass it as lead_id on GET /v1/sourcing/leads/{lead_id} and the /screening, /attribution and /promote routes under it.

object always "lead" required

Always lead. 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.

requisition_id string required

The requisition this lead answered: its id from POST /v1/sourcing/requisitions, starting with flow_enterprise_requisition_. With requisition_version, it pins the exact version the lead was captured against.

requisition_version integer required

Which version produced this lead. A shortlist is reproducible against it.

channel_id string optional

The channel the lead came through: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. It is what your yield at GET /v1/sourcing/analytics counts against.

screening_outcome string optional
in_progresspassedfailedabandoned
fit_score number · minimum 0 · maximum 100 required

How well the lead fits the requisition, from 0 to 100. The three factors behind it are in top_factors, so a score never arrives without a reason.

top_factors array of object optional

The three factors that most influenced the score. Three, always, so a score is never a number without a reason.

2 fields
factor string required

The name of one of the three things that most influenced fit_score. Read it with contribution to see how far it moved the score.

contribution number required

How much this factor moved fit_score. Read the three together to see why the lead scored as it did.

consent LeadConsent required

Captured at first contact, before anything else is asked.

4 fields of LeadConsent
lawful_basis string required

The lawful basis you process this candidate's data under: consent (they agreed), legitimate_interest (your hiring interest) or contract (steps toward a contract with them). Recorded at first contact, before anything else is asked.

consentlegitimate_interestcontract
channel string required

The channel the candidate gave consent on. The channel agreed to nothing on their behalf; this is where the candidate themselves agreed.

wording_version string required

The exact wording presented. Answered from the record, not the current page.

captured_at string · date-time required

When the candidate gave consent, as an RFC 3339 timestamp in UTC. It comes before every answer the lead gave.

duplicate_of string · nullable optional

Set when this response matched an existing lead on contact identity.

promoted_subject_ref string · nullable optional

Set once you promote the lead at POST /v1/sourcing/leads/{lead_id}/promote, null before: the person's opaque reference, starting with sub_, that you send as subject_ref at POST /v1/identity/consent_tokens and POST /v1/identity/verifications.

captured_at string · date-time optional

When the lead came in, as an RFC 3339 timestamp in UTC. Screening can still be in_progress after this; it says when the candidate was captured, not when they were judged.

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

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), requisition_id (optional), outcome (optional)
curl -X GET "https://sandbox.droomwork.io/v1/sourcing/leads?limit=25&requisition_id=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=in_progress" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

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

// query parameters: limit (optional), starting_after (optional), requisition_id (optional), outcome (optional)
const result = await api.sourcingLeadsList({ limit: 25, requisitionId: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', outcome: 'in_progress' });
// query parameters: limit (optional), starting_after (optional), requisition_id (optional), outcome (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/leads?limit=25&requisition_id=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=in_progress', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

# query parameters: limit (optional), starting_after (optional), requisition_id (optional), outcome (optional)
result = api.sourcing_leads_list(limit=25, requisition_id='flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', outcome='in_progress')
import os

import requests

# query parameters: limit (optional), starting_after (optional), requisition_id (optional), outcome (optional)
response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/leads?limit=25&requisition_id=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=in_progress',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

# query parameters: limit (optional), starting_after (optional), requisition_id (optional), outcome (optional)
$result = $api->sourcingLeadsList(limit: 25, requisition_id: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', outcome: 'in_progress');
<?php
// query parameters: limit (optional), starting_after (optional), requisition_id (optional), outcome (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/leads?limit=25&requisition_id=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=in_progress');
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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;
import com.useheros.sdk.model.*;

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

// query parameters: limit (optional), starting_after (optional), requisition_id (optional), outcome (optional)
var result = api.sourcingLeadsList(25, null, "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", FlowScreeningOutcome.fromValue("in_progress"));
// query parameters: limit (optional), starting_after (optional), requisition_id (optional), outcome (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/leads?limit=25&requisition_id=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=in_progress"))
    .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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

// query parameters: limit (optional), starting_after (optional), requisition_id (optional), outcome (optional)
var result = api.SourcingLeadsList(limit: 25, requisitionId: "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", outcome: FlowScreeningOutcome.InProgress);
// query parameters: limit (optional), starting_after (optional), requisition_id (optional), outcome (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/sourcing/leads?limit=25&requisition_id=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=in_progress");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();
import heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

// query parameters: limit (optional), starting_after (optional), requisition_id (optional), outcome (optional)
result, _, err := client.FLOWAPI.SourcingLeadsList(ctx).Limit(25).RequisitionId("flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Outcome(heros.FlowScreeningOutcome("in_progress")).Execute()
// query parameters: limit (optional), starting_after (optional), requisition_id (optional), outcome (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/leads?limit=25&requisition_id=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=in_progress", 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": "flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "object": "lead",
      "livemode": false,
      "mocked": true,
      "requisition_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "requisition_version": 1,
      "fit_score": 0,
      "consent": {
        "lawful_basis": "consent",
        "channel": "example",
        "wording_version": "2026.08.1",
        "captured_at": "2026-09-01T09:00:00Z"
      },
      "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "screening_outcome": "in_progress",
      "top_factors": [
        {
          "factor": "example",
          "contribution": 1
        },
        {
          "factor": "example",
          "contribution": 1
        },
        {
          "factor": "example",
          "contribution": 1
        }
      ],
      "duplicate_of": "example",
      "promoted_subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "captured_at": "2026-09-01T09:00:00Z"
    }
  ],
  "has_more": true
}
POST/v1/sourcing/leads#

Record a lead

sourcing.leads.create

This records a candidate you already know about against one of your requisitions. It comes back scored, and the same reads work on it as on a lead that came in by itself, so one review queue in your product can hold both.

Send the requisition the candidate answered and the channel they came through. You get the lead back with its own id.

The requisition has to exist first, from POST /v1/sourcing/requisitions, and the channel has to be one from GET /v1/sourcing/channels.

Both fields are optional in the schema, and a lead is created without them. Send both: no later call changes the requisition or the channel on a lead.

Carry the id as lead_id to read the screening behind the outcome at GET /v1/sourcing/leads/{lead_id}/screening.

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

requisition_id string optional

The requisition the lead is for: the id of a requisition you created at POST /v1/sourcing/requisitions or listed at GET /v1/sourcing/requisitions. It starts with flow_enterprise_requisition_.

channel_id string optional

The channel the lead came through: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. Your yield at GET /v1/sourcing/analytics counts the lead against it.

Returns

The lead as recorded, with the id every later call about this candidate uses.

id string required

The lead's identifier. It starts with flow_enterprise_lead_ and never changes; pass it as lead_id on GET /v1/sourcing/leads/{lead_id} and the /screening, /attribution and /promote routes under it.

object always "lead" required

Always lead. 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.

requisition_id string required

The requisition this lead answered: its id from POST /v1/sourcing/requisitions, starting with flow_enterprise_requisition_. With requisition_version, it pins the exact version the lead was captured against.

requisition_version integer required

Which version produced this lead. A shortlist is reproducible against it.

channel_id string optional

The channel the lead came through: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. It is what your yield at GET /v1/sourcing/analytics counts against.

screening_outcome string optional
in_progresspassedfailedabandoned
fit_score number · minimum 0 · maximum 100 required

How well the lead fits the requisition, from 0 to 100. The three factors behind it are in top_factors, so a score never arrives without a reason.

top_factors array of object optional

The three factors that most influenced the score. Three, always, so a score is never a number without a reason.

2 fields
factor string required

The name of one of the three things that most influenced fit_score. Read it with contribution to see how far it moved the score.

contribution number required

How much this factor moved fit_score. Read the three together to see why the lead scored as it did.

consent LeadConsent required

Captured at first contact, before anything else is asked.

4 fields of LeadConsent
lawful_basis string required

The lawful basis you process this candidate's data under: consent (they agreed), legitimate_interest (your hiring interest) or contract (steps toward a contract with them). Recorded at first contact, before anything else is asked.

consentlegitimate_interestcontract
channel string required

The channel the candidate gave consent on. The channel agreed to nothing on their behalf; this is where the candidate themselves agreed.

wording_version string required

The exact wording presented. Answered from the record, not the current page.

captured_at string · date-time required

When the candidate gave consent, as an RFC 3339 timestamp in UTC. It comes before every answer the lead gave.

duplicate_of string · nullable optional

Set when this response matched an existing lead on contact identity.

promoted_subject_ref string · nullable optional

Set once you promote the lead at POST /v1/sourcing/leads/{lead_id}/promote, null before: the person's opaque reference, starting with sub_, that you send as subject_ref at POST /v1/identity/consent_tokens and POST /v1/identity/verifications.

captured_at string · date-time optional

When the lead came in, as an RFC 3339 timestamp in UTC. Screening can still be in_progress after this; it says when the candidate was captured, not when they were judged.

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/sourcing/leads" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"requisition_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","channel_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}'
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingLeadsCreate({});
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/leads', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    "requisition_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
    "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
  }),
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_leads_create()
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/sourcing/leads',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
        'Idempotency-Key': str(uuid.uuid4()),
    },
    json={"requisition_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingLeadsCreate();
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/leads');
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 => '{"requisition_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","channel_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}',
]);
$result = json_decode(curl_exec($ch), true);
import com.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingLeadsCreate();
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/leads"))
    .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("""
        {
          "requisition_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
          "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
        }
        """))
    .build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();
using Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingLeadsCreate();
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/sourcing/leads");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
    {
      "requisition_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
    }
    """, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();
import heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingLeadsCreate(ctx).Execute()
body := strings.NewReader(`{
  "requisition_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/sourcing/leads", 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": "flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "lead",
  "livemode": false,
  "mocked": true,
  "requisition_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "requisition_version": 1,
  "fit_score": 0,
  "consent": {
    "lawful_basis": "consent",
    "channel": "example",
    "wording_version": "2026.08.1",
    "captured_at": "2026-09-01T09:00:00Z"
  },
  "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "screening_outcome": "in_progress",
  "top_factors": [
    {
      "factor": "example",
      "contribution": 1
    },
    {
      "factor": "example",
      "contribution": 1
    },
    {
      "factor": "example",
      "contribution": 1
    }
  ],
  "duplicate_of": "example",
  "promoted_subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "captured_at": "2026-09-01T09:00:00Z"
}
GET/v1/sourcing/leads/{lead_id}#

Open one candidate's record

sourcing.leads.retrieve

This reads one lead back, which is one candidate's answer to one of your requisitions. So you can fill a candidate card in your product from the identifier you stored, without keeping your own copy of the score and the consent.

Send the lead's id in the path. You get the lead: its score, where screening has got to, and the channel it came through.

The lead carries the outcome of screening and nothing behind it. Read the questions at GET /v1/sourcing/leads/{lead_id}/screening, and the channel credit at GET /v1/sourcing/leads/{lead_id}/attribution.

Path parameters

lead_id string required

The lead's identifier: the id of a lead from GET /v1/sourcing/leads, from the response to POST /v1/sourcing/leads, or from a lead.captured webhook. It starts with flow_enterprise_lead_.

Returns

The candidate's record as it stands now: score, screening outcome, channel and consent.

id string required

The lead's identifier. It starts with flow_enterprise_lead_ and never changes; pass it as lead_id on GET /v1/sourcing/leads/{lead_id} and the /screening, /attribution and /promote routes under it.

object always "lead" required

Always lead. 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.

requisition_id string required

The requisition this lead answered: its id from POST /v1/sourcing/requisitions, starting with flow_enterprise_requisition_. With requisition_version, it pins the exact version the lead was captured against.

requisition_version integer required

Which version produced this lead. A shortlist is reproducible against it.

channel_id string optional

The channel the lead came through: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. It is what your yield at GET /v1/sourcing/analytics counts against.

screening_outcome string optional
in_progresspassedfailedabandoned
fit_score number · minimum 0 · maximum 100 required

How well the lead fits the requisition, from 0 to 100. The three factors behind it are in top_factors, so a score never arrives without a reason.

top_factors array of object optional

The three factors that most influenced the score. Three, always, so a score is never a number without a reason.

2 fields
factor string required

The name of one of the three things that most influenced fit_score. Read it with contribution to see how far it moved the score.

contribution number required

How much this factor moved fit_score. Read the three together to see why the lead scored as it did.

consent LeadConsent required

Captured at first contact, before anything else is asked.

4 fields of LeadConsent
lawful_basis string required

The lawful basis you process this candidate's data under: consent (they agreed), legitimate_interest (your hiring interest) or contract (steps toward a contract with them). Recorded at first contact, before anything else is asked.

consentlegitimate_interestcontract
channel string required

The channel the candidate gave consent on. The channel agreed to nothing on their behalf; this is where the candidate themselves agreed.

wording_version string required

The exact wording presented. Answered from the record, not the current page.

captured_at string · date-time required

When the candidate gave consent, as an RFC 3339 timestamp in UTC. It comes before every answer the lead gave.

duplicate_of string · nullable optional

Set when this response matched an existing lead on contact identity.

promoted_subject_ref string · nullable optional

Set once you promote the lead at POST /v1/sourcing/leads/{lead_id}/promote, null before: the person's opaque reference, starting with sub_, that you send as subject_ref at POST /v1/identity/consent_tokens and POST /v1/identity/verifications.

captured_at string · date-time optional

When the lead came in, as an RFC 3339 timestamp in UTC. Screening can still be in_progress after this; it says when the candidate was captured, not when they were judged.

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/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingLeadsRetrieve({ leadId: 'flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_leads_retrieve(lead_id='flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingLeadsRetrieve(lead_id: 'flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingLeadsRetrieve("flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingLeadsRetrieve(leadId: "flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_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 heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingLeadsRetrieve(ctx, "flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_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": "flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "lead",
  "livemode": false,
  "mocked": true,
  "requisition_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "requisition_version": 1,
  "fit_score": 0,
  "consent": {
    "lawful_basis": "consent",
    "channel": "example",
    "wording_version": "2026.08.1",
    "captured_at": "2026-09-01T09:00:00Z"
  },
  "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "screening_outcome": "in_progress",
  "top_factors": [
    {
      "factor": "example",
      "contribution": 1
    },
    {
      "factor": "example",
      "contribution": 1
    },
    {
      "factor": "example",
      "contribution": 1
    }
  ],
  "duplicate_of": "example",
  "promoted_subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "captured_at": "2026-09-01T09:00:00Z"
}
GET/v1/sourcing/leads/{lead_id}/screening#

See the working behind a pass or fail

sourcing.screenings.retrieve

This opens the screening one lead's outcome came from. The lead itself carries the outcome and nothing behind it, so this is where your product reads why a candidate was dropped, question by question.

Send the lead's id in the path. You get that lead's one screening: its outcome, the surface it ran on, the exchanges behind it, and decided_by.

The rule a decision names is one of the requisition's own, listed at GET /v1/sourcing/requisitions/{requisition_id}/knockout_rules.

Path parameters

lead_id string required

The lead's identifier: the id of a lead from GET /v1/sourcing/leads, from the response to POST /v1/sourcing/leads, or from a lead.captured webhook. It starts with flow_enterprise_lead_.

Returns

Every question this candidate was asked, and what your rule decided on each answer.

object always "screening" required

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

lead_id string required

The lead this screening belongs to: the lead_id you put in the path at GET /v1/sourcing/leads/{lead_id}/screening, that lead's id starting with flow_enterprise_lead_. Returned so the record stands on its own.

outcome string required
in_progresspassedfailedabandoned
surface string required

Where the candidate answered: whatsapp (in a WhatsApp conversation) or web (on a web page). The rules that decide pass and fail are the same on either.

whatsappweb
exchanges array of object required

One entry per question put to the candidate: the question, how it was answered, what was extracted, and what the rule decided. Never more than the question_cap on the requisition's knockout rules, five by default.

4 fields
question string required

The question as it was put to the candidate. The wording can differ from the knockout rule's question; the rule it was asked for is named in decision.rule.

answer_mode string required

How the candidate answered: text (typed) or voice (spoken). Either way, extracted holds what was taken from the answer and the same rule judges it.

textvoice
extracted object required

What was extracted from the candidate's answer. It decides nothing.

3 fields
field string optional

The fact the answer was read for, named as your rule names it: years_experience for a rule of years_experience >= 3.

value string optional

What the answer was taken to say for that field, as a string. It is what the rule is applied to; a wrong value here is an extraction problem, not a rule problem.

confidence number optional

How confident the extraction is that value is what the candidate meant. It changes no decision; a low figure beside a failed rule tells you to check the extraction first.

decision object required

The decision on the answer, and the rule that made it. This is the only thing that decides.

3 fields
rule string required

The knockout rule that was applied, written out exactly as you gave it, such as years_experience >= 3. What it decided is in passed.

passed boolean required

true when the extracted value met the rule, false when it didn't. Decided by the rule alone, never by the conversation.

rule_pack_version string optional

The version of the rule pack that judged this answer, such as 2026.08.1. Rules change between versions, and this names the one that applied.

decided_by always "rule_pack" optional

Always rule_pack. The decision comes from your knockout rules, never from the conversation.

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/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/screening" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingScreeningsRetrieve({ leadId: 'flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/screening', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_screenings_retrieve(lead_id='flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/screening',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingScreeningsRetrieve(lead_id: 'flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/screening');
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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingScreeningsRetrieve("flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/screening"))
    .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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingScreeningsRetrieve(leadId: "flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/screening");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();
import heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingScreeningsRetrieve(ctx, "flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/screening", 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": "screening",
  "lead_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "outcome": "in_progress",
  "surface": "whatsapp",
  "exchanges": [
    {
      "question": "example",
      "answer_mode": "text",
      "extracted": {
        "field": "gross.amount",
        "value": "example",
        "confidence": 1
      },
      "decision": {
        "rule": "years_experience >= 3",
        "passed": true,
        "rule_pack_version": "2026.08.1"
      }
    }
  ],
  "decided_by": "rule_pack"
}
POST/v1/sourcing/leads/{lead_id}/promote#

Promote a lead toward identity

sourcing.leads.promote

This moves one candidate out of sourcing and on toward identity. So a candidate who answered your advert does not become a person in your records until you say so, one lead at a time.

Send the lead's id in the path and no request body. You get the lead back, with its fit_score and the requisition_version that produced it.

A lead has to exist first, either one the channels brought you or one you recorded at POST /v1/sourcing/leads.

Promotion goes one way. No call takes a lead back out, so promote when you have decided about the candidate and not before.

Path parameters

lead_id string required

The lead's identifier: the id of a lead from GET /v1/sourcing/leads, from the response to POST /v1/sourcing/leads, or from a lead.captured webhook. It starts with flow_enterprise_lead_.

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 same lead record, now promoted, to store against the candidate in your product.

id string required

The lead's identifier. It starts with flow_enterprise_lead_ and never changes; pass it as lead_id on GET /v1/sourcing/leads/{lead_id} and the /screening, /attribution and /promote routes under it.

object always "lead" required

Always lead. 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.

requisition_id string required

The requisition this lead answered: its id from POST /v1/sourcing/requisitions, starting with flow_enterprise_requisition_. With requisition_version, it pins the exact version the lead was captured against.

requisition_version integer required

Which version produced this lead. A shortlist is reproducible against it.

channel_id string optional

The channel the lead came through: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. It is what your yield at GET /v1/sourcing/analytics counts against.

screening_outcome string optional
in_progresspassedfailedabandoned
fit_score number · minimum 0 · maximum 100 required

How well the lead fits the requisition, from 0 to 100. The three factors behind it are in top_factors, so a score never arrives without a reason.

top_factors array of object optional

The three factors that most influenced the score. Three, always, so a score is never a number without a reason.

2 fields
factor string required

The name of one of the three things that most influenced fit_score. Read it with contribution to see how far it moved the score.

contribution number required

How much this factor moved fit_score. Read the three together to see why the lead scored as it did.

consent LeadConsent required

Captured at first contact, before anything else is asked.

4 fields of LeadConsent
lawful_basis string required

The lawful basis you process this candidate's data under: consent (they agreed), legitimate_interest (your hiring interest) or contract (steps toward a contract with them). Recorded at first contact, before anything else is asked.

consentlegitimate_interestcontract
channel string required

The channel the candidate gave consent on. The channel agreed to nothing on their behalf; this is where the candidate themselves agreed.

wording_version string required

The exact wording presented. Answered from the record, not the current page.

captured_at string · date-time required

When the candidate gave consent, as an RFC 3339 timestamp in UTC. It comes before every answer the lead gave.

duplicate_of string · nullable optional

Set when this response matched an existing lead on contact identity.

promoted_subject_ref string · nullable optional

Set once you promote the lead at POST /v1/sourcing/leads/{lead_id}/promote, null before: the person's opaque reference, starting with sub_, that you send as subject_ref at POST /v1/identity/consent_tokens and POST /v1/identity/verifications.

captured_at string · date-time optional

When the lead came in, as an RFC 3339 timestamp in UTC. Screening can still be in_progress after this; it says when the candidate was captured, not when they were judged.

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 POST "https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/promote" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingLeadsPromote({ leadId: 'flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/promote', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Idempotency-Key': crypto.randomUUID(),
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_leads_promote(lead_id='flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/promote',
    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 = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingLeadsPromote(lead_id: 'flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/promote');
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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingLeadsPromote("flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/promote"))
    .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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingLeadsPromote(leadId: "flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/promote");
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 heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingLeadsPromote(ctx, "flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/promote", 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": "flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "lead",
  "livemode": false,
  "mocked": true,
  "requisition_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "requisition_version": 1,
  "fit_score": 0,
  "consent": {
    "lawful_basis": "consent",
    "channel": "example",
    "wording_version": "2026.08.1",
    "captured_at": "2026-09-01T09:00:00Z"
  },
  "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "screening_outcome": "in_progress",
  "top_factors": [
    {
      "factor": "example",
      "contribution": 1
    },
    {
      "factor": "example",
      "contribution": 1
    },
    {
      "factor": "example",
      "contribution": 1
    }
  ],
  "duplicate_of": "example",
  "promoted_subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "captured_at": "2026-09-01T09:00:00Z"
}
GET/v1/sourcing/leads/{lead_id}/attribution#

See where a candidate came from

sourcing.attributions.retrieve

This says which channel a lead first came through, and which one it came through last. So you can credit a hire to the channel that found the candidate, and tell that apart from the one they came back through.

Send the lead's id in the path. You get first_touch and last_touch, each naming a channel and when the contact happened.

A touch carries capture_link_id only when the candidate followed a link, so check it is there before you read it.

Carry a touch's channel_id to GET /v1/sourcing/channels/{channel_id} to see what that channel costs you.

Path parameters

lead_id string required

The lead's identifier: the id of a lead from GET /v1/sourcing/leads, from the response to POST /v1/sourcing/leads, or from a lead.captured webhook. It starts with flow_enterprise_lead_.

Returns

Where this candidate first and last came from, for crediting a channel with the hire.

object always "attribution" required

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

lead_id string required

The lead this attribution belongs to: the lead_id from the path at GET /v1/sourcing/leads/{lead_id}/attribution, starting with flow_enterprise_lead_. It survives promotion, so you can still ask which channel produced the person.

first_touch object required

The first contact this lead made: which channel it came through, the capture link if one was used, and when. Written once and never changed.

3 fields
channel_id string required

The channel the first contact came through: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. Written once and never changed.

capture_link_id string optional

The capture link the first contact came through: the id of a link from POST /v1/sourcing/requisitions/{requisition_id}/capture_links, starting with flow_enterprise_lead_. Absent when the candidate arrived without a link.

at string · date-time required

When the first contact happened, as an RFC 3339 timestamp in UTC.

last_touch object required

The latest contact this lead made: which channel it came through, the capture link if one was used, and when. Equal to first_touch when there was only one contact.

3 fields
channel_id string required

The channel the latest contact came through: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. Equal to first_touch.channel_id when there was only one contact.

capture_link_id string optional

The capture link the latest contact came through: the id of a link from POST /v1/sourcing/requisitions/{requisition_id}/capture_links, starting with flow_enterprise_lead_. Absent when the candidate arrived without a link.

at string · date-time required

When the latest contact happened, as an RFC 3339 timestamp in UTC.

referring_party string · nullable optional

Who referred the candidate, when the lead came by referral, and null when nobody did. Like the touches, it can't change once written.

survives_promotion always true optional

Always true. Promoting the lead toward identity leaves this record in place, so you can ask which channel produced a hire long after the hire.

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/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/attribution" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingAttributionsRetrieve({ leadId: 'flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/attribution', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_attributions_retrieve(lead_id='flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/attribution',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingAttributionsRetrieve(lead_id: 'flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/attribution');
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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingAttributionsRetrieve("flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/attribution"))
    .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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingAttributionsRetrieve(leadId: "flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/attribution");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();
import heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingAttributionsRetrieve(ctx, "flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/leads/flow_enterprise_lead_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/attribution", 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": "attribution",
  "lead_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "first_touch": {
    "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
    "at": "2026-09-01T09:00:00Z",
    "capture_link_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
  },
  "last_touch": {
    "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
    "at": "2026-09-01T09:00:00Z",
    "capture_link_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
  },
  "referring_party": "example",
  "survives_promotion": true
}
GET/v1/sourcing/analytics#

See which channels produce hires

sourcing.analytics.retrieve

The channel that sends the most leads is not always the one that fills the job. This is what your dashboard argues next quarter's sourcing budget from, and it runs as far as the hire.

Send nothing to get every channel your organisation has used, or narrow the figures with requisition_id, from and to. You get one entry per channel for the period you asked for.

The one money figure here is cost_per_qualified_lead, and it is not always present. What you have spent against a ceiling, and what is left of it, are at GET /v1/sourcing/spend_ceilings.

A channel arrives as a channel_id and no name. Pass it to GET /v1/sourcing/channels/{channel_id} for the label you put in front of a person.

Query parameters

requisition_id string optional

Restricts the figures to one requisition: its id from POST /v1/sourcing/requisitions or GET /v1/sourcing/requisitions, starting with flow_enterprise_requisition_. Leave it out to get your yield across every requisition.

from string optional

The earliest date to count from, as YYYY-MM-DD. Leave it out and nothing is cut off at the start; pair it with to to bound a period.

to string optional

The latest date to count to, as YYYY-MM-DD. Leave it out and the figures run to today; pair it with from to bound a period.

Returns

Each channel's counts and cost over the period, for comparing one channel with another.

object always "channel_analytics" required

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

scope always "requesting_organisation" required

Your own figures only. Never another organisation's yield on a channel.

channels array of object required

One entry per channel: the leads it captured, how many qualified, how many you promoted and how many were hired, and what each qualified lead cost you there. Your own figures only.

6 fields
channel_id string required

The channel these figures are for, by its id as GET /v1/sourcing/channels lists it, prefix flow_enterprise_broadcast_. Pass it to GET /v1/sourcing/channels/{channel_id} to read the channel.

captured integer required

How many leads this channel captured in the period. One person answering twice on the same requisition is one lead, so they count once.

qualified integer required

How many of this channel's leads passed your knockout rules in the period. Divide it by captured for the channel's qualification rate.

promoted integer required

How many of those leads you promoted toward identity in the period, with POST /v1/sourcing/leads/{lead_id}/promote.

hired integer required

How many of those leads went on to be hired in the period. Attribution survives promotion, so the hire is still credited to the channel that produced the lead.

cost_per_qualified_lead 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.

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: requisition_id (optional), from (optional), to (optional)
curl -X GET "https://sandbox.droomwork.io/v1/sourcing/analytics?requisition_id=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&from=2026-01-01&to=2027-01-01" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

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

// query parameters: requisition_id (optional), from (optional), to (optional)
const result = await api.sourcingAnalyticsRetrieve({ requisitionId: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', from: '2026-01-01', to: '2027-01-01' });
// query parameters: requisition_id (optional), from (optional), to (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/analytics?requisition_id=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&from=2026-01-01&to=2027-01-01', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

# query parameters: requisition_id (optional), from (optional), to (optional)
result = api.sourcing_analytics_retrieve(requisition_id='flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', from='2026-01-01', to='2027-01-01')
import os

import requests

# query parameters: requisition_id (optional), from (optional), to (optional)
response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/analytics?requisition_id=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&from=2026-01-01&to=2027-01-01',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

# query parameters: requisition_id (optional), from (optional), to (optional)
$result = $api->sourcingAnalyticsRetrieve(requisition_id: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', from: '2026-01-01', to: '2027-01-01');
<?php
// query parameters: requisition_id (optional), from (optional), to (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/analytics?requisition_id=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&from=2026-01-01&to=2027-01-01');
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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

// query parameters: requisition_id (optional), from (optional), to (optional)
var result = api.sourcingAnalyticsRetrieve("flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "2026-01-01", "2027-01-01");
// query parameters: requisition_id (optional), from (optional), to (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/analytics?requisition_id=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&from=2026-01-01&to=2027-01-01"))
    .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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

// query parameters: requisition_id (optional), from (optional), to (optional)
var result = api.SourcingAnalyticsRetrieve(requisitionId: "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", from: "2026-01-01", to: "2027-01-01");
// query parameters: requisition_id (optional), from (optional), to (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/sourcing/analytics?requisition_id=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&from=2026-01-01&to=2027-01-01");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();
import heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

// query parameters: requisition_id (optional), from (optional), to (optional)
result, _, err := client.FLOWAPI.SourcingAnalyticsRetrieve(ctx).RequisitionId("flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").From("2026-01-01").To("2027-01-01").Execute()
// query parameters: requisition_id (optional), from (optional), to (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/analytics?requisition_id=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&from=2026-01-01&to=2027-01-01", 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": "channel_analytics",
  "scope": "requesting_organisation",
  "channels": [
    {
      "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
      "captured": 1,
      "qualified": 1,
      "promoted": 1,
      "hired": 1,
      "cost_per_qualified_lead": {
        "amount": 1500000,
        "currency": "NGN"
      }
    }
  ]
}
GET/v1/sourcing/readiness#

Check FLOW is ready to source

sourcing.readiness.retrieve

What FLOW needs before it will source is published here, so your code reads one field where a person would otherwise read a checklist. Nothing is created or changed here, so an onboarding screen or a setup script can read it before anything else runs.

Send no parameters. You get ready as the single answer, the requirements behind it, and the gates no configuration of yours can lift.

Returns

What FLOW requires of you, and whether it has what it 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 Heros 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.

channels_without_credentials array of string optional

The channels you hold no credentials for. Nothing broadcasts to one of them until you supply the credentials; credentials_held on the channel says the same thing.

wallet_funded boolean optional

Paid channels cannot run without it, and the ceiling check refuses first.

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/sourcing/readiness" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingReadinessRetrieve({});
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/readiness', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_readiness_retrieve()
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/readiness',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingReadinessRetrieve();
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingReadinessRetrieve();
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingReadinessRetrieve();
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/sourcing/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 heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingReadinessRetrieve(ctx).Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/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": "flow",
  "mode": "integrated",
  "ready": true,
  "rows": [],
  "always_enforced": [
    "consent_token",
    "bank_account_validation",
    "tax_identifier_validation",
    "duplicate_detection"
  ],
  "channels_without_credentials": [],
  "wallet_funded": true
}
GET/v1/sourcing/deliveries/{delivery_id}#

What one channel did with a send

sourcing.deliveries.retrieve

A delivery is one channel's record of one broadcast. This reads one back on its own, so your product can open the single channel somebody is asking about without pulling a whole send.

Send the delivery's id in the path and you get that one record, with the broadcast and the requisition it belongs to.

POST /v1/sourcing/broadcasts/{broadcast_id}/deliveries/{delivery_id}/retry takes the broadcast's id as well as the delivery's, and this record carries both. An id that matches no delivery of yours answers not_found.

Path parameters

delivery_id string required

The delivery's identifier, starting with flow_enterprise_delivery_: the id of an entry in deliveries on a broadcast, of a delivery listed at GET /v1/sourcing/deliveries, or of one you recorded at POST /v1/sourcing/deliveries.

Returns

One channel's record of one send, for the detail behind a single outcome.

id string required

The delivery's identifier, starting with flow_enterprise_delivery_; it never changes. It is in a broadcast's deliveries, at GET /v1/sourcing/deliveries and from POST /v1/sourcing/deliveries; pass it as delivery_id to read or retry it.

object always "channel_delivery" required

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

broadcast_id string required

The broadcast this delivery is part of: the id, starting with flow_enterprise_broadcast_, from POST /v1/sourcing/requisitions/{requisition_id}/broadcasts, or the one you named as broadcast_id at POST /v1/sourcing/deliveries.

requisition_id string optional

The requisition this delivery carried: the id of the requisition behind the broadcast, starting with flow_enterprise_requisition_, from POST /v1/sourcing/requisitions. Filter GET /v1/sourcing/deliveries by it to see every channel it went to.

channel_id string required

The channel this delivery went to: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. One delivery covers one channel, so a broadcast has one delivery per channel it went to.

state string required

failed is terminal and always reported. A channel you selected is never dropped silently.

queuedsendingdeliveredretryingfailed
attempts integer · minimum 0 required

How many times posting to this channel has been tried so far, 0 before the first try. Read it with state and last_fault to see how a channel is doing.

next_attempt_at string · date-time · nullable optional

Exponential backoff. Absent once the state is terminal.

last_fault string · nullable optional

Why the most recent attempt at this channel failed, in plain words; null while nothing has failed. Read it before you retry.

external_reference string · nullable optional

The channel's own reference for this posting, where the channel gives one. null otherwise, so name the delivery by id, never by this.

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/sourcing/deliveries/%7Bdelivery_id%7D" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingDeliveriesRetrieve({ deliveryId: '{delivery_id}' });
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/deliveries/%7Bdelivery_id%7D', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_deliveries_retrieve(delivery_id='{delivery_id}')
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/deliveries/%7Bdelivery_id%7D',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingDeliveriesRetrieve(delivery_id: '{delivery_id}');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/deliveries/%7Bdelivery_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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingDeliveriesRetrieve("{delivery_id}");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/deliveries/%7Bdelivery_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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingDeliveriesRetrieve(deliveryId: "{delivery_id}");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/sourcing/deliveries/%7Bdelivery_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 heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingDeliveriesRetrieve(ctx, "{delivery_id}").Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/deliveries/%7Bdelivery_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": "flow_enterprise_delivery_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "channel_delivery",
  "broadcast_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "channel_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "state": "queued",
  "attempts": 0,
  "requisition_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "next_attempt_at": "2026-09-01T09:00:00Z",
  "last_fault": "The register did not answer within the timeout.",
  "external_reference": "paye-2026-09-rivers"
}
POST/v1/sourcing/requisitions/{requisition_id}/quarantine#

Hold a role back before it goes out

sourcing.requisitions.quarantine

This holds a role back: it stays readable, and it stops being one you can publish. So a complaint about the wording, or a check of your own, can stop a vacancy on the record itself and not in somebody's inbox.

Send the requisition's id in the path and no request body. The requisition comes back in quarantined, and a requisition.quarantined event goes to your webhook endpoints.

Hold a role before POST /v1/sourcing/requisitions/{requisition_id}/publish has run on it. One already published answers conflict.

No call here takes a role back out of it, and publishing is refused with conflict while it is held. Where the wording is the problem, a fresh POST /v1/sourcing/requisitions is the way on.

Path parameters

requisition_id string required

The requisition's identifier, starting with flow_enterprise_requisition_: the id returned by POST /v1/sourcing/requisitions or listed at GET /v1/sourcing/requisitions, or one of the requisition_ids on a bulk upload.

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 requisition, held where you can still read it but not publish it.

id string required

The requisition's identifier, starting with flow_enterprise_requisition_; it never changes. You get it from POST /v1/sourcing/requisitions (or in requisition_ids on a bulk upload) and pass it as requisition_id on every call about it.

object always "requisition" required

Always requisition. 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

A requisition sits in pending_confirmation while an inferred pay figure, headcount, location or start date is unconfirmed. It cannot leave that state until you confirm.

pending_confirmationdraftunder_reviewquarantinedpublishedclosed
version integer · minimum 1 required

An edit after first broadcast makes a new version. Leads stay on theirs.

job_title string required

The title of the role as you gave it. The occupation code is inferred from it when you don't send one, so a local or colloquial title is fine.

occupation_code string optional

The occupation this role maps to in the taxonomy, such as ng-7412. Inferred from the title when you didn't send one, and listed in inferred_fields until you confirm it.

employment_type string optional
permanentfixed_termcontractcasualapprenticeship
headcount integer · minimum 1 required

How many people you are hiring for this role, at least 1. It never broadcasts on inference alone; confirm it if it appears in inferred_fields.

compensation Compensation optional
3 fields of Compensation
amount Money required
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.

period string required

The span the amount pays for: monthly, annual, daily or per_task (one payment for each task done). Required whenever you send compensation.

monthlyannualdailyper_task
negotiable boolean optional

Whether the figure is open to negotiation: true when it is, false when it is fixed.

location Location optional
3 fields of Location
description string required

Where the work is, in words a candidate would recognise, such as Ikeja, Lagos. The one part of a location you must give.

state string optional

The Nigerian state the work is in, such as Lagos. Optional; the description alone is enough to create the requisition.

geofence_radius_metres integer · nullable optional

A radius around the location, in whole metres, or null when there is none. If it had to be inferred it comes back in inferred_fields for you to confirm.

starts_on string · date · nullable optional

The date the role starts, as YYYY-MM-DD, or null when none is set. It never broadcasts on inference alone; confirm it if it appears in inferred_fields.

inferred_fields array of string optional

Fields that were inferred rather than read from what you sent. Each has to be confirmed, and compensation, headcount, location and start date can never broadcast while they are here.

quarantine one of optional

Why the requisition was held: the rule it tripped, the rule pack version, the detail and the offending text. null when nothing held it; fix what it names before you publish.

QuarantineFindingor
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/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/quarantine" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingRequisitionsQuarantine({ requisitionId: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/quarantine', {
  method: 'POST',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
    'Idempotency-Key': crypto.randomUUID(),
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_requisitions_quarantine(requisition_id='flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')
import os
import uuid

import requests

response = requests.request(
    'POST',
    'https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/quarantine',
    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 = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingRequisitionsQuarantine(requisition_id: 'flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/quarantine');
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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingRequisitionsQuarantine("flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/quarantine"))
    .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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingRequisitionsQuarantine(requisitionId: "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/quarantine");
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 heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingRequisitionsQuarantine(ctx, "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/sourcing/requisitions/flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/quarantine", 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": "flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
  "object": "requisition",
  "livemode": false,
  "mocked": true,
  "status": "quarantined",
  "version": 1,
  "job_title": "example",
  "headcount": 1,
  "occupation_code": "ng-7412",
  "employment_type": "permanent",
  "compensation": {
    "amount": {
      "amount": 7500000,
      "currency": "NGN"
    },
    "period": "monthly",
    "negotiable": true
  },
  "location": {
    "description": "example",
    "state": "Lagos",
    "geofence_radius_metres": 1
  },
  "starts_on": "2026-09-01",
  "inferred_fields": [
    "example"
  ],
  "quarantine": {
    "rule_code": "discriminatory_criteria",
    "rule_pack_version": "2026.08.1",
    "detail": "The payee has no verified destination, so this line cannot be paid.",
    "contested": true,
    "offending_text": "example"
  },
  "created_at": "2026-09-01T09:00:00Z"
}
GET/v1/sourcing/events#

Replay one record's event history

sourcing.events.list

FLOW keeps each record's history as events, and this is where you read it back. So a webhook receiver that was down can catch up from the last event it handled, and your product can show how a requisition reached its current state.

Send nothing and your events come back oldest first, across every record, each under an evt_ identifier. Name one record's id as stream, with the sequence you last handled as after, and you get that record's own history in sequence order.

Across records, events come back in the order FLOW recorded them, which is not always occurred_at order. Sort on occurred_at yourself where a reader needs the timeline.

Read any one of them again at GET /v1/sourcing/events/{event_id}.

Query parameters

stream string optional

The id of the record whose events you want, such as a requisition's id from POST /v1/sourcing/requisitions, prefix flow_enterprise_requisition_. Leave it out to get events across every record, paged with starting_after.

after integer optional

The sequence of the last event you handled on this stream, as it reads on each event at GET /v1/sourcing/events; you get what came after it, and 0 reads from the start. 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 events, oldest first, to replay one record's history in order.

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 Heros 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 requisition's id from POST /v1/sourcing/requisitions (flow_enterprise_requisition_). sequence counts within this stream only; replay with stream and after at GET /v1/sourcing/events.

data object required

The record the event is about, in the shape its type names: a requisition, a channel delivery, a lead, or your spend ceilings. 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/sourcing/events?stream=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&limit=25" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

const api = new FLOWApi(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.sourcingEventsList({ stream: 'flow_enterprise_requisition_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/sourcing/events?stream=flow_enterprise_requisition_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&limit=25', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

# query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
result = api.sourcing_events_list(stream='flow_enterprise_requisition_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/sourcing/events?stream=flow_enterprise_requisition_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 = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

# query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
$result = $api->sourcingEventsList(stream: 'flow_enterprise_requisition_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/sourcing/events?stream=flow_enterprise_requisition_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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
var result = api.sourcingEventsList("flow_enterprise_requisition_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/sourcing/events?stream=flow_enterprise_requisition_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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
var result = api.SourcingEventsList(stream: "flow_enterprise_requisition_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/sourcing/events?stream=flow_enterprise_requisition_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 heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
result, _, err := client.FLOWAPI.SourcingEventsList(ctx).Stream("flow_enterprise_requisition_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/sourcing/events?stream=flow_enterprise_requisition_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/sourcing/events/{event_id}#

Open one event by its identifier

sourcing.events.retrieve

This turns one event identifier into the whole event: what happened, which of your records it happened to, and where it sits in that record's history. So an identifier you kept from a webhook delivery, or from your own log, is enough to show the event again.

Send the event's id in the path, the one starting evt_, and nothing else. You get that one event in full.

Pass this event's stream and sequence to GET /v1/sourcing/events as stream and after to read what came next. An identifier that is not yours reads the same as one that never existed, so this cannot tell you what another organisation holds.

Path parameters

event_id string required

The event's id, as it reads on an event you listed at GET /v1/sourcing/events or received on a webhook delivery. It starts with evt_.

Returns

The one event, with the record it is about and its place in that record's history.

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 Heros 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 requisition's id from POST /v1/sourcing/requisitions (flow_enterprise_requisition_). sequence counts within this stream only; replay with stream and after at GET /v1/sourcing/events.

data object required

The record the event is about, in the shape its type names: a requisition, a channel delivery, a lead, or your spend ceilings. 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/sourcing/events/%7Bevent_id%7D" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingEventsRetrieve({ eventId: '{event_id}' });
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/events/%7Bevent_id%7D', {
  method: 'GET',
  headers: {
    'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
  },
});
const result = await response.json();
import os

import heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_events_retrieve(event_id='{event_id}')
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/events/%7Bevent_id%7D',
    headers={
        'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
    },
)
result = response.json()
<?php
require_once __DIR__ . '/vendor/autoload.php';

$config = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingEventsRetrieve(event_id: '{event_id}');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingEventsRetrieve("{event_id}");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingEventsRetrieve(eventId: "{event_id}");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/sourcing/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 heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingEventsRetrieve(ctx, "{event_id}").Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/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/sourcing/audit_entries#

Who changed what, and who was refused

sourcing.audit_entries.list

This answers who changed something in your organisation and what came of it. So your product can show who published a requisition or promoted a lead, and which attempts were refused, without keeping a log of its own.

Send nothing, or narrow by actor, by record, by outcome, or to one window of time. Entries come back newest first.

A value for action that is not exactly what an entry stores gives you an empty page, not a refusal. So read an empty page as a question about the spelling first.

This log covers the sourcing routes only. An attempt on another part of the platform is in that part's own list.

Open any one of them again at GET /v1/sourcing/audit_entries/{audit_entry_id}.

Query parameters

action string optional

The method and route pattern that was attempted, exactly as action reads on each entry, such as POST /v1/sourcing/requisitions. Leave it out to get attempts on every route.

actor_id string optional

The id of the API key (prefix key_) or staff account that made the attempt, exactly as actor_id reads on each entry. Leave it out to get every actor; pair it with outcome=refused to see repeated refusals on one credential.

resource string optional

The collection segment of the route that was attempted, exactly as resource reads on each entry, such as requisitions, broadcasts or leads. Leave it out to get every kind of record; pair it with resource_id to narrow to one.

resource_id string optional

The id of the record the route named, exactly as resource_id reads on each entry, such as a requisition's id from POST /v1/sourcing/requisitions (flow_enterprise_requisition_). Leave it out to get every record; pair it with resource.

outcome string optional

One outcome only: succeeded (a 2xx answer), refused (a 4xx) or failed (a 5xx). Leave it out to get every outcome; ask for refused to see every attempt that was turned away.

succeededrefusedfailed
recorded_after string optional

Only entries made after this moment, as an RFC 3339 timestamp in UTC, compared with at on each entry; an entry at exactly this instant is left out. Leave it out to reach back to your oldest entry.

recorded_before string optional

Only entries made before this moment, as an RFC 3339 timestamp in UTC, compared with at on each entry; an entry at exactly this instant is left out. Leave it out to read up to now; pair it with recorded_after to read one window.

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 attempts, newest first, with the actor, the route and the status for each.

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 aud_ and never changes; pass it to GET /v1/sourcing/audit_entries/{audit_entry_id} to read this entry again, 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 is the route and not the answer: a refused attempt and a replayed idempotent request aimed at a mocked route both say 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.

request_id string required

The Droomwork-Request-Id the call was answered with. It is on every response, refusals included, so a refusal you were shown can be matched to its entry here.

actor_type string required

What kind of credential made the attempt. client is one of your API keys or OAuth clients; staff is a Heros staff member on a support grant, the same access /v1/support_access shows you.

actor_id string required

Who made the attempt: with actor_type client, the API key's id as GET /v1/api_keys lists it or the client_id you send to POST /v1/oauth/token, prefix key_; with staff, the staff account's id. Filter by it to follow one credential.

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 name in the route, such as requisitions or leads. null when the call reached no route at all.

resource_id string · nullable optional

The id of the record the route named, such as a requisition's id (flow_enterprise_requisition_) on POST /v1/sourcing/requisitions/{requisition_id}/publish. null for an attempt on a collection, such as creating or listing.

outcome string required

How the attempt ended: succeeded for a 2xx status, refused for a 4xx, failed for a 5xx. It is read from status, so the two never disagree.

succeededrefusedfailed
status integer required

The HTTP status the caller was given, such as 201 or 403. It is what outcome is read from.

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/sourcing/audit_entries?action=POST%20%2Fv1%2Fsourcing%2Frequisitions&actor_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=requisitions&resource_id=flow_enterprise_requisition_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, FLOWApi } from '@heros/sdk';

const api = new FLOWApi(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.sourcingAuditEntriesList({ action: 'POST /v1/sourcing/requisitions', actorId: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', resource: 'requisitions', resourceId: 'flow_enterprise_requisition_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/sourcing/audit_entries?action=POST%20%2Fv1%2Fsourcing%2Frequisitions&actor_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=requisitions&resource_id=flow_enterprise_requisition_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 heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(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.sourcing_audit_entries_list(action='POST /v1/sourcing/requisitions', actor_id='sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', resource='requisitions', resource_id='flow_enterprise_requisition_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/sourcing/audit_entries?action=POST%20%2Fv1%2Fsourcing%2Frequisitions&actor_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=requisitions&resource_id=flow_enterprise_requisition_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 = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(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->sourcingAuditEntriesList(action: 'POST /v1/sourcing/requisitions', actor_id: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', resource: 'requisitions', resource_id: 'flow_enterprise_requisition_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/sourcing/audit_entries?action=POST%20%2Fv1%2Fsourcing%2Frequisitions&actor_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=requisitions&resource_id=flow_enterprise_requisition_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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
FlowApi api = new FlowApi(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.sourcingAuditEntriesList("POST /v1/sourcing/requisitions", "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "requisitions", "flow_enterprise_requisition_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/sourcing/audit_entries?action=POST%20%2Fv1%2Fsourcing%2Frequisitions&actor_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=requisitions&resource_id=flow_enterprise_requisition_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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(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.SourcingAuditEntriesList(action: "POST /v1/sourcing/requisitions", actorId: "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", resource: "requisitions", resourceId: "flow_enterprise_requisition_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/sourcing/audit_entries?action=POST%20%2Fv1%2Fsourcing%2Frequisitions&actor_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=requisitions&resource_id=flow_enterprise_requisition_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 heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.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.FLOWAPI.SourcingAuditEntriesList(ctx).Action("POST /v1/sourcing/requisitions").ActorId("sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Resource("requisitions").ResourceId("flow_enterprise_requisition_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/sourcing/audit_entries?action=POST%20%2Fv1%2Fsourcing%2Frequisitions&actor_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=requisitions&resource_id=flow_enterprise_requisition_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": "aud_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": "requisitions",
      "resource_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
    }
  ],
  "has_more": true
}
GET/v1/sourcing/audit_entries/{audit_entry_id}#

Open one recorded attempt

sourcing.audit_entries.retrieve

This turns one entry identifier into the attempt it names: who made it, what they aimed at, and the answer they were given. So wherever your product keeps that identifier, a reader can open the same recorded attempt again instead of searching the log for it.

Send the entry's id in the path and nothing else. You get that one entry, carrying the request_id of the attempt it records, which is what lines it up against your own logs.

An identifier that is not yours reads the same as one that never existed, so this cannot tell you what another organisation holds.

Path parameters

audit_entry_id string required

The entry's id, as it reads on an entry you listed at GET /v1/sourcing/audit_entries. It starts with aud_.

Returns

The one attempt: who made it, what it was aimed at, and the answer given.

id string required

The entry's identifier. It starts with aud_ and never changes; pass it to GET /v1/sourcing/audit_entries/{audit_entry_id} to read this entry again, 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 is the route and not the answer: a refused attempt and a replayed idempotent request aimed at a mocked route both say 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.

request_id string required

The Droomwork-Request-Id the call was answered with. It is on every response, refusals included, so a refusal you were shown can be matched to its entry here.

actor_type string required

What kind of credential made the attempt. client is one of your API keys or OAuth clients; staff is a Heros staff member on a support grant, the same access /v1/support_access shows you.

actor_id string required

Who made the attempt: with actor_type client, the API key's id as GET /v1/api_keys lists it or the client_id you send to POST /v1/oauth/token, prefix key_; with staff, the staff account's id. Filter by it to follow one credential.

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 name in the route, such as requisitions or leads. null when the call reached no route at all.

resource_id string · nullable optional

The id of the record the route named, such as a requisition's id (flow_enterprise_requisition_) on POST /v1/sourcing/requisitions/{requisition_id}/publish. null for an attempt on a collection, such as creating or listing.

outcome string required

How the attempt ended: succeeded for a 2xx status, refused for a 4xx, failed for a 5xx. It is read from status, so the two never disagree.

succeededrefusedfailed
status integer required

The HTTP status the caller was given, such as 201 or 403. It is what outcome is read from.

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/sourcing/audit_entries/%7Baudit_entry_id%7D" \
  -H "Droomwork-Api-Key: $DROOMWORK_API_KEY"
import { Configuration, FLOWApi } from '@heros/sdk';

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

const result = await api.sourcingAuditEntriesRetrieve({ auditEntryId: '{audit_entry_id}' });
const response = await fetch('https://sandbox.droomwork.io/v1/sourcing/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 heros_sdk

config = heros_sdk.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = heros_sdk.ApiClient(config)
api = heros_sdk.FLOWApi(client)

result = api.sourcing_audit_entries_retrieve(audit_entry_id='{audit_entry_id}')
import os

import requests

response = requests.request(
    'GET',
    'https://sandbox.droomwork.io/v1/sourcing/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 = HerosSdk\Configuration::getDefaultConfiguration()
  ->setHost('https://sandbox.droomwork.io')
  ->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new HerosSdk\Api\FLOWApi(new GuzzleHttp\Client(), $config);

$result = $api->sourcingAuditEntriesRetrieve(audit_entry_id: '{audit_entry_id}');
<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/sourcing/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.useheros.sdk.ApiClient;
import com.useheros.sdk.Configuration;
import com.useheros.sdk.api.FlowApi;

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

var result = api.sourcingAuditEntriesRetrieve("{audit_entry_id}");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/sourcing/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 Heros.Sdk.Api;
using Heros.Sdk.Client;

var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new FLOWApi(config);

var result = api.SourcingAuditEntriesRetrieve(auditEntryId: "{audit_entry_id}");
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/sourcing/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 heros "github.com/fenibofubara/heros-sdk-go"

ctx := context.WithValue(context.Background(), heros.ContextAPIKeys, map[string]heros.APIKey{
	"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := heros.NewConfiguration()
cfg.Servers = heros.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := heros.NewAPIClient(cfg)

result, _, err := client.FLOWAPI.SourcingAuditEntriesRetrieve(ctx, "{audit_entry_id}").Execute()
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/sourcing/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": "aud_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": "requisitions",
  "resource_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}