Bulk data requests
A bulk data request — a set — is one data request definition over many passports: the same ontology, the same level, the same requested properties, asked of a list of passports or of every child of one passport at a level. Each passport gets an ordinary data request of its own, the set's child, which is answered, reviewed and applied exactly as a single request is. The set is the bulk view on top: one page of children, one Excel workbook out and back, one mass fulfilment from the supplier's own passports, one submit all and one accept and apply all.
Kestrel Energy selects the cells of a pack and asks Nordvolt Cells for each cell's capacity and supplier part number. Nordvolt downloads the workbook, fills two columns and uploads it — or matches the rows to its own cell passports and lets Synex draft the answers — then submits all; Kestrel accepts and applies all. Both teams read the same set, each from its own side.
A child carries set: {id, position, identifier_value}. GET …/children lists one set's children in row order; they also appear in GET /v1/data_requests beside single requests, told apart by a non-null set. Each is read, answered, reviewed or applied one at a time through /v1/data_requests/:id like any other request. The set travels over a supplier link, as a single request does.
Sides and abilities
The abilities are the data requests' own. Opening a set and applying it also need passports:write, because a request is a passport write taken on credit.
| Operation | Ability | Side |
|---|---|---|
GET /v1/data_request_sets | data_requests:read | Either — direction picks |
POST /v1/data_request_sets | data_requests:write and passports:write covering the passports' ontology | Customer |
GET /v1/data_request_sets/:id, …/children, …/jobs/:job | data_requests:read | Either |
GET …/export, GET …/uploads/:upload | data_requests:read | Either |
POST …/uploads | data_requests:write | Supplier |
GET …/fulfil/sources | data_requests:write | Supplier |
POST …/fulfil/match | data_requests:write, plus passports:read on the chosen ontology | Supplier |
POST …/fulfil/fill | data_requests:write, plus passports:read on each source | Supplier |
POST …/submit_all | data_requests:write | Supplier |
POST …/apply_all | data_requests:write and passports:write covering the passports' ontology | Customer |
POST …/cancel | data_requests:write | Customer |
Every POST accepts an optional Idempotency-Key header, the upload included.
A set belongs to the team, not to an ontology, so data_requests:* cannot
be restricted to particular ontologies: a key whose grant carries an
ontology scope is refused with 403 ability_not_scoped_to_resource on every
route here. A set your team is on neither side of — or on the wrong side of
for the call — reads as 404 resource_missing, the same answer another
tenant gets.
The lifecycle
A set is creating while a queued job makes its children, open once they exist and at least one is not terminal, and closed once every child is — closed_reason: "settled" when they all settled, "cancelled" when the customer cancelled the set. The status is derived from the children and never set directly: a child applied through its own POST /v1/data_requests/:id/apply moves the set's counts as a bulk apply would, and the last child to settle closes the set.
counts tallies the children by status and is where a poller reads how far an open set has got. While the set is creating, progress mirrors the create job's counters and create_job names the job.
Jobs
Every bulk action — making the children, importing an upload, drafting from your passports, submitting all, applying all — runs as a queued job. The call answers with a data_request_set_job row (202; a create answers 201 with the set and names the job in create_job). Poll GET …/jobs/:job until status is done or failed: progress.done and progress.total move while it runs, progress.failed names what it could not process and why, and the kind's own lists say what became of the rest. Nothing is rolled back across children — one refusal never undoes another child's success.
kind | Queued by | Lists in progress | result once terminal |
|---|---|---|---|
create | Opening a set | failed: {id, error}, id the passport's pass_ id | {created, failed} |
import | An upload | none — done and total count sheet rows; refusals are on the upload's errors | {rows, valid, invalid, submitted} |
fill | fulfil/fill | filled: {id, source, filled, of, caveats}; failed: {id, source, error} | {requests, filled, of, failed} |
submit | submit_all | submitted, applied, blocked: {id, label, reason}; failed: {id, label, error} | {submitted, applied, blocked, refused} |
apply | apply_all | applied, returned: {id, label}; failed: {id, label, error} | {applied, returned, failed} |
In those rows id is the child's dreq_ id (except on create), label its passport label, error the code the single operation was refused with, and reason the apply_blocked_reason of a child whose automatic apply was held back. A fill's filled and of count the fields it drafted over the fields still open, and caveats carries the converter's {property_id, kind, message} notes on what it copied. A failed job adds error to result; a create job cut short by a cancel ends done with stopped: "set_closed".
The data request set model
- Name
object- Type
- string
- Description
Always
data_request_set.
- Name
id- Type
- string
- Description
Unique identifier, e.g.
dset_7b3e9c1a5d2f8460. The same on both sides.
- Name
status- Type
- string
- Description
creating,open, orclosed— see the lifecycle.
- Name
closed_reason- Type
- string
- Description
settledorcancelledonce the set isclosed;nulluntil then.
- Name
passport_count- Type
- integer
- Description
How many passports the selection resolved to — the number of children once they are made.
- Name
depth- Type
- object
- Description
The level every passport of the set sits at: its
keyandname(nullable).
- Name
ontology- Type
- object
- Description
The passports' ontology: its
id, theversionpinned at create, and thedefault_languagea bare answer to a multilingual property is read in.
- Name
properties- Type
- array
- Description
The requested property ids, in the order the supplier sees them.
- Name
definitions- Type
- array
- Description
The requested properties' definitions as pinned at create —
id,name,type,value(the type's rules) anddata_element_idwhere set. Every answer is validated against these.
- Name
identifier- Type
- object
- Description
What the supplier matches rows on:
{"kind": "friendly_id"}for the customer's friendly ids, or{"kind": "property", "property_id", "name", "type"}for a property of the level.nullwhen the customer marked none.
- Name
selection- Type
- object
- Description
The selection as the customer gave it —
mode: "list"with theids(trimmed, each once), ormode: "children"with theparent'spass_id and thelevel.
- Name
message- Type
- string
- Description
The customer's note to the supplier, shown on every child. Nullable.
- Name
auto_apply- Type
- boolean
- Description
Whether each child's submission is applied the moment it arrives.
- Name
due_at- Type
- string
- Description
The advisory deadline, ISO 8601. Nullable.
- Name
supplier- Type
- object
- Description
The supplier link the set travels over, as it stands now:
id(sup_…),nameandstatus(pending,linkedorrevoked).
- Name
customer_team- Type
- object
- Description
The customer team:
idandname.
- Name
supplier_team- Type
- object
- Description
The supplier team:
idandname.nullwhile the link's invitation is unaccepted.
- Name
requested_by- Type
- object
- Description
The user who opened the set:
idandname. Nullable.
- Name
cancelled_by- Type
- object
- Description
The user who cancelled the set. Nullable.
- Name
counts- Type
- object
- Description
The children by status —
total,open,submitted,changes_requested,applied,cancelled. Computed per read.totalis the number of children made so far, belowpassport_countwhilecreating.
- Name
create_job- Type
- string
- Description
The
dsj_id of the job that made (or is making) the children.
- Name
progress- Type
- object
- Description
The create job's
done,totalandfailedwhile the set iscreating;nullotherwise.
- Name
created_at- Type
- string
- Description
ISO 8601 timestamp of creation.
- Name
closed_at- Type
- string
- Description
ISO 8601 timestamp the set closed. Nullable.
- Name
updated_at- Type
- string
- Description
ISO 8601 timestamp of the last change.
- Name
viewer_role- Type
- string
- Description
customerorsupplier— which side of the set your team is on. Present on a single set (a read, a create, a cancel), not on list rows. Both sides see the same fields.
The data request set job model
- Name
object- Type
- string
- Description
Always
data_request_set_job.
- Name
id- Type
- string
- Description
Unique identifier, e.g.
dsj_9c2e4a7b1d8f3056.
- Name
set- Type
- string
- Description
The
dset_id of the set the job runs on.
- Name
kind- Type
- string
- Description
create,import,fill,submit, orapply— see jobs.
- Name
status- Type
- string
- Description
queued,running,done, orfailed. The last two are terminal.
- Name
progress- Type
- object
- Description
done,totalandfailedalways, plus the lists the kind tracks.
- Name
result- Type
- object
- Description
The kind's summary once the job is terminal, with
erroradded on a failure.nulluntil then.
- Name
started_by- Type
- object
- Description
The user who started the job:
idandname. Nullable.
- Name
created_at- Type
- string
- Description
ISO 8601 timestamp the job was queued.
- Name
updated_at- Type
- string
- Description
ISO 8601 timestamp of the last progress write.
The data request set upload model
- Name
object- Type
- string
- Description
Always
data_request_set_upload.
- Name
id- Type
- string
- Description
Unique identifier, e.g.
dsu_4e1b7d9a2c6f8035.
- Name
set- Type
- string
- Description
The
dset_id of the set.
- Name
job- Type
- string
- Description
The
dsj_id of theimportjob reading the workbook.
- Name
status- Type
- string
- Description
processing,done, orfailed, following the job.
- Name
submit_on_upload- Type
- boolean
- Description
Whether every row that imported clean was submitted straight away.
- Name
file- Type
- string
- Description
The
file_id of the workbook as kept, in the supplier's Drive.
- Name
errors_file- Type
- string
- Description
The
file_id of the errors workbook — the uploaded sheet with anErrorcolumn — in the supplier's Drive. It is written the first time someone downloads the errors in the Synex app, not by the API, so an API-only integration seesnullhere and readserrorsinstead.
- Name
summary- Type
- object
- Description
Once
done:rows(data rows read),valid(rows drafted),invalid(rows refused),submitted(rows submitted undersubmit_on_upload), andsheet(the 0-based index of the worksheet read).nullwhile processing.
- Name
errors- Type
- array
- Description
Every refusal, at most 500:
row(the 1-based sheet row, as Excel numbers it),column(the heading concerned —Passport IDfor a row that could not be matched;nullwhen the sheet has noPassport IDcolumn),property_id(nullable),code,message, andpassport(the row'spass_id once matched, otherwisenull). See the codes.
- Name
uploaded_by- Type
- object
- Description
The user who uploaded the workbook:
idandname. Nullable.
- Name
created_at- Type
- string
- Description
ISO 8601 timestamp of the upload.
- Name
updated_at- Type
- string
- Description
ISO 8601 timestamp of the last change.
List bulk data requests
Lists your team's sets, newest first and without viewer_role. direction picks the side: the sets your team opened as the customer, or the ones addressed to it as the supplier. Returns a paginated list. Requires data_requests:read.
Optional attributes
- Name
direction- Type
- string
- Description
sent(default) — sets your team opened as the customer;received— your inbox as the supplier, where a set stillcreatingis never listed.
- Name
status- Type
- string
- Description
creating,open, orclosed. An unknown value is400 parameter_invalidrather than an empty page.
- Name
supplier- Type
- string
- Description
Only sets travelling over this supplier link, by
sup_id.
- Name
ontology- Type
- string
- Description
Only sets over passports of this ontology, by
ontology_id.
- Name
limit- Type
- integer
- Description
Page size, 1–100 (default 10).
- Name
cursor- Type
- string
- Description
A pagination cursor from a previous response's
next_cursor.
direction and every filter join the cursor's scope: a cursor minted under one combination is 400 invalid_cursor under another.
Request
curl -G https://api.synexcloud.com/v1/data_request_sets \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-d direction=sent \
-d status=open
Response
{
"object": "list",
"data": [
{
"object": "data_request_set",
"id": "dset_7b3e9c1a5d2f8460",
"status": "open",
"closed_reason": null,
"passport_count": 3,
"depth": { "key": "B", "name": "Cell" },
"ontology": {
"id": "ontology_00a1b2c3d4e5f607",
"version": 3,
"default_language": "en"
},
"properties": ["property_capacity", "property_part_no"],
"definitions": [
{
"id": "property_capacity",
"name": "Capacity",
"type": "measurement",
"value": { "measurement": "mAh" }
},
{
"id": "property_part_no",
"name": "Supplier part no.",
"type": "short_text",
"value": { "required": false }
}
],
"identifier": { "kind": "friendly_id" },
"selection": {
"mode": "children",
"parent": "pass_3e9a1c5b7d2f4083",
"level": "B"
},
"message": "Cell data for the Q4 audit, please.",
"auto_apply": false,
"due_at": "2026-10-15T00:00:00Z",
"supplier": {
"id": "sup_7c2e9a4f1b8d3506",
"name": "Nordvolt Cells",
"status": "linked"
},
"customer_team": { "id": 4821, "name": "Kestrel Energy" },
"supplier_team": { "id": 5107, "name": "Nordvolt Cells AB" },
"requested_by": { "id": 9031, "name": "Mara Lindqvist" },
"cancelled_by": null,
"counts": {
"total": 3,
"open": 2,
"submitted": 1,
"changes_requested": 0,
"applied": 0,
"cancelled": 0
},
"create_job": "dsj_1f4a8c2e6b9d3075",
"progress": null,
"created_at": "2026-09-20T09:12:40Z",
"closed_at": null,
"updated_at": "2026-09-20T09:40:02Z"
}
],
"has_more": false,
"next_cursor": null,
"url": "/v1/data_request_sets"
}
Open a bulk data request
Opens one request for named properties over many passports of one level, addressed to a supplier you already have (supplier) or to an address you invite on the spot (recipient). The children are made by a queued create job: the answer is 201 with the set creating, create_job the job to poll and progress its counters. Requires data_requests:write and passports:write covering the passports' ontology — 403 ability_missing (param: "selection") without it, checked before anything is looked up. Accepts an optional Idempotency-Key header.
Required attributes
- Name
selection- Type
- object
- Description
The passports.
{"mode": "list", "ids": [...]}— up to 500pass_ids or friendly ids of your team, in the order the rows should take; or{"mode": "children", "parent": "...", "level": "B"}— every passport atlevelbeneathparent(apass_id or friendly id). The level must lie below the parent, or it is400 parameter_invalidonselection.level.
- Name
properties- Type
- array
- Description
The property ids to ask for — defined at the selection's level in its pinned version, and carrying a fillable value. Duplicates are collapsed. Anything else is
400 parameter_invalid, keyedproperties.0,properties.1, ….
- Name
supplier- Type
- string
- Description
The
sup_id of apendingorlinkedsupplier of yours. Required unlessrecipientis given; a revoked link is409 link_revoked.
- Name
recipient- Type
- object
- Description
{"email", "name"}of an address to invite as a new supplier, opening a pending link with the set. Required unlesssupplieris given; an address already linked or invited is409 supplier_already_linked. Name exactly one of the two.
Optional attributes
- Name
identifier- Type
- object
- Description
What the supplier will match rows on:
{"kind": "friendly_id"}for your friendly ids, or{"kind": "property", "property_id": "..."}for a property of the level that carries a value. Omit it for none — the supplier then pairs rows by hand.
- Name
message- Type
- string
- Description
A note to the supplier, shown on every child. At most 2,000 characters.
- Name
auto_apply- Type
- boolean
- Description
Apply each child's submission the moment it arrives. Defaults to
false.
- Name
due_at- Type
- string
- Description
An advisory deadline — any date after today.
Resolving the selection
The selection is resolved before anything is written, all or nothing. Every passport must be your team's and share one level of one ontology on one pinned version, anchored by the first passport that is. An id that is not a passport of your team, a friendly id that names more than one passport, or a passport in another ontology, at another level or on another version refuses the whole call with 400 selection_invalid — error.errors keyed by the offending id, one message each — and nothing is opened. More than 500 passports is 400 selection_too_large.
Children are made in the order the passports were given — set.position on each — and the set turns open when the job finishes. data_request_set.created fires on your feed then, and data_request_set.received on the supplier's once a supplier team holds the link.
Request
curl https://api.synexcloud.com/v1/data_request_sets \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Idempotency-Key: cells-q4-audit" \
-H "Content-Type: application/json" \
-d '{
"selection": { "mode": "children", "parent": "pass_3e9a1c5b7d2f4083", "level": "B" },
"identifier": { "kind": "friendly_id" },
"properties": ["property_capacity", "property_part_no"],
"supplier": "sup_7c2e9a4f1b8d3506",
"message": "Cell data for the Q4 audit, please.",
"due_at": "2026-10-15"
}'
Response
{
"object": "data_request_set",
"id": "dset_7b3e9c1a5d2f8460",
"status": "creating",
"closed_reason": null,
"passport_count": 3,
"depth": { "key": "B", "name": "Cell" },
"ontology": {
"id": "ontology_00a1b2c3d4e5f607",
"version": 3,
"default_language": "en"
},
"properties": ["property_capacity", "property_part_no"],
"definitions": [
{
"id": "property_capacity",
"name": "Capacity",
"type": "measurement",
"value": { "measurement": "mAh" }
},
{
"id": "property_part_no",
"name": "Supplier part no.",
"type": "short_text",
"value": { "required": false }
}
],
"identifier": { "kind": "friendly_id" },
"selection": {
"mode": "children",
"parent": "pass_3e9a1c5b7d2f4083",
"level": "B"
},
"message": "Cell data for the Q4 audit, please.",
"auto_apply": false,
"due_at": "2026-10-15T00:00:00Z",
"supplier": {
"id": "sup_7c2e9a4f1b8d3506",
"name": "Nordvolt Cells",
"status": "linked"
},
"customer_team": { "id": 4821, "name": "Kestrel Energy" },
"supplier_team": { "id": 5107, "name": "Nordvolt Cells AB" },
"requested_by": { "id": 9031, "name": "Mara Lindqvist" },
"cancelled_by": null,
"counts": {
"total": 0,
"open": 0,
"submitted": 0,
"changes_requested": 0,
"applied": 0,
"cancelled": 0
},
"create_job": "dsj_1f4a8c2e6b9d3075",
"progress": { "done": 0, "total": 3, "failed": [] },
"created_at": "2026-09-20T09:12:40Z",
"closed_at": null,
"updated_at": "2026-09-20T09:12:40Z",
"viewer_role": "customer"
}
400
{
"error": {
"type": "invalid_request_error",
"code": "selection_invalid",
"message": "One or more selected passports cannot be requested together.",
"param": "selection",
"errors": {
"LOT-9": ["\"LOT-9\" is not a passport of this team."],
"pass_00a1b2c3d4e5f607": ["\"pass_00a1b2c3d4e5f607\" is at level A, not B."]
},
"request_id": "req_9f2c1ab34d5e6f70"
}
}
Retrieve a bulk data request
Retrieves one set, from either side; viewer_role says which side your team is on. Poll it while creating, or read counts to see how far an open set has got. Requires data_requests:read.
Request
curl https://api.synexcloud.com/v1/data_request_sets/dset_7b3e9c1a5d2f8460 \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "data_request_set",
"id": "dset_7b3e9c1a5d2f8460",
"status": "open",
"closed_reason": null,
"passport_count": 3,
"depth": { "key": "B", "name": "Cell" },
"ontology": {
"id": "ontology_00a1b2c3d4e5f607",
"version": 3,
"default_language": "en"
},
"properties": ["property_capacity", "property_part_no"],
"definitions": [
{
"id": "property_capacity",
"name": "Capacity",
"type": "measurement",
"value": { "measurement": "mAh" }
},
{
"id": "property_part_no",
"name": "Supplier part no.",
"type": "short_text",
"value": { "required": false }
}
],
"identifier": {
"kind": "property",
"property_id": "property_part_no",
"name": "Supplier part no.",
"type": "short_text"
},
"selection": { "mode": "list", "ids": ["LOT-1", "LOT-2", "LOT-3"] },
"message": null,
"auto_apply": false,
"due_at": null,
"supplier": {
"id": "sup_7c2e9a4f1b8d3506",
"name": "Nordvolt Cells",
"status": "linked"
},
"customer_team": { "id": 4821, "name": "Kestrel Energy" },
"supplier_team": { "id": 5107, "name": "Nordvolt Cells AB" },
"requested_by": { "id": 9031, "name": "Mara Lindqvist" },
"cancelled_by": null,
"counts": {
"total": 3,
"open": 2,
"submitted": 1,
"changes_requested": 0,
"applied": 0,
"cancelled": 0
},
"create_job": "dsj_1f4a8c2e6b9d3075",
"progress": null,
"created_at": "2026-09-20T09:12:40Z",
"closed_at": null,
"updated_at": "2026-09-20T09:40:02Z",
"viewer_role": "supplier"
}
List a set's children
Lists the set's data requests as list rows (no values), in row order — the order the passports were selected in, set.position on each row — not newest first. Either side. To read one child with its values, or to answer, review or apply it alone, use /v1/data_requests/:id: a child is an ordinary request. Returns a paginated list. Requires data_requests:read.
Optional attributes
- Name
status- Type
- string
- Description
Only children in this status —
open,submitted,changes_requested,applied, orcancelled. An unknown value is400 parameter_invalid.
- Name
limit- Type
- integer
- Description
Page size, 1–100 (default 10).
- Name
cursor- Type
- string
- Description
A pagination cursor from a previous response's
next_cursor.
The cursor carries the position of the last row handed out, so a walk resumes exactly after it whatever changes on the children in between. It is scoped to the set and the status filter, and opaque like every cursor — a bare position is 400 invalid_cursor. The envelope carries no counts; read them on the set.
Request
curl -G https://api.synexcloud.com/v1/data_request_sets/dset_7b3e9c1a5d2f8460/children \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-d status=submitted \
-d limit=100
Response
{
"object": "list",
"data": [
{
"object": "data_request",
"id": "dreq_5a1c8e3f9b2d7046",
"status": "submitted",
"closed_reason": null,
"apply_blocked_reason": null,
"auto_apply": false,
"message": null,
"due_at": null,
"revision": 1,
"passport": {
"snowflake": "pass_6b0d3f8a2c5e1794",
"label": "LOT-1",
"ontology_id": "ontology_00a1b2c3d4e5f607",
"ontology_version": 3,
"default_language": "en"
},
"set": {
"id": "dset_7b3e9c1a5d2f8460",
"position": 0,
"identifier_value": "SPN-1"
},
"supplier": {
"id": "sup_7c2e9a4f1b8d3506",
"name": "Nordvolt Cells",
"status": "linked"
},
"customer_team": { "id": 4821, "name": "Kestrel Energy" },
"supplier_team": { "id": 5107, "name": "Nordvolt Cells AB" },
"requested_by": { "id": 9031, "name": "Mara Lindqvist" },
"submitted_by": { "id": 11208, "name": "Erna Kuvalis" },
"reviewed_by": null,
"applied_by": null,
"cancelled_by": null,
"source_passport": "pass_8d2f6a1c4e9b3057",
"counts": { "total": 2, "accepted": 0, "rejected": 0, "applied": 0 },
"created_at": "2026-09-20T09:13:02Z",
"submitted_at": "2026-09-22T10:05:22Z",
"reviewed_at": null,
"applied_at": null,
"cancelled_at": null,
"updated_at": "2026-09-22T10:05:22Z"
}
],
"has_more": true,
"next_cursor": "c_9f3a2b1c8d7e6f50",
"url": "/v1/data_request_sets/dset_7b3e9c1a5d2f8460/children"
}
Retrieve a job
Retrieves one bulk job of the set — the row a 202 answered with, or the set's create_job — from either side. Poll it until status is done or failed; progress moves while it runs and result fills in at the end. See jobs for what each kind reports. A job is looked up under its set only: one that is not this set's is 404 resource_missing, whoever started it. Requires data_requests:read.
Request
curl https://api.synexcloud.com/v1/data_request_sets/dset_7b3e9c1a5d2f8460/jobs/dsj_9c2e4a7b1d8f3056 \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "data_request_set_job",
"id": "dsj_9c2e4a7b1d8f3056",
"set": "dset_7b3e9c1a5d2f8460",
"kind": "apply",
"status": "done",
"progress": {
"done": 1,
"total": 1,
"failed": [],
"applied": [{ "id": "dreq_5a1c8e3f9b2d7046", "label": "LOT-1" }],
"returned": []
},
"result": { "applied": 1, "returned": 0, "failed": 0 },
"started_by": { "id": 9031, "name": "Mara Lindqvist" },
"created_at": "2026-09-23T08:00:00Z",
"updated_at": "2026-09-23T08:00:04Z"
}
The Excel round trip
Either side can download the set as an .xlsx workbook; the supplier fills it in and uploads it back. The workbook is generated on request from the children as they stand, so it doubles as a report.
The Answers sheet has one row per child, in row order. Three identity columns come first — Passport (the passport's label), Passport ID (its pass_ id) and Identifier (Friendly ID), Identifier (Supplier part no.) or a bare Identifier (the value the customer marked) — then one column per requested property, headed by the property's name. Cells are prefilled with whatever has been drafted or submitted so far: tags joined with , , booleans as true or false, files by their filename.
| Passport | Passport ID | Identifier (Friendly ID) | Capacity | Supplier part no. |
|---|---|---|---|---|
| LOT-1 | pass_6b0d3f8a2c5e1794 | LOT-1 | 3000 | SPN-1 |
- A multilingual property (
short_text,long_text,tags,file) has its bare column in the set'sdefault_language, plus a<Name> (DE)-style column for every other language a row already holds. Add more columns for other EU languages as you need them —(FR),[fr]and- frmarkers are all read. - A measurement column holds the bare number; the unit is not in the heading.
- When two columns would share a heading — two requested properties with the same name, or one named like an identity column — the property's id heads its column instead.
The Properties sheet lists each requested property — its column, id, type, unit, whether it is required, and what a cell may hold — followed by the round-trip rules.
On upload, the sheet read is the one whose header has a Passport ID column, else the first. Then:
- Rows are matched by
Passport ID, never by their order. Keep that column as it is;PassportandIdentifierare for reference and are not read. - Columns are matched by heading — a property's name or id, case-insensitively, with an optional language marker. An
Errorcolumn is skipped too, so a corrected errors workbook can be uploaded as it is; any other heading is reported once asunknown_columnand ignored. - An empty cell leaves the value alone. A cell holding only
-clears it — the whole property in a bare column, one translation in a language column. A multilingual value merges per language: the columns you send replace the translations they name and leave the rest. - File and image cells take the filename of a file in your Drive (the only file of that name) or its
file_…id. - Every cell is validated against the definitions pinned at create, as a submit is. One refused cell refuses its whole row and nothing of that row is drafted; the clean rows are.
Download the workbook
Returns the set as an .xlsx attachment, not JSON — see the Excel round trip for the layout. Either side. The attachment is named after the set: its level, its passport count and its id. Requires data_requests:read.
Request
curl https://api.synexcloud.com/v1/data_request_sets/dset_7b3e9c1a5d2f8460/export \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-o answers.xlsx
Response
HTTP/1.1 200 OK
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Content-Disposition: attachment; filename="Cell x 3 (dset_7b3e9c1a5d2f8460).xlsx"
X-Request-Id: req_9f2c1ab34d5e6f70
Upload an answers workbook
Sends the filled-in workbook back as multipart/form-data. The file is kept in your Drive — in a folder named after the set under /Data requests/ — and read by a queued import job: the answer is 202 with the upload, processing, whose job you poll (or poll the upload itself) until it is done or failed. The supplier's call. Requires data_requests:write. Accepts an optional Idempotency-Key header, whose fingerprint covers the file's name, size and content.
A set still creating takes no upload (409 data_request_set_creating), nor does a closed one (409 data_request_set_closed).
Required attributes
- Name
file- Type
- string
- Description
The workbook, sent as a binary
multipart/form-datapart — an.xlsxof at most 10 MB. Anything else is400 parameter_invalidwithparam: "file".
Optional attributes
- Name
submit_on_upload- Type
- boolean
- Description
Submit every row that imports clean as soon as it is drafted (
trueor1) — and, on anauto_applyset, apply it. A row whose submit is refused is reported assubmit_refused. Defaults tofalse.
Request
curl https://api.synexcloud.com/v1/data_request_sets/dset_7b3e9c1a5d2f8460/uploads \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Idempotency-Key: answers-2026-09-22" \
-F "file=@answers.xlsx" \
-F "submit_on_upload=1"
Response
{
"object": "data_request_set_upload",
"id": "dsu_4e1b7d9a2c6f8035",
"set": "dset_7b3e9c1a5d2f8460",
"job": "dsj_3a8c5e1f7b2d9064",
"status": "processing",
"submit_on_upload": true,
"file": "file_2d9f7a1c4e8b3056",
"errors_file": null,
"summary": null,
"errors": [],
"uploaded_by": { "id": 11208, "name": "Erna Kuvalis" },
"created_at": "2026-09-22T10:02:11Z",
"updated_at": "2026-09-22T10:02:11Z"
}
Retrieve an upload
Retrieves one upload of the set, from either side, as the import left it: summary counts the rows and errors says which cells were refused and why. file lives in the supplier's Drive — the supplier's key downloads it with GET /v1/files/:file/download; the customer reads errors here and has no download. An upload that is not this set's is 404 resource_missing. Requires data_requests:read.
Error codes
An entry's code is the value validator's own — invalid_number, invalid_measurement, invalid_boolean, value_too_large and the like, exactly as a direct passport edit reports them — or one of the importer's:
- Name
passport_id_column_missing- Type
- code
- Description
The sheet has no
Passport IDcolumn, so no row could be matched. Nothing was read.
- Name
unknown_column- Type
- code
- Description
A heading that is none of the requested properties, or a language column on a property with no translations. Reported on row 1; the column is ignored and the rows are not refused for it.
- Name
unknown_passport- Type
- code
- Description
An empty
Passport IDcell, or an id that is not a passport of this set.
- Name
duplicate_row- Type
- code
- Description
A second row for the same passport. Only its first row was read.
- Name
row_closed- Type
- code
- Description
The row's child is no longer open to answers —
submitted,appliedorcancelled.
- Name
invalid_file- Type
- code
- Description
A file or image cell that names no single file of your Drive.
- Name
submit_refused- Type
- code
- Description
Under
submit_on_upload, the row drafted but its submit was refused — a required field still blank, say. The draft stays.
Request
curl https://api.synexcloud.com/v1/data_request_sets/dset_7b3e9c1a5d2f8460/uploads/dsu_4e1b7d9a2c6f8035 \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "data_request_set_upload",
"id": "dsu_4e1b7d9a2c6f8035",
"set": "dset_7b3e9c1a5d2f8460",
"job": "dsj_3a8c5e1f7b2d9064",
"status": "done",
"submit_on_upload": true,
"file": "file_2d9f7a1c4e8b3056",
"errors_file": null,
"summary": {
"rows": 3,
"valid": 2,
"invalid": 1,
"submitted": 2,
"sheet": 0
},
"errors": [
{
"row": 2,
"column": "Capacity",
"property_id": "property_capacity",
"code": "invalid_measurement",
"message": "Expected a numeric measurement.",
"passport": "pass_6b0d3f8a2c5e1794"
}
],
"uploaded_by": { "id": 11208, "name": "Erna Kuvalis" },
"created_at": "2026-09-22T10:02:11Z",
"updated_at": "2026-09-22T10:02:39Z"
}
Fulfilling from your own passports
When the customer marked an identifier, the supplier can pair the set's rows with its own passports and let Synex draft the answers from them — the bulk form of a single request's prefill. Three calls, all the supplier's:
GET …/fulfil/sources— your ontologies, their levels and the properties that carry a value: where a match can look.POST …/fulfil/match— which level to read and which property holds the customer's identifier value. Each row comes backmatched,ambiguousorunmatched. Nothing is stored.POST …/fulfil/fill— the pairs you accept: the matched rows as returned, the others paired by hand with any passport of yours. A queued job drafts each child from its source.
Nothing is submitted. Review the drafts, then submit every child. A set with no identifier cannot be matched (409 identifier_missing) — pair its rows by hand and go straight to fill.
List match sources
Lists your ontologies with their levels and, per level, the properties that carry a value — the pick list for a match. It covers the first 50 of your ontologies, as GET /v1/ontologies lists them, so an ontology-scoped ontologies:read narrows it the same way. A fixed list, never paged. The supplier's call. Requires data_requests:write.
Request
curl https://api.synexcloud.com/v1/data_request_sets/dset_7b3e9c1a5d2f8460/fulfil/sources \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "list",
"data": [
{
"id": "ontology_9b2d7e4f1a8c3065",
"name": "Supplier cells",
"default_language": "en",
"levels": [
{
"key": "S",
"name": "Pack",
"properties": [
{ "id": "property_pack_name", "name": "Pack name", "type": "short_text" }
]
},
{
"key": "T",
"name": "Cell",
"properties": [
{ "id": "property_t1", "name": "Supplier part no.", "type": "short_text" },
{ "id": "property_t2", "name": "Capacity", "type": "measurement" }
]
}
]
}
],
"has_more": false,
"next_cursor": null,
"url": "/v1/data_request_sets/dset_7b3e9c1a5d2f8460/fulfil/sources"
}
Match rows to your passports
Pairs every child still open to answers (open or changes_requested), in row order, with the passport of yours that carries the same identifier value, at one level of one of your ontologies. Exactly one passport is matched (in source, and alone in candidates), several are ambiguous (all in candidates, source null), none is unmatched. Values compare trimmed and case-insensitively; a language map by every translation it holds, a list by each element, a number by its value however it is written — "3000", 3000 and 3000.0 meet. Nothing is stored; feed the pairs you accept to fill. The supplier's call. Requires data_requests:write, plus passports:read covering the chosen ontology.
match is synchronous and read-heavy. The level's passports are read
one by one, at most 5,000 of them (truncated: true past that;
passports_read says how many), so on a large level the call takes
seconds. Run it once per set, not per row, and keep it off hot paths.
A set the customer marked no identifier on is 409 identifier_missing; an ontology that is not yours is 404 resource_missing; a level or property it does not have is 400 parameter_invalid.
Required attributes
- Name
ontology- Type
- string
- Description
The
ontology_id of one of your ontologies.
- Name
depth- Type
- string
- Description
The level key whose passports are read.
- Name
property- Type
- string
- Description
A property id of that level holding the customer's identifier value, or
friendly_idto match on your friendly ids.
Request
curl https://api.synexcloud.com/v1/data_request_sets/dset_7b3e9c1a5d2f8460/fulfil/match \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ontology": "ontology_9b2d7e4f1a8c3065",
"depth": "T",
"property": "property_t1"
}'
Response
{
"source": {
"ontology": "ontology_9b2d7e4f1a8c3065",
"depth": "T",
"property": "property_t1"
},
"passports_read": 412,
"truncated": false,
"rows": [
{
"request_id": "dreq_5a1c8e3f9b2d7046",
"passport_label": "LOT-1",
"identifier_value": "SPN-1",
"status": "matched",
"source": { "snowflake": "pass_8d2f6a1c4e9b3057", "friendly_id": "S-1" },
"candidates": [
{ "snowflake": "pass_8d2f6a1c4e9b3057", "friendly_id": "S-1" }
]
},
{
"request_id": "dreq_6b2d9f4a0c3e8157",
"passport_label": "LOT-2",
"identifier_value": "SPN-2",
"status": "ambiguous",
"source": null,
"candidates": [
{ "snowflake": "pass_1c4e8b3a7d2f9065", "friendly_id": "S-2" },
{ "snowflake": "pass_2d5f9c4b8e3a0176", "friendly_id": "S-3" }
]
},
{
"request_id": "dreq_7c3e0a5b1d4f9268",
"passport_label": "LOT-3",
"identifier_value": null,
"status": "unmatched",
"source": null,
"candidates": []
}
]
}
Draft children from your passports
Drafts the paired children from the named passports of yours, as one queued fill job: 202 with the job to poll. Per pair it is the single request's prefill — properties paired by name, values converted where the types allow, the source recorded on each value — saved as that child's draft. A field that already holds a value is left alone unless replace is true. Nothing is submitted; follow with submit_all or the children's own submit. The supplier's call. Requires data_requests:write, plus passports:read covering each source's ontology. Accepts an optional Idempotency-Key header.
A closed set is 409 data_request_set_closed. A pair naming a child that is not the set's, a passport that is not yours, or a child no longer open to answers fails that pair alone, in progress.failed.
Required attributes
- Name
pairs- Type
- array
- Description
1–500 entries, each a
request_id(a child'sdreq_id, fromchildrenor a match row) and asource_snowflake(thepass_id of one of your passports). A child named twice takes the last source given.
Optional attributes
- Name
replace- Type
- boolean
- Description
Overwrite fields that already hold a value. Defaults to
false.
Request
curl https://api.synexcloud.com/v1/data_request_sets/dset_7b3e9c1a5d2f8460/fulfil/fill \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"pairs": [
{ "request_id": "dreq_5a1c8e3f9b2d7046", "source_snowflake": "pass_8d2f6a1c4e9b3057" },
{ "request_id": "dreq_6b2d9f4a0c3e8157", "source_snowflake": "pass_1c4e8b3a7d2f9065" }
],
"replace": false
}'
Response
{
"object": "data_request_set_job",
"id": "dsj_5d8f2a7c1e4b9063",
"set": "dset_7b3e9c1a5d2f8460",
"kind": "fill",
"status": "queued",
"progress": { "done": 0, "total": 2, "failed": [], "filled": [] },
"result": null,
"started_by": { "id": 11208, "name": "Erna Kuvalis" },
"created_at": "2026-09-22T11:15:00Z",
"updated_at": "2026-09-22T11:15:00Z"
}
Submit every child
Submits every open or changes_requested child with whatever it holds — drafted by hand, by an upload or by a fill — as one queued submit job: 202 with the job to poll. Each child is submitted exactly as a single submit would be, validated against the definitions pinned at create: a child that is refused is left as it was and listed in progress.failed with the code; the rest go submitted — or applied at once on an auto_apply set (progress.applied), or stay submitted with apply_blocked_reason when the passport refused the automatic apply (progress.blocked). The supplier's call. Requires data_requests:write. Accepts an optional Idempotency-Key header.
A closed set is 409 data_request_set_closed; one with no child left to answer is 409 nothing_to_submit. Once anything was submitted, one data_request_set.submitted fires on the customer's feed.
Request
curl -X POST https://api.synexcloud.com/v1/data_request_sets/dset_7b3e9c1a5d2f8460/submit_all \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Idempotency-Key: submit-dset_7b3e9c1a5d2f8460"
Response
{
"object": "data_request_set_job",
"id": "dsj_2b7d1e9c4a6f8035",
"set": "dset_7b3e9c1a5d2f8460",
"kind": "submit",
"status": "queued",
"progress": {
"done": 0,
"total": 3,
"failed": [],
"submitted": [],
"applied": [],
"blocked": []
},
"result": null,
"started_by": { "id": 11208, "name": "Erna Kuvalis" },
"created_at": "2026-09-22T10:00:00Z",
"updated_at": "2026-09-22T10:00:00Z"
}
Accept and apply every child
Accepts every value still awaiting a verdict on every submitted child and applies the child, as one queued apply job: 202 with the job to poll — the bulk form of a single request's review and apply with every verdict accepted. A value you rejected by hand stays rejected, and that child goes back to changes_requested for the supplier to revise (progress.returned); the rest apply (progress.applied). A child whose apply is refused — passport_released, team_suspended, ontology_locked, a passports:write scope that does not cover it — is left exactly as it was, still submitted, and listed in progress.failed with the code. To return one child or reject single values, use the child's own review on /v1/data_requests/:id. The customer's call. Requires data_requests:write and passports:write covering the passports' ontology — 403 ability_missing without it. Accepts an optional Idempotency-Key header.
A closed set is 409 data_request_set_closed; one with no submission waiting is 409 nothing_to_apply. Once anything applied, one data_request_set.applied fires on each team's feed.
Request
curl -X POST https://api.synexcloud.com/v1/data_request_sets/dset_7b3e9c1a5d2f8460/apply_all \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Idempotency-Key: apply-dset_7b3e9c1a5d2f8460"
Response
{
"object": "data_request_set_job",
"id": "dsj_9c2e4a7b1d8f3056",
"set": "dset_7b3e9c1a5d2f8460",
"kind": "apply",
"status": "queued",
"progress": {
"done": 0,
"total": 1,
"failed": [],
"applied": [],
"returned": []
},
"result": null,
"started_by": { "id": 9031, "name": "Mara Lindqvist" },
"created_at": "2026-09-23T08:00:00Z",
"updated_at": "2026-09-23T08:00:00Z"
}
Cancel a bulk data request
Takes the whole set back. The set closes first, so a create job still running makes no more children; then every child not yet terminal is cancelled with closed_reason: "by_customer", each emitting its own data_request.cancelled. Values a child had already applied stay on its passport. Returns the closed set. The customer's call. Requires data_requests:write. Accepts an optional Idempotency-Key header.
A set already closed is 409 data_request_set_closed. data_request_set.cancelled fires on your feed and, when a supplier team holds the set, on theirs.
Request
curl -X POST https://api.synexcloud.com/v1/data_request_sets/dset_7b3e9c1a5d2f8460/cancel \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "data_request_set",
"id": "dset_7b3e9c1a5d2f8460",
"status": "closed",
"closed_reason": "cancelled",
"passport_count": 3,
"depth": { "key": "B", "name": "Cell" },
"ontology": {
"id": "ontology_00a1b2c3d4e5f607",
"version": 3,
"default_language": "en"
},
"properties": ["property_capacity", "property_part_no"],
"definitions": [
{
"id": "property_capacity",
"name": "Capacity",
"type": "measurement",
"value": { "measurement": "mAh" }
},
{
"id": "property_part_no",
"name": "Supplier part no.",
"type": "short_text",
"value": { "required": false }
}
],
"identifier": { "kind": "friendly_id" },
"selection": { "mode": "list", "ids": ["LOT-1", "LOT-2", "LOT-3"] },
"message": null,
"auto_apply": false,
"due_at": null,
"supplier": {
"id": "sup_7c2e9a4f1b8d3506",
"name": "Nordvolt Cells",
"status": "linked"
},
"customer_team": { "id": 4821, "name": "Kestrel Energy" },
"supplier_team": { "id": 5107, "name": "Nordvolt Cells AB" },
"requested_by": { "id": 9031, "name": "Mara Lindqvist" },
"cancelled_by": { "id": 9031, "name": "Mara Lindqvist" },
"counts": {
"total": 3,
"open": 0,
"submitted": 0,
"changes_requested": 0,
"applied": 1,
"cancelled": 2
},
"create_job": "dsj_1f4a8c2e6b9d3075",
"progress": null,
"created_at": "2026-09-20T09:12:40Z",
"closed_at": "2026-09-25T16:20:08Z",
"updated_at": "2026-09-25T16:20:08Z",
"viewer_role": "customer"
}
Events
A set's own events carry the set, as that side sees it, in data.object.
| Type | Fires when | Feed |
|---|---|---|
data_request_set.created | The create job has made the children. | Customer |
data_request_set.received | The set reaches the supplier team — when the create job finishes over a linked supplier, or when the link's invitation is accepted. | Supplier |
data_request_set.submitted | A submit_all, or an upload with submit_on_upload, submitted anything. | Customer |
data_request_set.applied | An apply_all applied anything. | Both |
data_request_set.cancelled | The customer cancelled the set. | Customer, and the supplier's when a supplier team holds the set |
A child's creation is announced by the set alone: the create job emits no data_request.created or data_request.received per child. From then on the children are real requests with their own data_request.* events — each gets data_request.received when the supplier accepts a pending link's invitation, and each submit, apply and cancel emits as it would for a single request. An apply_all over 240 children produces 240 data_request.applied on each feed and one data_request_set.applied. Filter the events feed on object_id — the dset_ id for the set's own events, a dreq_ id for one child's.
Errors
code | Status | When |
|---|---|---|
selection_invalid | 400 | A passport of the selection cannot be taken, or the selection names none. error.errors is keyed by the offending id. Nothing is opened. |
selection_too_large | 400 | The selection covers more than 500 passports. |
parameter_invalid | 400 | A malformed body or query; a level not below the parent; a property that is not at the level or carries no fillable value; an identifier property that carries no value; an upload that is not an .xlsx of at most 10 MB; an unknown depth or property on a match. |
invalid_cursor | 400 | A cursor minted under another direction or filter, or a bare position on children. |
ability_missing | 403 | Opening a set or apply_all without passports:write; param is selection. |
ability_not_scoped_to_resource | 403 | A data_requests:* grant with an ontology scope, or a passports:write / passports:read scope that does not cover the ontology. |
team_suspended | 403 | Your team is suspended. |
resource_missing | 404 | The set, upload or job is not your team's, your team is on the wrong side for the call, or the supplier or the match ontology is not yours. |
link_revoked | 409 | The supplier link has been revoked. |
supplier_already_linked | 409 | The recipient address is already linked or invited — name its supplier instead. |
data_request_set_creating | 409 | An upload on a set whose children are not made yet. |
data_request_set_closed | 409 | A cancel, submit_all, apply_all, upload or fill on a closed set. |
nothing_to_submit | 409 | submit_all with no child left to answer. |
nothing_to_apply | 409 | apply_all with no submission waiting. |
identifier_missing | 409 | fulfil/match on a set the customer marked no identifier on. |
See errors for the envelope and handling guidance.