Data requests
A data request is what a customer asks a linked supplier for: named properties of one passport, filled in by the supplier and written onto that passport with the customer's authority. Kestrel Energy asks Nordvolt Cells for the serial number, the pack weight and the datasheet of pack LOT-870; Nordvolt answers — by typing, or by pulling the values out of one of their own passports — Kestrel reviews, and the accepted values land on LOT-870 as if Kestrel had written them.
A request is typed from the start: the properties are validated against the passport's pinned ontology version when it is opened, the answers are validated against the same definitions when they are submitted, and there is no free-text-then-map step in between. Both teams see the same request, each from their own side.
To ask for the same properties of many passports at once, open a bulk data request. It opens one ordinary request per passport, each carrying its set, and those children are answered, reviewed and applied with the endpoints on this page exactly as single requests are.
Neither data_requests:read nor data_requests:write is in a key's default set — grant them explicitly when the key is minted. Neither can be restricted to particular ontologies: a request belongs to the team, not to an ontology. Opening and applying a request also need passports:write — see the request is the capability.
| Endpoint | Ability | Side |
|---|---|---|
GET /v1/data_requests | data_requests:read | Either — direction=sent (default) or direction=received |
POST /v1/data_requests | data_requests:write and passports:write on the passport's ontology | Customer |
GET /v1/data_requests/:id | data_requests:read | Either |
GET /v1/data_requests/:id/prefill | data_requests:write, plus passports:read on the source passport | Supplier |
PUT /v1/data_requests/:id/draft | data_requests:write | Supplier |
POST /v1/data_requests/:id/submit | data_requests:write | Supplier |
POST /v1/data_requests/:id/review | data_requests:write | Customer |
POST /v1/data_requests/:id/apply | data_requests:write and passports:write on the passport's ontology | Customer |
POST /v1/data_requests/:id/return | data_requests:write | Customer |
POST /v1/data_requests/:id/cancel | data_requests:write | Customer |
The data request model
- Name
object- Type
- string
- Description
Always
data_request.
- Name
id- Type
- string
- Description
Unique identifier, prefixed
dreq_. The same on both sides.
- Name
status- Type
- string
- Description
open,submitted,changes_requested,appliedorcancelled. See the lifecycle.
- Name
closed_reason- Type
- string
- Description
Why a
cancelledrequest was cancelled:by_customer,link_revoked,passport_deletedorpassport_transferred(not produced until passport transfers ship). Null otherwise.
- Name
apply_blocked_reason- Type
- string
- Description
Set on a
submittedrequest whoseauto_applywrite was refused:passport_released,team_suspendedorontology_locked(not produced until passport transfers ship). Cleared by the next apply or return. Null otherwise.
- Name
auto_apply- Type
- boolean
- Description
Whether the supplier's submission is applied the moment it arrives, without a review step.
- Name
message- Type
- string
- Description
The customer's note to the supplier, at most 2000 characters. Null when none.
- Name
due_at- Type
- string
- Description
An advisory deadline, shown to the supplier and never enforced. Null when none.
- Name
revision- Type
- integer
- Description
How many times the supplier has submitted.
0whileopen; every submit, a resubmission after a return included, increments it.
- Name
passport- Type
- object
- Description
The passport the values are for, as snapshotted at create:
snowflake(itspass_id),label(itsfriendly_idat create, or its id when it had none),ontology_id,ontology_version(the schema version it was pinned to, which the definitions were taken from) anddefault_language(see languages).
- Name
set- Type
- object
- Description
The bulk data request this request is a child of, as
{ id, position, identifier_value }: thedset_id, the row's 0-based place in the set, and the passport's value of the identifier the customer marked — its friendly id or the marked property's value, null when the set has none. Null for a request opened on its own. Always present.
- Name
supplier- Type
- object
- Description
The supplier link the request travels over, as it stands now:
{ id, name, status }.
- Name
customer_team- Type
- object
- Description
The team that opened the request, as
{ id, name }.
- Name
supplier_team- Type
- object
- Description
The team it is addressed to, in the same shape. Null while the link's invitation is unaccepted.
- Name
requested_by- Type
- object
- Description
Who opened the request, as
{ id, name }— a user's integer id and display name,namenull if the account has since been removed.
- Name
submitted_by- Type
- object
- Description
Who last submitted. Null until the first submit.
- Name
reviewed_by- Type
- object
- Description
Who last recorded a verdict. Null until then.
- Name
applied_by- Type
- object
- Description
Who applied the request. On an
auto_applyrequest this is the supplier's submitter, whose submission carried the write. Null until applied.
- Name
cancelled_by- Type
- object
- Description
Who cancelled the request. Null when the cancel was a side effect — a link revoked, a passport deleted or moved to another team.
- Name
source_passport- Type
- string
- Description
The supplier's passport most of the answers were taken from, set on each submit from the values'
source. Null when none names one.
- Name
counts- Type
- object
- Description
The values tallied as
{ total, accepted, rejected, applied }.
- Name
values- Type
- array
- Description
One value per requested property, in request order. On single reads, mutation responses and event snapshots — list rows leave it out.
- Name
created_at- Type
- string
- Description
ISO 8601 timestamp of creation.
- Name
submitted_at- Type
- string
- Description
When the supplier last submitted. Null until then.
- Name
reviewed_at- Type
- string
- Description
When a verdict was last recorded. Null until then.
- Name
applied_at- Type
- string
- Description
When the request reached
applied. Null until then.
- Name
cancelled_at- Type
- string
- Description
When the request was cancelled. Null otherwise.
- Name
updated_at- Type
- string
- Description
When the request last changed.
- Name
viewer_role- Type
- string
- Description
customerorsupplier— the side this rendering is for. On single reads and mutation responses only.
The value model
- Name
property_id- Type
- string
- Description
The requested property.
- Name
position- Type
- integer
- Description
Its place in the request, zero-based, in the order the customer named the properties.
- Name
definition- Type
- object
- Description
The property as defined at create:
id,name,type,value(the type-specific configuration —required, themeasurementunit, and so on) and, when it is mapped to an EU registry element,data_element_id. Answers are validated against this snapshot, whatever the ontology does afterwards.typeis one ofshort_text,long_text,number,boolean,date,tags,range,measurement,fileorimage.
- Name
submitted_value- Type
- any
- Description
The answer exactly as the supplier sent it. Null until one is given.
- Name
coerced_value- Type
- any
- Description
The answer as validated and stored on submit — a number for a
measurement, a language map for a multilingual property. Null on a draft and before any submit.
- Name
review_status- Type
- string
- Description
The customer's verdict:
pending,acceptedorrejected. Reset topendingon every submit.
- Name
review_comment- Type
- string
- Description
The customer's note to the supplier, required on a rejection, at most 2000 characters. Null otherwise.
- Name
reviewed_at- Type
- string
- Description
When the verdict was recorded. Null until then.
- Name
source- Type
- object
- Description
Where the answer was taken from, as
{ passport, property, match, passport_label, property_name }: one of the supplier's own passports, a property on it, how the two were matched (data_elementorname_typewhen the prefill proposed it,manualwhen the supplier picked it by hand), and what the passport and property were called when the value was copied — null when the submission did not carry them. Null on a value that was typed in.
- Name
applied_at- Type
- string
- Description
When the value reached the passport. A value with
applied_atset is frozen: it takes no further answer and no further verdict.
- Name
current_value- Type
- any
- Description
Customer side only, on single reads and mutation responses: what the passport holds for this property now, or null. See current values. The supplier's rendering does not carry the key.
The lifecycle
supplier submits customer applies
open ──────────────────────────▶ submitted ─────────────────▶ applied
▲ │ ▲
│ │ │ supplier resubmits
│ customer returns ▼ │ (revision + 1)
│ changes_requested
│ │
└──── customer cancels · link revoked · passport deleted ────▶ cancelled
(from open, submitted or changes_requested)
| Status | Meaning |
|---|---|
open | Awaiting the supplier's answers. revision is 0. |
submitted | The supplier answered; awaiting the customer's review. On an auto_apply request this is where the request waits when the automatic write was blocked. |
changes_requested | The customer returned it. The supplier sees the rejected values with their comments, revises and submits again, which moves it back to submitted with revision one higher. |
applied | Terminal. Every value has reached the passport. |
cancelled | Terminal. closed_reason says why. Nothing of a cancelled request reaches the passport. |
closed_reason is by_customer for a cancel, link_revoked when the supplier link is revoked, passport_deleted when the passport is deleted, and passport_transferred when the passport is moved to another team by a passport transfer — not produced until transfers ship. The three side-effect cancels leave cancelled_by null.
There is no expiry. due_at is advisory, and a request stays where it is until somebody moves it.
Two teams, one request
A request lives on a supplier link and, like the link, is visible from both sides. GET /v1/data_requests/:id looks the id up on both sides at once and says which one you are with viewer_role: customer for the team that opened it, supplier for the team it is addressed to. The list takes the side as a parameter — direction=sent (the default) is what your team opened, direction=received is your inbox.
An API key is pinned to one team, and a team is never on both sides of the same link, so a given key is always exactly one of the two. That decides what it may do: create, review, apply, return and cancel are the customer's; prefill, draft and submit are the supplier's. Calling an operation from the wrong side is 404 resource_missing — the same answer another tenant gets — so the response never confirms that a request exists to a key that cannot act on it.
The two renderings differ in exactly one respect: the customer's values carry current_value, and the supplier's do not carry the key at all. The supplier never reads the customer's passport; the request's own snapshot — passport.label, the definition on each value — is all they see of it.
The request is the capability
A data request is a passport write taken on credit. Once it exists, the supplier's submission is written onto your passport with your authority — automatically on auto_apply, or when you apply it. Opening one is therefore gated like the write it stands for: POST /v1/data_requests and POST /v1/data_requests/:id/apply both require passports:write covering the passport's ontology, on top of data_requests:write.
The order of the checks is deliberate. A key holding no passports:write at all is 403 ability_missing (param: passport), and a malformed body is 400 — both refused before anything is looked up, so neither doubles as a probe for whether the passport exists. The passport is then resolved (404 resource_missing when it is missing or another team's, exactly alike), and only once its ontology is known can the scope be checked: a passports:write scoped to other ontologies is 403 ability_not_scoped_to_resource.
The supplier needs no ability on your passport at any point. Their submit is gated by data_requests:write on their own team; the authority for the write was yours, granted when you opened the request. Every step the supplier takes is still recorded against a real person (submitted_by, and the actor on the events), so the write is attributable end to end.
Languages
The request snapshots the ontology's default language as passport.default_language. A multilingual property — short_text, long_text, tags or file — is answered with a bare value, read as that language, or with a language map to answer in several; its coerced_value is always the language map. When the value is applied, its translations are merged into what the passport holds: each language the answer carries replaces that translation, and the passport's other translations stay as they are.
current_value is the passport's stored value with every translation it holds. These endpoints take no ?lang=; see passports for localized reads.
File and image values
A file or image property is answered with a reference to one of the supplier's own Drive files, named by its file_ id — the reference object the Drive renders, or the bare file_… id. What is stored is the canonical core of the reference, { snowflake, filename, extension, size, filetype, status, path_dir } — whichever of those the Drive record has — with the upload bookkeeping and any presigned URL dropped. A bare id is stored as the string it was.
- On a single read and on every mutation response, each reference object in
submitted_valueandcoerced_valuegains alink: a presigned download URL, valid for 24 hours, signed against the supplier's team, so the customer can open what they were sent before deciding on it. A file the supplier has since removed carrieslink: null. A barefile_…string never gains one. Lists and event payloads never carry links. - On apply, the file is copied from the supplier's Drive into the customer's, under
/Supplied/<supplier name>/— one folder per supplier, named after the link'sname— and the passport holds the copy. The supplier's original stays theirs. See files. - The file must be the supplier's. A
file_id of any other team, the customer's included, is400 value_invalidon submit.
Current values
The customer's rendering of every value carries current_value: what the passport holds today for that property, so a submission can be judged against what it would replace. It is present on single reads and mutation responses, never on list rows, and never on the supplier's side. A file/image value is hydrated the way a passport read hydrates it, with its own link.
The passport's values are shown only to a key that could read the passport itself: passports:read covering its ontology. A key without it, or one whose scope stops short of this ontology, sees every current_value as null with the shape kept; so does one whose passport has since been deleted, and so does any property the passport has nothing for. Null means "nothing to show you", not "empty".
Passport storage is eventually consistent, so the current_value on an apply response may still show the value the apply just replaced. Read the request again a moment later for the settled view, or take the applied value from coerced_value, which is exactly what was written.
List data requests
Your team's requests on one side, newest first, without their values: a list row is the request's header, parties and counts, and carries neither values nor viewer_role. Requires data_requests:read. Returns a paginated list.
Rows include the children of bulk data requests, each with its set.
Every filter joins the cursor's scope: a cursor minted under one combination is rejected with 400 invalid_cursor under another, so keep the filters identical for the whole walk. An unknown direction or status is 400 parameter_invalid rather than an empty page.
A request over a link whose invitation has not been accepted yet has no supplier team, so it appears in nobody's received list until it has — the customer sees it under sent with supplier_team: null.
Optional attributes
- Name
direction- Type
- string
- Description
sent(default) — requests your team opened as the customer;received— your inbox as the supplier.
- Name
status- Type
- string
- Description
Only requests in this status.
- Name
passport- Type
- string
- Description
Only requests for this
pass_id.
- Name
supplier- Type
- string
- Description
Only requests travelling over this
sup_link.
- 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.
Request
curl -G https://api.synexcloud.com/v1/data_requests \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-d "supplier=sup_7c2e9a4f1b8d3506"
Response
{
"object": "list",
"data": [
{
"object": "data_request",
"id": "dreq_5a1c8e3f9b2d7046",
"status": "submitted",
"closed_reason": null,
"apply_blocked_reason": null,
"auto_apply": false,
"message": "Before the audit, please.",
"due_at": "2026-09-30T00:00:00Z",
"revision": 1,
"passport": {
"snowflake": "pass_3e9a1c5b7d2f4083",
"label": "LOT-870",
"ontology_id": "ontology_00a1b2c3d4e5f607",
"ontology_version": 3,
"default_language": "en"
},
"set": 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" },
"submitted_by": { "id": 11208, "name": "Erna Kuvalis" },
"reviewed_by": null,
"applied_by": null,
"cancelled_by": null,
"source_passport": "pass_8d2f6a1c4e9b3057",
"counts": { "total": 3, "accepted": 0, "rejected": 0, "applied": 0 },
"created_at": "2026-09-15T09:12:40Z",
"submitted_at": "2026-09-17T13:05:22Z",
"reviewed_at": null,
"applied_at": null,
"cancelled_at": null,
"updated_at": "2026-09-17T13:05:22Z"
},
{
"object": "data_request",
"id": "dreq_2e7b4d9a0c1f6358",
"status": "applied",
"closed_reason": null,
"apply_blocked_reason": null,
"auto_apply": true,
"message": null,
"due_at": null,
"revision": 1,
"passport": {
"snowflake": "pass_6b0d3f8a2c5e1794",
"label": "LOT-864",
"ontology_id": "ontology_00a1b2c3d4e5f607",
"ontology_version": 3,
"default_language": "en"
},
"set": 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" },
"submitted_by": { "id": 11208, "name": "Erna Kuvalis" },
"reviewed_by": null,
"applied_by": { "id": 11208, "name": "Erna Kuvalis" },
"cancelled_by": null,
"source_passport": null,
"counts": { "total": 1, "accepted": 1, "rejected": 0, "applied": 1 },
"created_at": "2026-09-08T10:44:03Z",
"submitted_at": "2026-09-09T07:31:50Z",
"reviewed_at": null,
"applied_at": "2026-09-09T07:31:50Z",
"cancelled_at": null,
"updated_at": "2026-09-09T07:31:50Z"
}
],
"has_more": false,
"next_cursor": null,
"url": "/v1/data_requests"
}
Create a data request
Opens a request for named properties of one of your passports, addressed to a supplier. Requires data_requests:write and passports:write covering the passport's ontology — see the request is the capability. Refused with 403 team_suspended while your team is suspended at the create tier or above.
The response is the request as the customer sees it: status: open, one entry in values per property, viewer_role: customer. Emits data_request.created on your feed.
Properties are validated against the pinned version
Every id in properties is checked against the ontology version the passport is pinned to — not the latest — and must carry a fillable value. Rejected with 400 parameter_invalid and errors keyed properties.{i}:
- an id not defined at the passport's level;
- a
categorydivider, which is a heading rather than a field; - a computed
composite, whose value is derived and not writable.
The check runs before a recipient is invited, so a typo cannot leave a pending link and a mailed invitation behind. A passport whose level is missing from its pinned schema version is 404 level_missing (param: passport) — an integrity edge, not something a client causes.
The property definitions, the passport's label and the ontology's default_language are snapshotted onto the request: the supplier's answers are validated against the definitions as they were at create, whatever the ontology does afterwards.
Who is asked
Exactly one of supplier or recipient. Naming both is 400 parameter_invalid on supplier; a key sent as null counts as absent. A revoked link is 409 link_revoked.
Over a linked supplier the request reaches their inbox at once: data_request.received fires on their feed, and the supplier team's owner and the person who accepted the link are emailed. Over a pending supplier it waits with supplier_team: null, and lands in their inbox the moment the invitation is accepted.
Inviting with the request
recipient opens a new pending link and sends its invitation in the same call, under the same rules as POST /v1/suppliers — a lowercased address, one active link per address, 409 supplier_already_linked if the address already holds one — and emits supplier.invited for the link beside data_request.created.
The request and the pending link commit first, and the invitation is mailed afterwards, so the email can tell the recipient how many requests already wait for them. If the mail transport then fails, the call answers 5xx but the request and its link already stand, with invite_status: null. Do not re-POST — not even with the same Idempotency-Key, since a 5xx is not stored: the retry is 409 supplier_already_linked. Instead find the request with GET /v1/data_requests?passport=…, take the sup_ id from its supplier.id, and resend the invitation, which issues the first one.
Auto-apply
With auto_apply: true there is no review step: the supplier's submission is applied the moment it arrives, the request goes straight to applied, and applied_by records the supplier's submitter.
Three refusals of that write are not failures of the submission. A released passport, a customer team that cannot take writes, or a passport transfer running on the passport's ontology leaves the request submitted with apply_blocked_reason set to passport_released, team_suspended or ontology_locked, values intact, for you to apply once the block is lifted. Passport transfers have not shipped, so ontology_locked is not produced yet. Any other failure of the automatic write reaches the supplier as an error from their submit — see submit.
Required attributes
- Name
passport- Type
- string
- Description
The
pass_id of the passport the values are for. Must be your team's.
- Name
properties- Type
- array
- Description
The property ids to ask for, in the order the supplier should see them. At least one; duplicates are collapsed.
- Name
supplier- Type
- string
- Description
The
sup_id of apendingorlinkedsupplier of yours. Required unlessrecipientis given.
- Name
recipient- Type
- object
- Description
An address to invite as a new supplier, as
{ email, name }—namebeing your 1–120 character label for them. Required unlesssupplieris given.
Optional attributes
- Name
message- Type
- string
- Description
A note shown to the supplier, at most 2000 characters. Trimmed; blank reads as none.
- Name
auto_apply- Type
- boolean
- Description
Apply the submission the moment it arrives, without a review step. Defaults to
false.
- Name
due_at- Type
- string
- Description
An advisory deadline: any date after today. Stored at second precision in UTC.
Request
curl https://api.synexcloud.com/v1/data_requests \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"passport": "pass_3e9a1c5b7d2f4083",
"properties": ["property_serial", "property_pack_weight", "property_datasheet"],
"supplier": "sup_7c2e9a4f1b8d3506",
"message": "Before the audit, please.",
"due_at": "2026-09-30"
}'
Response
{
"object": "data_request",
"id": "dreq_5a1c8e3f9b2d7046",
"status": "open",
"closed_reason": null,
"apply_blocked_reason": null,
"auto_apply": false,
"message": "Before the audit, please.",
"due_at": "2026-09-30T00:00:00Z",
"revision": 0,
"passport": {
"snowflake": "pass_3e9a1c5b7d2f4083",
"label": "LOT-870",
"ontology_id": "ontology_00a1b2c3d4e5f607",
"ontology_version": 3,
"default_language": "en"
},
"set": 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" },
"submitted_by": null,
"reviewed_by": null,
"applied_by": null,
"cancelled_by": null,
"source_passport": null,
"counts": { "total": 3, "accepted": 0, "rejected": 0, "applied": 0 },
"values": [
{
"property_id": "property_serial",
"position": 0,
"definition": {
"id": "property_serial",
"name": "Serial number",
"type": "short_text",
"value": { "required": true }
},
"submitted_value": null,
"coerced_value": null,
"review_status": "pending",
"review_comment": null,
"reviewed_at": null,
"source": null,
"applied_at": null,
"current_value": { "en": "SN-OLD" }
},
{
"property_id": "property_pack_weight",
"position": 1,
"definition": {
"id": "property_pack_weight",
"name": "Weight",
"type": "measurement",
"value": { "measurement": "kg" },
"data_element_id": "BATT-PACK-WEIGHT"
},
"submitted_value": null,
"coerced_value": null,
"review_status": "pending",
"review_comment": null,
"reviewed_at": null,
"source": null,
"applied_at": null,
"current_value": null
},
{
"property_id": "property_datasheet",
"position": 2,
"definition": {
"id": "property_datasheet",
"name": "Datasheet",
"type": "file",
"value": { "required": false }
},
"submitted_value": null,
"coerced_value": null,
"review_status": "pending",
"review_comment": null,
"reviewed_at": null,
"source": null,
"applied_at": null,
"current_value": null
}
],
"created_at": "2026-09-15T09:12:40Z",
"submitted_at": null,
"reviewed_at": null,
"applied_at": null,
"cancelled_at": null,
"updated_at": "2026-09-15T09:12:40Z",
"viewer_role": "customer"
}
Retrieve a data request
One request with its values, from whichever side your key's team is on. Requires data_requests:read.
viewer_role says which side you are. The customer's values carry current_value; the supplier's rendering of the same request is identical except that the key is absent. File and image values carry their presigned link.
A team on neither side gets 404 resource_missing.
Request
curl https://api.synexcloud.com/v1/data_requests/dreq_5a1c8e3f9b2d7046 \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "data_request",
"id": "dreq_5a1c8e3f9b2d7046",
"status": "submitted",
"closed_reason": null,
"apply_blocked_reason": null,
"auto_apply": false,
"message": "Before the audit, please.",
"due_at": "2026-09-30T00:00:00Z",
"revision": 1,
"passport": {
"snowflake": "pass_3e9a1c5b7d2f4083",
"label": "LOT-870",
"ontology_id": "ontology_00a1b2c3d4e5f607",
"ontology_version": 3,
"default_language": "en"
},
"set": 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" },
"submitted_by": { "id": 11208, "name": "Erna Kuvalis" },
"reviewed_by": null,
"applied_by": null,
"cancelled_by": null,
"source_passport": "pass_8d2f6a1c4e9b3057",
"counts": { "total": 3, "accepted": 0, "rejected": 0, "applied": 0 },
"values": [
{
"property_id": "property_serial",
"position": 0,
"definition": {
"id": "property_serial",
"name": "Serial number",
"type": "short_text",
"value": { "required": true }
},
"submitted_value": "SN-42",
"coerced_value": { "en": "SN-42" },
"review_status": "pending",
"review_comment": null,
"reviewed_at": null,
"source": null,
"applied_at": null,
"current_value": { "en": "SN-OLD" }
},
{
"property_id": "property_pack_weight",
"position": 1,
"definition": {
"id": "property_pack_weight",
"name": "Weight",
"type": "measurement",
"value": { "measurement": "kg" },
"data_element_id": "BATT-PACK-WEIGHT"
},
"submitted_value": "12.5",
"coerced_value": 12.5,
"review_status": "pending",
"review_comment": null,
"reviewed_at": null,
"source": {
"passport": "pass_8d2f6a1c4e9b3057",
"property": "property_cell_weight",
"match": "data_element",
"passport_label": "CELL-7",
"property_name": "Cell weight"
},
"applied_at": null,
"current_value": null
},
{
"property_id": "property_datasheet",
"position": 2,
"definition": {
"id": "property_datasheet",
"name": "Datasheet",
"type": "file",
"value": { "required": false }
},
"submitted_value": {
"snowflake": "file_5e2a9c7d1b4f0836",
"filename": "Report.pdf",
"extension": "pdf",
"size": 184320,
"link": "https://drive.synex.example/signed/file_5e2a9c7d1b4f0836?X-Amz-Expires=86400"
},
"coerced_value": {
"en": {
"snowflake": "file_5e2a9c7d1b4f0836",
"filename": "Report.pdf",
"extension": "pdf",
"size": 184320,
"link": "https://drive.synex.example/signed/file_5e2a9c7d1b4f0836?X-Amz-Expires=86400"
}
},
"review_status": "pending",
"review_comment": null,
"reviewed_at": null,
"source": null,
"applied_at": null,
"current_value": {
"en": {
"snowflake": "file_1a7c4e9b3d0f2685",
"filename": "Old.pdf",
"extension": "pdf",
"size": 42011,
"link": "https://drive.synex.example/signed/file_1a7c4e9b3d0f2685?X-Amz-Expires=86400"
}
}
}
],
"created_at": "2026-09-15T09:12:40Z",
"submitted_at": "2026-09-17T13:05:22Z",
"reviewed_at": null,
"applied_at": null,
"cancelled_at": null,
"updated_at": "2026-09-17T13:05:22Z",
"viewer_role": "customer"
}
Propose answers from your own passport
The supplier usually already holds the answer — on the passport of the cell they shipped. This proposes which property of one of your passports fills each open property of the request, and with what value, so the form starts filled rather than empty. Requires data_requests:write; the supplier's call. It stores nothing, so it answers for a request in any status.
source is read with your own authority: it must be your team's passport (another team's is 404 resource_missing), and your key needs passports:read covering its ontology — 403 insufficient_permissions or 403 ability_not_scoped_to_resource otherwise. Nothing of the customer's passport is read; the request's own snapshot says what is being asked.
How properties are matched
The two passports usually sit on different ontologies, so nothing links their properties structurally. Two heuristics do, run as two passes — the first completed over every requested property before the second starts:
- A shared data element. Both properties mapped to the same EU registry element —
data_element_id, compared exactly — is an explicit statement of "same quantity" and always wins:matched_by: data_element. - The same name, compared case-insensitively and trimmed:
matched_by: name_type. A source of the requested type is preferred over one that merely converts to it, each sub-pass completed over every still-open property before the next, so an earlier text "Weight" never takes the measurement "Weight" a later measurement property matches exactly.
Either pairing stands only when the source holds a value, the value converts to the requested type, and the conversion raises no warning. A source property fills at most one requested property: one measured value answering two different questions is almost always wrong, and a wrong prefill is worse than an empty field, because it gets submitted.
Each match also lists candidates: every property of the source whose value converts to the requested type, proposed or not, sorted by name, for a pick by hand. A blank source is never a candidate, structural types (category, composite) are neither filled nor offered, and a property already applied is left out.
What converts to what
Every value on the wire — on the match and on each candidate — is already converted to the requested type. A note says what a safe conversion changed (Copied as text with its unit., Joined with commas., Split on commas., a unit dropped, the translation taken); a warning says what the supplier must check. A measurement in another unit is never proposed: it is offered with a warning, its number copied as is — nothing is converted between units.
| Requested type | Accepted source types | Conversion |
|---|---|---|
short_text, long_text | text, number, measurement, tags, boolean, date | number → plain decimal without trailing zeros; measurement → number and source unit, as 2342 mAh; tags → joined with commas; boolean → true/false; date → YYYY-MM-DD |
number | number, measurement, text | measurement → its number, unit dropped; text → only when numeric once trimmed |
measurement | measurement, number, text | as for number; another unit → offered with a warning, never proposed |
range | range in the same unit | as is |
boolean | boolean, text, number | text → true/false/yes/no/1/0, case-insensitive; number → 1 or 0 only |
date | date, text | text → only when it reads as one calendar day; ambiguous dates such as 01/02/2026 are not offered |
tags | tags, text | text → split on commas, trimmed, empties dropped |
file | file, image | as is |
image | image | as is |
Languages: a multilingual source landing in a multilingual field converts every translation; a single value landing in a multilingual field lands in the request's passport.default_language; a multilingual source landing in a single-valued field contributes the source ontology's default language (source.default_language) when present, else the first translation in canonical order, and the note names it. A converted translation the requested type could not store — over 255 characters for short_text, 7,000 for long_text, more than 50 tags or a tag over 64 characters — is dropped like one that did not convert, so what is offered is what a submit accepts.
Using the proposals
Take the values you want into a draft or a submission, with source set from the match — passport and property from the ids, match from matched_by, passport_label from source.label, property_name from the candidate's name — so the provenance travels with the answer. A property picked by hand from candidates is sent with match: manual.
Required attributes
- Name
source- Type
- string
- Description
The
pass_id of one of your team's passports to propose answers from.
The prefill object
- Name
source- Type
- object
- Description
The passport read from:
passport(itspass_id),label(its friendly id, or its id when it has none) anddefault_language(its ontology's default language).
- Name
matches- Type
- array
- Description
One entry per open property of the request, in request order:
property_id;source_property_idandmatched_by(data_elementorname_type) for the proposal, or null when nothing was proposed; the proposedvalue, itsnote, andwarning(always null on a proposal); andcandidates, each{ id, name, type, unit, value, note, warning, suggested }—typeandunitthe source property's own,suggestedtrue on exactly the proposed one.
Request
curl -G https://api.synexcloud.com/v1/data_requests/dreq_5a1c8e3f9b2d7046/prefill \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-d "source=pass_8d2f6a1c4e9b3057"
Response
{
"source": {
"passport": "pass_8d2f6a1c4e9b3057",
"label": "CELL-7",
"default_language": "en"
},
"matches": [
{
"property_id": "property_serial",
"source_property_id": "property_cell_serial",
"matched_by": "name_type",
"value": { "en": "SN-42" },
"note": null,
"warning": null,
"candidates": [
{
"id": "property_batch_code",
"name": "Batch code",
"type": "short_text",
"unit": null,
"value": { "en": "B-2026-09" },
"note": null,
"warning": null,
"suggested": false
},
{
"id": "property_cell_serial",
"name": "Serial number",
"type": "short_text",
"unit": null,
"value": { "en": "SN-42" },
"note": null,
"warning": null,
"suggested": true
}
]
},
{
"property_id": "property_pack_weight",
"source_property_id": "property_cell_weight",
"matched_by": "data_element",
"value": 12.5,
"note": null,
"warning": null,
"candidates": [
{
"id": "property_cell_weight",
"name": "Cell weight",
"type": "measurement",
"unit": "kg",
"value": 12.5,
"note": null,
"warning": null,
"suggested": true
},
{
"id": "property_electrolyte_mass",
"name": "Electrolyte mass",
"type": "measurement",
"unit": "g",
"value": 340,
"note": null,
"warning": "Source is in g; this field expects kg — check the value.",
"suggested": false
}
]
},
{
"property_id": "property_datasheet",
"source_property_id": null,
"matched_by": null,
"value": null,
"note": null,
"warning": null,
"candidates": [
{
"id": "property_safety_sheet",
"name": "Safety data sheet",
"type": "file",
"unit": null,
"value": {
"en": {
"snowflake": "file_3c9e1a7f2b8d4056",
"filename": "safety-data-sheet.pdf",
"extension": "pdf",
"size": 482113,
"filetype": "upload",
"status": "uploaded"
}
},
"note": null,
"warning": null,
"suggested": false
}
]
}
]
}
Save a draft
Keeps the supplier's answers as typed, without judging them. Requires data_requests:write; the supplier's call, against a request in your inbox.
A draft is what a half-filled form saves. Nothing is validated or coerced — submitted_value takes exactly what you send and coerced_value is cleared — the status does not move, nobody is told and no event is emitted. Only the properties you name are touched, so a draft may cover one field or all of them; keys the request never asked for are ignored, and a property already applied is never drafted over.
Drafting needs the request to be open or changes_requested; anything else is 409 data_request_not_open. A body without values is 400 parameter_invalid.
Required attributes
- Name
values- Type
- object
- Description
Answers keyed by property id, each
{ value, source }.sourceis optional: omit it, or send null, for a value typed in; otherwise{ passport, property, match, passport_label, property_name }as the prefill proposed it. A label that is not a string or is longer than 255 characters is dropped, not refused; the ids are kept.
Request
curl -X PUT https://api.synexcloud.com/v1/data_requests/dreq_5a1c8e3f9b2d7046/draft \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"values": {
"property_serial": { "value": "SN-4" },
"property_pack_weight": {
"value": "12.5",
"source": {
"passport": "pass_8d2f6a1c4e9b3057",
"property": "property_cell_weight",
"match": "data_element",
"passport_label": "CELL-7",
"property_name": "Cell weight"
}
}
}
}'
Response
{
"object": "data_request",
"id": "dreq_5a1c8e3f9b2d7046",
"status": "open",
"closed_reason": null,
"apply_blocked_reason": null,
"auto_apply": false,
"message": "Before the audit, please.",
"due_at": "2026-09-30T00:00:00Z",
"revision": 0,
"passport": {
"snowflake": "pass_3e9a1c5b7d2f4083",
"label": "LOT-870",
"ontology_id": "ontology_00a1b2c3d4e5f607",
"ontology_version": 3,
"default_language": "en"
},
"set": 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" },
"submitted_by": null,
"reviewed_by": null,
"applied_by": null,
"cancelled_by": null,
"source_passport": null,
"counts": { "total": 3, "accepted": 0, "rejected": 0, "applied": 0 },
"values": [
{
"property_id": "property_serial",
"position": 0,
"definition": {
"id": "property_serial",
"name": "Serial number",
"type": "short_text",
"value": { "required": true }
},
"submitted_value": "SN-4",
"coerced_value": null,
"review_status": "pending",
"review_comment": null,
"reviewed_at": null,
"source": null,
"applied_at": null
},
{
"property_id": "property_pack_weight",
"position": 1,
"definition": {
"id": "property_pack_weight",
"name": "Weight",
"type": "measurement",
"value": { "measurement": "kg" },
"data_element_id": "BATT-PACK-WEIGHT"
},
"submitted_value": "12.5",
"coerced_value": null,
"review_status": "pending",
"review_comment": null,
"reviewed_at": null,
"source": {
"passport": "pass_8d2f6a1c4e9b3057",
"property": "property_cell_weight",
"match": "data_element",
"passport_label": "CELL-7",
"property_name": "Cell weight"
},
"applied_at": null
},
{
"property_id": "property_datasheet",
"position": 2,
"definition": {
"id": "property_datasheet",
"name": "Datasheet",
"type": "file",
"value": { "required": false }
},
"submitted_value": null,
"coerced_value": null,
"review_status": "pending",
"review_comment": null,
"reviewed_at": null,
"source": null,
"applied_at": null
}
],
"created_at": "2026-09-15T09:12:40Z",
"submitted_at": null,
"reviewed_at": null,
"applied_at": null,
"cancelled_at": null,
"updated_at": "2026-09-16T16:20:09Z",
"viewer_role": "supplier"
}
Submit the answers
Answers the request. Requires data_requests:write; the supplier's call, against a request in your inbox that is open or changes_requested — anything else is 409 data_request_not_open.
Every open property must be answered, and every answer must be valid — all of them are stored or none is. Each value is validated against the definition snapshotted at create, with the same rules a direct passport edit runs; a bare value for a multilingual property is read as the request's passport.default_language. A file/image answer names a file of your team's Drive — see file and image values. A property already applied is skipped whatever the body says for it.
Failures are 400 value_invalid with errors keyed by property id: a missing answer, an invalid one, a file that is not yours, or a property the request never asked for — refused rather than silently dropped, because that is how a typo loses data. Type-check each element of errors[<id>] before reading .code: a missing answer, a foreign file and an unrequested property arrive as plain message strings, while a value the definition refuses is a { code, message, … } detail object. See errors.
On success the request goes submitted, revision increments, submitted_by and submitted_at are stamped, every earlier verdict is reset to pending, and source_passport records the passport most of the answers were taken from. A request in changes_requested is resubmitted the same way: applied values stay frozen, the rejected ones must be answered again, and the rest are up for a fresh verdict.
On an auto_apply request the response reads status: applied, with applied_at set on the request and on every value — or submitted with apply_blocked_reason, if the write was blocked. If the automatic write fails for any other reason — the passport was deleted since the request was opened, or your answers would push it past its 400 KB storage ceiling (413 passport_too_large) — that error is what you get back, after the submission has committed: the request is submitted with your values on it, and the customer sees them. It is the one place on this surface where the status code does not describe the request's state, so read the request again when a submit on an auto_apply request errors.
Emits data_request.submitted on the customer's feed and, when auto-applied, data_request.applied on both.
Required attributes
- Name
values- Type
- object
- Description
Answers keyed by property id, each
{ value, source }as on a draft. Avalueis required for every open property.
Request
curl https://api.synexcloud.com/v1/data_requests/dreq_5a1c8e3f9b2d7046/submit \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"values": {
"property_serial": { "value": "SN-42" },
"property_pack_weight": {
"value": "12.5",
"source": {
"passport": "pass_8d2f6a1c4e9b3057",
"property": "property_cell_weight",
"match": "data_element",
"passport_label": "CELL-7",
"property_name": "Cell weight"
}
},
"property_datasheet": {
"value": {
"snowflake": "file_5e2a9c7d1b4f0836",
"filename": "Report.pdf",
"extension": "pdf",
"size": 184320
}
}
}
}'
Response
{
"object": "data_request",
"id": "dreq_5a1c8e3f9b2d7046",
"status": "submitted",
"closed_reason": null,
"apply_blocked_reason": null,
"auto_apply": false,
"message": "Before the audit, please.",
"due_at": "2026-09-30T00:00:00Z",
"revision": 1,
"passport": {
"snowflake": "pass_3e9a1c5b7d2f4083",
"label": "LOT-870",
"ontology_id": "ontology_00a1b2c3d4e5f607",
"ontology_version": 3,
"default_language": "en"
},
"set": 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" },
"submitted_by": { "id": 11208, "name": "Erna Kuvalis" },
"reviewed_by": null,
"applied_by": null,
"cancelled_by": null,
"source_passport": "pass_8d2f6a1c4e9b3057",
"counts": { "total": 3, "accepted": 0, "rejected": 0, "applied": 0 },
"values": [
{
"property_id": "property_serial",
"position": 0,
"definition": {
"id": "property_serial",
"name": "Serial number",
"type": "short_text",
"value": { "required": true }
},
"submitted_value": "SN-42",
"coerced_value": { "en": "SN-42" },
"review_status": "pending",
"review_comment": null,
"reviewed_at": null,
"source": null,
"applied_at": null
},
{
"property_id": "property_pack_weight",
"position": 1,
"definition": {
"id": "property_pack_weight",
"name": "Weight",
"type": "measurement",
"value": { "measurement": "kg" },
"data_element_id": "BATT-PACK-WEIGHT"
},
"submitted_value": "12.5",
"coerced_value": 12.5,
"review_status": "pending",
"review_comment": null,
"reviewed_at": null,
"source": {
"passport": "pass_8d2f6a1c4e9b3057",
"property": "property_cell_weight",
"match": "data_element",
"passport_label": "CELL-7",
"property_name": "Cell weight"
},
"applied_at": null
},
{
"property_id": "property_datasheet",
"position": 2,
"definition": {
"id": "property_datasheet",
"name": "Datasheet",
"type": "file",
"value": { "required": false }
},
"submitted_value": {
"snowflake": "file_5e2a9c7d1b4f0836",
"filename": "Report.pdf",
"extension": "pdf",
"size": 184320,
"link": "https://drive.synex.example/signed/file_5e2a9c7d1b4f0836?X-Amz-Expires=86400"
},
"coerced_value": {
"en": {
"snowflake": "file_5e2a9c7d1b4f0836",
"filename": "Report.pdf",
"extension": "pdf",
"size": 184320,
"link": "https://drive.synex.example/signed/file_5e2a9c7d1b4f0836?X-Amz-Expires=86400"
}
},
"review_status": "pending",
"review_comment": null,
"reviewed_at": null,
"source": null,
"applied_at": null
}
],
"created_at": "2026-09-15T09:12:40Z",
"submitted_at": "2026-09-17T13:05:22Z",
"reviewed_at": null,
"applied_at": null,
"cancelled_at": null,
"updated_at": "2026-09-17T13:05:22Z",
"viewer_role": "supplier"
}
Review the submitted values
Records the customer's verdict on the submitted values, and moves nothing. Requires data_requests:write; the customer's call, on a submitted request — anything else is 409 data_request_not_submitted.
Each decision names an open property and a verdict: accepted, rejected with a comment telling the supplier what to change, or pending to withdraw an earlier verdict. Only the properties named are touched and the request stays submitted, so a reviewer may decide in as many passes as they like before applying or returning. A property already applied takes no verdict, and one the request never asked for is refused rather than dropped.
Failures are 400 parameter_invalid with errors keyed decisions.{i}.property_id, decisions.{i}.status or decisions.{i}.comment; an empty decisions is refused too.
A review is private to the customer: no event, no email. The supplier learns of it when the request is applied or returned.
Required attributes
- Name
decisions- Type
- array
- Description
At least one
{ property_id, status, comment }.statusisaccepted,rejectedorpending;comment, at most 2000 characters, is required on a rejection and ignored otherwise.
Request
curl https://api.synexcloud.com/v1/data_requests/dreq_5a1c8e3f9b2d7046/review \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"decisions": [
{ "property_id": "property_serial", "status": "accepted" },
{
"property_id": "property_pack_weight",
"status": "rejected",
"comment": "Net weight, not gross — please re-weigh without the crate."
},
{ "property_id": "property_datasheet", "status": "accepted" }
]
}'
Response
{
"object": "data_request",
"id": "dreq_5a1c8e3f9b2d7046",
"status": "submitted",
"closed_reason": null,
"apply_blocked_reason": null,
"auto_apply": false,
"message": "Before the audit, please.",
"due_at": "2026-09-30T00:00:00Z",
"revision": 1,
"passport": {
"snowflake": "pass_3e9a1c5b7d2f4083",
"label": "LOT-870",
"ontology_id": "ontology_00a1b2c3d4e5f607",
"ontology_version": 3,
"default_language": "en"
},
"set": 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" },
"submitted_by": { "id": 11208, "name": "Erna Kuvalis" },
"reviewed_by": { "id": 9031, "name": "Mara Lindqvist" },
"applied_by": null,
"cancelled_by": null,
"source_passport": "pass_8d2f6a1c4e9b3057",
"counts": { "total": 3, "accepted": 2, "rejected": 1, "applied": 0 },
"values": [
{
"property_id": "property_serial",
"position": 0,
"definition": {
"id": "property_serial",
"name": "Serial number",
"type": "short_text",
"value": { "required": true }
},
"submitted_value": "SN-42",
"coerced_value": { "en": "SN-42" },
"review_status": "accepted",
"review_comment": null,
"reviewed_at": "2026-09-18T11:30:47Z",
"source": null,
"applied_at": null,
"current_value": { "en": "SN-OLD" }
},
{
"property_id": "property_pack_weight",
"position": 1,
"definition": {
"id": "property_pack_weight",
"name": "Weight",
"type": "measurement",
"value": { "measurement": "kg" },
"data_element_id": "BATT-PACK-WEIGHT"
},
"submitted_value": "12.5",
"coerced_value": 12.5,
"review_status": "rejected",
"review_comment": "Net weight, not gross — please re-weigh without the crate.",
"reviewed_at": "2026-09-18T11:30:47Z",
"source": {
"passport": "pass_8d2f6a1c4e9b3057",
"property": "property_cell_weight",
"match": "data_element",
"passport_label": "CELL-7",
"property_name": "Cell weight"
},
"applied_at": null,
"current_value": null
},
{
"property_id": "property_datasheet",
"position": 2,
"definition": {
"id": "property_datasheet",
"name": "Datasheet",
"type": "file",
"value": { "required": false }
},
"submitted_value": {
"snowflake": "file_5e2a9c7d1b4f0836",
"filename": "Report.pdf",
"extension": "pdf",
"size": 184320,
"link": "https://drive.synex.example/signed/file_5e2a9c7d1b4f0836?X-Amz-Expires=86400"
},
"coerced_value": {
"en": {
"snowflake": "file_5e2a9c7d1b4f0836",
"filename": "Report.pdf",
"extension": "pdf",
"size": 184320,
"link": "https://drive.synex.example/signed/file_5e2a9c7d1b4f0836?X-Amz-Expires=86400"
}
},
"review_status": "accepted",
"review_comment": null,
"reviewed_at": "2026-09-18T11:30:47Z",
"source": null,
"applied_at": null,
"current_value": {
"en": {
"snowflake": "file_1a7c4e9b3d0f2685",
"filename": "Old.pdf",
"extension": "pdf",
"size": 42011,
"link": "https://drive.synex.example/signed/file_1a7c4e9b3d0f2685?X-Amz-Expires=86400"
}
}
}
],
"created_at": "2026-09-15T09:12:40Z",
"submitted_at": "2026-09-17T13:05:22Z",
"reviewed_at": "2026-09-18T11:30:47Z",
"applied_at": null,
"cancelled_at": null,
"updated_at": "2026-09-18T11:30:47Z",
"viewer_role": "customer"
}
Apply the accepted values
Writes the accepted values onto the passport and settles the request. Requires data_requests:write and passports:write covering the passport's ontology; the customer's call, on a submitted request — anything else is 409 data_request_not_submitted.
This is where the credit taken at create is redeemed, and the write is the same one a direct PATCH /v1/passports/:id makes, gated the same way: a key without passports:write is 403 ability_missing (param: passport), checked before the request is even looked up; one scoped to other ontologies is 403 ability_not_scoped_to_resource. A write-suspended team is refused at the door with 403 team_suspended, like every write.
A released passport is 409 passport_released, and a passport transfer running on the passport's ontology is 409 ontology_locked (not produced until transfers ship); either leaves the request exactly as it was, verdicts included, for a retry. The values are validated again against the passport's pinned schema as it stands now: one that schema refuses — a definition that has moved under the request, or a property it no longer carries — is 400 value_invalid with errors keyed by property id, and a write that would push the passport past its 400 KB storage ceiling is 413 passport_too_large. Nothing is written in either case.
What is applied
On an ordinary request only the accepted values are written; with nothing accepted it is 409 review_incomplete. On an auto_apply request the review is implicit — you chose to take the answers as they come, and only a block kept them off the passport — so values nobody decided on count as accepted too, and the apply records you as having accepted them. A value you rejected stays rejected either way.
The request then settles:
appliedonce every value has landed —data_request.appliedon both feeds, and the submitter is emailed;changes_requestedwhen a rejected value remains for the supplier to revise — the request is returned in the same call, rejected values and comments with it,data_request.returnedon the supplier's feed, and the submitter is emailed;- still
submittedwhen the rest merely await a verdict — a partial apply: values written, others pending, no event, no mail.
Each written value gets its own applied_at, and a value applied now is frozen: it takes no further answer or verdict and survives every later resubmit untouched. A request can therefore be applied in stages, and the passport is never written twice for the same property. A file/image value is copied into your Drive first, so the passport holds your copy — see file and image values.
The current_value beside each value in the response is a fresh read of the passport, which may not show the write yet — see current values.
Request
curl -X POST https://api.synexcloud.com/v1/data_requests/dreq_5a1c8e3f9b2d7046/apply \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "data_request",
"id": "dreq_5a1c8e3f9b2d7046",
"status": "applied",
"closed_reason": null,
"apply_blocked_reason": null,
"auto_apply": false,
"message": "Before the audit, please.",
"due_at": "2026-09-30T00:00:00Z",
"revision": 2,
"passport": {
"snowflake": "pass_3e9a1c5b7d2f4083",
"label": "LOT-870",
"ontology_id": "ontology_00a1b2c3d4e5f607",
"ontology_version": 3,
"default_language": "en"
},
"set": 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" },
"submitted_by": { "id": 11208, "name": "Erna Kuvalis" },
"reviewed_by": { "id": 9031, "name": "Mara Lindqvist" },
"applied_by": { "id": 9031, "name": "Mara Lindqvist" },
"cancelled_by": null,
"source_passport": "pass_8d2f6a1c4e9b3057",
"counts": { "total": 3, "accepted": 3, "rejected": 0, "applied": 3 },
"values": [
{
"property_id": "property_serial",
"position": 0,
"definition": {
"id": "property_serial",
"name": "Serial number",
"type": "short_text",
"value": { "required": true }
},
"submitted_value": "SN-42",
"coerced_value": { "en": "SN-42" },
"review_status": "accepted",
"review_comment": null,
"reviewed_at": "2026-09-18T11:30:47Z",
"source": null,
"applied_at": "2026-09-18T11:31:02Z",
"current_value": { "en": "SN-42" }
},
{
"property_id": "property_pack_weight",
"position": 1,
"definition": {
"id": "property_pack_weight",
"name": "Weight",
"type": "measurement",
"value": { "measurement": "kg" },
"data_element_id": "BATT-PACK-WEIGHT"
},
"submitted_value": "11.8",
"coerced_value": 11.8,
"review_status": "accepted",
"review_comment": null,
"reviewed_at": "2026-09-19T08:02:19Z",
"source": {
"passport": "pass_8d2f6a1c4e9b3057",
"property": "property_cell_weight",
"match": "data_element",
"passport_label": "CELL-7",
"property_name": "Cell weight"
},
"applied_at": "2026-09-19T08:02:31Z",
"current_value": 11.8
},
{
"property_id": "property_datasheet",
"position": 2,
"definition": {
"id": "property_datasheet",
"name": "Datasheet",
"type": "file",
"value": { "required": false }
},
"submitted_value": {
"snowflake": "file_5e2a9c7d1b4f0836",
"filename": "Report.pdf",
"extension": "pdf",
"size": 184320,
"link": "https://drive.synex.example/signed/file_5e2a9c7d1b4f0836?X-Amz-Expires=86400"
},
"coerced_value": {
"en": {
"snowflake": "file_5e2a9c7d1b4f0836",
"filename": "Report.pdf",
"extension": "pdf",
"size": 184320,
"link": "https://drive.synex.example/signed/file_5e2a9c7d1b4f0836?X-Amz-Expires=86400"
}
},
"review_status": "accepted",
"review_comment": null,
"reviewed_at": "2026-09-18T11:30:47Z",
"source": null,
"applied_at": "2026-09-18T11:31:02Z",
"current_value": {
"en": {
"snowflake": "file_0c4e8b2a6d1f3957",
"filename": "Report.pdf",
"extension": "pdf",
"size": 184320,
"link": "https://drive.synex.example/signed/file_0c4e8b2a6d1f3957?X-Amz-Expires=86400"
}
}
}
],
"created_at": "2026-09-15T09:12:40Z",
"submitted_at": "2026-09-19T07:58:44Z",
"reviewed_at": "2026-09-19T08:02:19Z",
"applied_at": "2026-09-19T08:02:31Z",
"cancelled_at": null,
"updated_at": "2026-09-19T08:02:31Z",
"viewer_role": "customer"
}
Return the submission
Sends the submission back to the supplier without writing anything. Requires data_requests:write; the customer's call, on a submitted request — anything else is 409 data_request_not_submitted.
The request goes changes_requested. The supplier sees the rejected values with their comments, revises, and submits again — which moves it back to submitted with a higher revision and every verdict reset. Values already applied stay frozen; only the open ones are up for revision.
A return needs something to return for: at least one open value must be rejected, with a comment, or it is 409 review_incomplete. A request nothing is wrong with is applied, not returned. An apply_blocked_reason an auto-apply recorded is cleared with the return — it belonged to the submission being sent back.
Emits data_request.returned on the supplier's feed — your own feed does not hear what you did — and the submitter is emailed.
Request
curl -X POST https://api.synexcloud.com/v1/data_requests/dreq_5a1c8e3f9b2d7046/return \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "data_request",
"id": "dreq_5a1c8e3f9b2d7046",
"status": "changes_requested",
"closed_reason": null,
"apply_blocked_reason": null,
"auto_apply": false,
"message": "Before the audit, please.",
"due_at": "2026-09-30T00:00:00Z",
"revision": 1,
"passport": {
"snowflake": "pass_3e9a1c5b7d2f4083",
"label": "LOT-870",
"ontology_id": "ontology_00a1b2c3d4e5f607",
"ontology_version": 3,
"default_language": "en"
},
"set": 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" },
"submitted_by": { "id": 11208, "name": "Erna Kuvalis" },
"reviewed_by": { "id": 9031, "name": "Mara Lindqvist" },
"applied_by": null,
"cancelled_by": null,
"source_passport": "pass_8d2f6a1c4e9b3057",
"counts": { "total": 3, "accepted": 2, "rejected": 1, "applied": 0 },
"values": [
{
"property_id": "property_serial",
"position": 0,
"definition": {
"id": "property_serial",
"name": "Serial number",
"type": "short_text",
"value": { "required": true }
},
"submitted_value": "SN-42",
"coerced_value": { "en": "SN-42" },
"review_status": "accepted",
"review_comment": null,
"reviewed_at": "2026-09-18T11:30:47Z",
"source": null,
"applied_at": null,
"current_value": { "en": "SN-OLD" }
},
{
"property_id": "property_pack_weight",
"position": 1,
"definition": {
"id": "property_pack_weight",
"name": "Weight",
"type": "measurement",
"value": { "measurement": "kg" },
"data_element_id": "BATT-PACK-WEIGHT"
},
"submitted_value": "12.5",
"coerced_value": 12.5,
"review_status": "rejected",
"review_comment": "Net weight, not gross — please re-weigh without the crate.",
"reviewed_at": "2026-09-18T11:30:47Z",
"source": {
"passport": "pass_8d2f6a1c4e9b3057",
"property": "property_cell_weight",
"match": "data_element",
"passport_label": "CELL-7",
"property_name": "Cell weight"
},
"applied_at": null,
"current_value": null
},
{
"property_id": "property_datasheet",
"position": 2,
"definition": {
"id": "property_datasheet",
"name": "Datasheet",
"type": "file",
"value": { "required": false }
},
"submitted_value": {
"snowflake": "file_5e2a9c7d1b4f0836",
"filename": "Report.pdf",
"extension": "pdf",
"size": 184320,
"link": "https://drive.synex.example/signed/file_5e2a9c7d1b4f0836?X-Amz-Expires=86400"
},
"coerced_value": {
"en": {
"snowflake": "file_5e2a9c7d1b4f0836",
"filename": "Report.pdf",
"extension": "pdf",
"size": 184320,
"link": "https://drive.synex.example/signed/file_5e2a9c7d1b4f0836?X-Amz-Expires=86400"
}
},
"review_status": "accepted",
"review_comment": null,
"reviewed_at": "2026-09-18T11:30:47Z",
"source": null,
"applied_at": null,
"current_value": {
"en": {
"snowflake": "file_1a7c4e9b3d0f2685",
"filename": "Old.pdf",
"extension": "pdf",
"size": 42011,
"link": "https://drive.synex.example/signed/file_1a7c4e9b3d0f2685?X-Amz-Expires=86400"
}
}
}
],
"created_at": "2026-09-15T09:12:40Z",
"submitted_at": "2026-09-17T13:05:22Z",
"reviewed_at": "2026-09-18T11:30:47Z",
"applied_at": null,
"cancelled_at": null,
"updated_at": "2026-09-18T11:33:15Z",
"viewer_role": "customer"
}
Cancel a data request
Takes the request back. Requires data_requests:write; the customer's call.
Anything not yet terminal — open, submitted, changes_requested — goes to cancelled with closed_reason: by_customer, and cancelled_by and cancelled_at stamped. Whatever the supplier had submitted stays on the request for the record, but nothing of it reaches the passport. Values already applied by an earlier partial apply stay on the passport; a cancel does not unwrite them.
A request that is already applied or cancelled is 409 data_request_not_open. There is no undo — open a new request.
Emits data_request.cancelled on your feed and, when the request had reached a supplier team, on theirs. Nobody is emailed.
Request
curl -X POST https://api.synexcloud.com/v1/data_requests/dreq_9f3c1b7e5a2d8460/cancel \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "data_request",
"id": "dreq_9f3c1b7e5a2d8460",
"status": "cancelled",
"closed_reason": "by_customer",
"apply_blocked_reason": null,
"auto_apply": false,
"message": null,
"due_at": null,
"revision": 0,
"passport": {
"snowflake": "pass_3e9a1c5b7d2f4083",
"label": "LOT-870",
"ontology_id": "ontology_00a1b2c3d4e5f607",
"ontology_version": 3,
"default_language": "en"
},
"set": null,
"supplier": {
"id": "sup_3f8b1d6e0a2c7495",
"name": "Helios Casings",
"status": "pending"
},
"customer_team": { "id": 4821, "name": "Kestrel Energy" },
"supplier_team": null,
"requested_by": { "id": 9031, "name": "Mara Lindqvist" },
"submitted_by": null,
"reviewed_by": null,
"applied_by": null,
"cancelled_by": { "id": 9031, "name": "Mara Lindqvist" },
"source_passport": null,
"counts": { "total": 1, "accepted": 0, "rejected": 0, "applied": 0 },
"values": [
{
"property_id": "property_casing_alloy",
"position": 0,
"definition": {
"id": "property_casing_alloy",
"name": "Casing alloy",
"type": "short_text",
"value": { "required": false }
},
"submitted_value": null,
"coerced_value": null,
"review_status": "pending",
"review_comment": null,
"reviewed_at": null,
"source": null,
"applied_at": null,
"current_value": null
}
],
"created_at": "2026-09-18T08:02:55Z",
"submitted_at": null,
"reviewed_at": null,
"applied_at": null,
"cancelled_at": "2026-09-19T10:40:12Z",
"updated_at": "2026-09-19T10:40:12Z",
"viewer_role": "customer"
}
Events
| Type | Fires when | Feed |
|---|---|---|
data_request.created | POST /v1/data_requests opens a request. With a recipient, the same call also emits supplier.invited for the link it opens. | Customer |
data_request.received | A request reaches a supplier team's inbox — at create over a linked supplier, or for every open request over a link the moment its invitation is accepted. | Supplier |
data_request.submitted | The supplier submits or resubmits. The snapshot is taken after any automatic apply, so it already says whether the answers landed, await review, or were held back and why. | Customer |
data_request.returned | The customer returns the submission — a return, or an apply that leaves a rejected value behind. | Supplier |
data_request.applied | The request reaches applied — by an apply, or by the submit of an auto_apply request. | Both |
data_request.cancelled | The request is cancelled — by the customer, by a link revoke, by the passport's deletion, or (once passport transfers ship) by its move to another team. | Customer; supplier too when a supplier team already held the request |
A review, a draft, a prefill and a partial apply emit nothing. The children a bulk data request opens emit no data_request.created or data_request.received of their own when the set creates them — the set's events announce them once — but they do receive data_request.received when the link's invitation is accepted, and every later event as single requests do.
Each snapshot is the request as that side reads it, without the presigned links, current_value or viewer_role a single read carries; re-fetch the request for those. The actor is the real person who acted, on both feeds. How one change lands on two teams' feeds is covered in two teams, one change.
Who is emailed
| When | Who |
|---|---|
A request is opened over a linked supplier | The supplier team's owner and the person who accepted the link, once each |
A request is opened with a recipient | The recipient — the supplier invitation, after the request has committed |
| The supplier submits | The person who opened the request, told whether the answers landed, await review, or were held back |
| The customer applies (closing the request) or returns it | The person who submitted. A submit that auto-applies sends only the requester's mail above. |
A review, a partial apply and a cancel send no mail. Every mail here except the invitation is queued and held until the change commits, so none can describe a change that was rolled back.
Errors
Beyond the standard error envelope, this surface has these codes:
| Code | Status | Meaning |
|---|---|---|
ability_missing | 403 | Create or apply without passports:write. param is passport. Refused before the request or passport is looked up. |
ability_not_scoped_to_resource | 403 | On create and apply, the key's passports:write does not cover the passport's ontology; on prefill, its passports:read does not cover the source's; or the key's data_requests:* grant carries an ontology scope. |
insufficient_permissions | 403 | On prefill, the key holds no passports:read. |
team_suspended | 403 | Your team is suspended: at the create tier, POST /v1/data_requests is refused; at the write tier, every write here is, apply included. On an auto_apply submit the suspension surfaces as apply_blocked_reason: team_suspended instead. |
parameter_invalid | 400 | A malformed body; a property that cannot be requested (properties.{i}); both supplier and recipient; a bad decisions entry; a draft without values; an unknown direction or status. |
value_invalid | 400 | On submit, a missing or invalid answer, a file that is not yours, or a property the request never asked for; on apply, a value the passport's pinned schema now refuses. errors is keyed by property id. |
resource_missing | 404 | A request your team is not on the right side of; a passport, link or source passport that is not yours — whether or not any of them exist. |
level_missing | 404 | On create, the passport's level is not in its pinned schema version. param is passport. |
link_revoked | 409 | The supplier named on create is a revoked link. |
supplier_already_linked | 409 | The recipient address already holds an active link with your team. |
data_request_not_open | 409 | Draft or submit on a request that is not open or changes_requested; cancel on one that is already terminal. |
data_request_not_submitted | 409 | Review, apply or return on a request that is not submitted. |
review_incomplete | 409 | Apply with nothing accepted, or return with nothing rejected. |
passport_released | 409 | Apply onto a released passport. Take it out of release first — see passports. |
ontology_locked | 409 | Apply while a passport transfer is running on the passport's ontology. Nothing is written. On an auto_apply submit the lock surfaces as apply_blocked_reason: ontology_locked instead. Not produced until passport transfers ship. |
passport_too_large | 413 | On apply, or the automatic apply of a submit, the write would push the passport past its 400 KB storage ceiling. Nothing is written; on a submit the submission has already committed. |