Data requests
A data request is a one-off, credential-bearing link that lets someone outside your team fill in a named subset of one passport's properties — a supplier confirming the capacity of a pack they shipped, a lab returning a test result. The recipient needs no Synex account and never sees the rest of your data: the link is emailed to them, and opening it renders a form holding exactly the properties you listed and nothing else.
Neither data_requests:read nor data_requests:write is in a key's default set, and neither is ontology-scopable. Grant them explicitly when the key is minted.
The Synex application could always create these, and could never list or revoke them. Once a link was mailed it dropped out of sight. List and revoke are new here; create is at parity — same fields, same email to the same recipient.
The data request model
- Name
object- Type
- string
- Description
Always
data_request.
- Name
id- Type
- string
- Description
Unique identifier, prefixed
dreq_.
- Name
passport- Type
- string
- Description
The passport whose properties are being requested.
- Name
properties- Type
- array
- Description
The property ids the form holds — and the only ones it can write.
- Name
recipient- Type
- string
- Description
The email address the link was sent to.
- Name
description- Type
- string
- Description
The note shown to the recipient explaining what you are asking for. Nullable.
- Name
expiry_date- Type
- string
- Description
When the link stops opening.
- Name
max_uses- Type
- integer
- Description
How many times the form may be submitted.
0means unlimited.
- Name
uses- Type
- integer
- Description
How many submissions have been consumed.
- Name
status- Type
- string
- Description
active,expiredorexhausted. See status.
- Name
created_at- Type
- string
- Description
ISO 8601 timestamp of creation.
The credentials appear exactly once
The 201 from POST /v1/data_requests carries three fields no other response does: token, secret, and the assembled url that combines them. Every other response uses the credential-free shape — every read, every list row, and every event payload.
That is also why a dreq_ public id had to be minted at all: if a request were addressed by its token, then every read of the list would be a handout of the credential, and data_requests:read would quietly be permission to fill in other people's forms.
The operational consequence is blunt: if the link is lost, revoke the request and create another. It cannot be recovered, by you or by support.
Status
| Status | Meaning |
|---|---|
active | The link opens and the form can be submitted |
expired | The expiry has passed; the link no longer opens |
exhausted | Every permitted use has been consumed |
expired rows survive — they are not cleaned up — and they are the usual reason a list holds a link nobody can use any more. Filter them out if you are showing outstanding work.
exhausted exists for completeness and is not normally observable. The fulfil path deletes the request the moment its last use is consumed, so an exhausted request stops existing rather than lingering in the list. Do not build a workflow that waits for a request to turn exhausted; watch for the data_request.fulfilled event, or for the request to disappear.
List data requests
Your team's requests, newest first. Requires data_requests:read. Returns a paginated list, without credentials.
The optional passport filter joins the cursor's scope, so a cursor minted with the filter set cannot be replayed without it.
Requests created before this surface existed are invisible to it. Those rows carry no team, and inferring an owner would mean reading each row's passport out of storage on every list call. They still work and still expire on their own schedule, so the gap closes without a backfill — but until then, an empty list is not proof that nothing is outstanding.
Optional attributes
- Name
passport- Type
- string
- Description
Narrow the list to one passport's requests.
- 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 "passport=pass_3e9a1c5b7d2f4083"
Response
{
"object": "list",
"data": [
{
"object": "data_request",
"id": "dreq_4f7a2e9c1b5d8306",
"passport": "pass_3e9a1c5b7d2f4083",
"properties": ["property_capacity", "property_chemistry"],
"recipient": "quality@nordvolt.example",
"description": "Please confirm the shipped cell capacity.",
"expiry_date": "2026-08-05T09:14:22Z",
"max_uses": 1,
"uses": 0,
"status": "active",
"created_at": "2026-07-22T09:14:22Z"
}
],
"has_more": false,
"next_cursor": null,
"url": "/v1/data_requests"
}
Create a data request
Creates the link and emails it to the recipient. Requires data_requests:write.
This is the only response that ever carries token, secret or url. Capture them if you need them; there is no way to read them back.
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 version — and must name a property the recipient can actually fill in. Rejected with 400 parameter_invalid on properties:
- an id not defined at the passport's level;
- a category divider, which is a heading rather than a field;
- a computed property, whose value is derived and not writable.
This is stricter than the application, where a mistyped id is accepted at creation and only discovered when the recipient opens a form with a field missing from it — by which point the email has already gone out.
The email is the delivery mechanism
There is no other way for the recipient to learn the link — the API will not hand you the url a second time to forward yourself. The mail is queued rather than sent inline, so a 201 means the request and its token exist, not that the message has already been delivered.
Required attributes
- Name
passport- Type
- string
- Description
The passport whose properties are being requested.
- Name
properties- Type
- array
- Description
The property ids the form will hold.
- Name
recipient- Type
- string
- Description
Email address the link is sent to. Named
recipientrather thanemailon purpose — it names who the request goes to, not how it travels.
Optional attributes
- Name
description- Type
- string
- Description
A note shown to the recipient explaining what you are asking for.
- Name
expiry_days- Type
- integer
- Description
How long the link stays usable, 1–365. Defaults to 7. The 365 ceiling is enforced here and was not in the application — a credential sitting in a third party's inbox forever is not a feature.
- Name
max_uses- Type
- integer
- Description
How many times the form may be submitted. Defaults to 1;
0means unlimited.
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_capacity", "property_chemistry"],
"recipient": "quality@nordvolt.example",
"description": "Confirm shipped capacity and cell chemistry",
"expiry_days": 14,
"max_uses": 1
}'
Response
{
"object": "data_request",
"id": "dreq_4f7a2e9c1b5d8306",
"passport": "pass_3e9a1c5b7d2f4083",
"properties": ["property_capacity", "property_chemistry"],
"recipient": "quality@nordvolt.example",
"description": "Please confirm the shipped cell capacity.",
"expiry_date": "2026-08-05T09:14:22Z",
"max_uses": 1,
"uses": 0,
"status": "active",
"created_at": "2026-07-22T09:14:22Z",
"token": "7b1e4c02a9d63f85c0e27a4b8d1f6395",
"secret": "4a0c9e75d2b81f36a7c5e0942b6d3f18e75c0a4b9d21f6837e0c5a49b2d67f13",
"url": "https://app.synex.example/external/data-request/7b1e4c02a9d63f85c0e27a4b8d1f6395?secret=4a0c9e75d2b81f36a7c5e0942b6d3f18e75c0a4b9d21f6837e0c5a49b2d67f13"
}
Retrieve a data request
Reads one request. Requires data_requests:read.
The credential-free shape: no token, no secret, no url.
Request
curl https://api.synexcloud.com/v1/data_requests/dreq_4f7a2e9c1b5d8306 \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "data_request",
"id": "dreq_4f7a2e9c1b5d8306",
"passport": "pass_3e9a1c5b7d2f4083",
"properties": ["property_capacity", "property_chemistry"],
"recipient": "quality@nordvolt.example",
"description": "Please confirm the shipped cell capacity.",
"expiry_date": "2026-08-05T09:14:22Z",
"max_uses": 1,
"uses": 0,
"status": "active",
"created_at": "2026-07-22T09:14:22Z"
}
Revoke a data request
Removes the row. Requires data_requests:write.
There is no revoked status, and that is deliberate on two counts.
First, consistency: the fulfil lifecycle already hard-deletes a request when it hits max_uses. If revocation left a tombstone instead, the list would mean two different things depending on how a request ended — present-but-dead for the ones you cancelled, absent for the ones that ran out — and every consumer would have to learn that distinction to count anything correctly.
Second, and more importantly, deleting makes revocation total. The credential is already sitting in somebody else's inbox, outside your control. The only guarantee worth giving about it is that it stops working, with nothing left behind that a future code path could accidentally honour.
Request
curl -X DELETE https://api.synexcloud.com/v1/data_requests/dreq_4f7a2e9c1b5d8306 \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "data_request",
"id": "dreq_4f7a2e9c1b5d8306",
"deleted": true
}
When the third party submits
Two things a consumer of this API needs to know about the far end of the link.
Values are validated before they are stored. Everything the form posts runs through the same rejecting validator the import pipeline and the property-edit endpoint use, checked against each property's own definition. The submission is all-or-nothing: if any value is rejected the whole submission fails, nothing is written, and no use is consumed — the recipient can correct it and submit again against the same link. Properties the recipient left untouched are skipped rather than overwritten with blanks.
This is a change in behaviour: until recently whatever the form posted was written verbatim, so "about forty" could land in a measurement property and sit there looking like data.
It emits an event with a null actor. A successful submission puts data_request.fulfilled on the change feed. The recorded actor is null, which is correct rather than missing information — the party filling the form is a stranger holding a link, with no user account and no API key to attribute the change to.
The payload names which properties were filled and never what they were filled with. Event payloads travel to customer webhook endpoints, and the submitted values are the confidential half of the exchange, exactly as the token and secret are. If you need the values, read the passport.
Events
| Type | Fires when |
|---|---|
data_request.created | A request is created. The snapshot never carries the token or secret. |
data_request.fulfilled | A third party submits the form. Carries properties_filled and no values, with a null actor. |
The submitted values also land as an ordinary passport update, so a consumer already watching passport.updated sees the change too — with the values, since a passport snapshot carries them. The two events describe the same write from different angles: one tells you a request was answered, the other tells you what the passport now says.