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 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. 0 means unlimited.

  • Name
    uses
    Type
    integer
    Description

    How many submissions have been consumed.

  • Name
    status
    Type
    string
    Description

    active, expired or exhausted. 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

StatusMeaning
activeThe link opens and the form can be submitted
expiredThe expiry has passed; the link no longer opens
exhaustedEvery 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.


GET/v1/data_requests

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.

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

GET
/v1/data_requests
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"
}

POST/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 recipient rather than email on 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; 0 means unlimited.

Request

POST
/v1/data_requests
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"
}

GET/v1/data_requests/:id

Retrieve a data request

Reads one request. Requires data_requests:read.

The credential-free shape: no token, no secret, no url.

Request

GET
/v1/data_requests/dreq_4f7a2e9c1b5d8306
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"
}

DELETE/v1/data_requests/:id

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

DELETE
/v1/data_requests/dreq_4f7a2e9c1b5d8306
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

TypeFires when
data_request.createdA request is created. The snapshot never carries the token or secret.
data_request.fulfilledA 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.

Was this page helpful?