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.

EndpointAbilitySide
GET /v1/data_requestsdata_requests:readEither — direction=sent (default) or direction=received
POST /v1/data_requestsdata_requests:write and passports:write on the passport's ontologyCustomer
GET /v1/data_requests/:iddata_requests:readEither
GET /v1/data_requests/:id/prefilldata_requests:write, plus passports:read on the source passportSupplier
PUT /v1/data_requests/:id/draftdata_requests:writeSupplier
POST /v1/data_requests/:id/submitdata_requests:writeSupplier
POST /v1/data_requests/:id/reviewdata_requests:writeCustomer
POST /v1/data_requests/:id/applydata_requests:write and passports:write on the passport's ontologyCustomer
POST /v1/data_requests/:id/returndata_requests:writeCustomer
POST /v1/data_requests/:id/canceldata_requests:writeCustomer

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, applied or cancelled. See the lifecycle.

  • Name
    closed_reason
    Type
    string
    Description

    Why a cancelled request was cancelled: by_customer, link_revoked, passport_deleted or passport_transferred (not produced until passport transfers ship). Null otherwise.

  • Name
    apply_blocked_reason
    Type
    string
    Description

    Set on a submitted request whose auto_apply write was refused: passport_released, team_suspended or ontology_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. 0 while open; 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 (its pass_ id), label (its friendly_id at 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) and default_language (see languages).

  • Name
    set
    Type
    object
    Description

    The bulk data request this request is a child of, as { id, position, identifier_value }: the dset_ 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, name null 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_apply request 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

    customer or supplier — 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, the measurement unit, 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. type is one of short_text, long_text, number, boolean, date, tags, range, measurement, file or image.

  • 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, accepted or rejected. Reset to pending on 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_element or name_type when the prefill proposed it, manual when 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_at set 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)
StatusMeaning
openAwaiting the supplier's answers. revision is 0.
submittedThe 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_requestedThe 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.
appliedTerminal. Every value has reached the passport.
cancelledTerminal. 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_value and coerced_value gains a link: 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 carries link: null. A bare file_… 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's name — 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, is 400 value_invalid on 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.


GET/v1/data_requests

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

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

POST/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 category divider, 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 a pending or linked supplier of yours. Required unless recipient is given.

  • Name
    recipient
    Type
    object
    Description

    An address to invite as a new supplier, as { email, name } — name being your 1–120 character label for them. Required unless supplier is 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

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_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"
}

GET/v1/data_requests/:id

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

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

GET/v1/data_requests/:id/prefill

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:

  1. 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.
  2. 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 typeAccepted source typesConversion
short_text, long_texttext, number, measurement, tags, boolean, datenumber → plain decimal without trailing zeros; measurement → number and source unit, as 2342 mAh; tags → joined with commas; boolean → true/false; date → YYYY-MM-DD
numbernumber, measurement, textmeasurement → its number, unit dropped; text → only when numeric once trimmed
measurementmeasurement, number, textas for number; another unit → offered with a warning, never proposed
rangerange in the same unitas is
booleanboolean, text, numbertext → true/false/yes/no/1/0, case-insensitive; number → 1 or 0 only
datedate, texttext → only when it reads as one calendar day; ambiguous dates such as 01/02/2026 are not offered
tagstags, texttext → split on commas, trimmed, empties dropped
filefile, imageas is
imageimageas 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 (its pass_ id), label (its friendly id, or its id when it has none) and default_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_id and matched_by (data_element or name_type) for the proposal, or null when nothing was proposed; the proposed value, its note, and warning (always null on a proposal); and candidates, each { id, name, type, unit, value, note, warning, suggested } — type and unit the source property's own, suggested true on exactly the proposed one.

Request

GET
/v1/data_requests/dreq_5a1c8e3f9b2d7046/prefill
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
        }
      ]
    }
  ]
}

PUT/v1/data_requests/:id/draft

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 }. source is 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

PUT
/v1/data_requests/dreq_5a1c8e3f9b2d7046/draft
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"
}

POST/v1/data_requests/:id/submit

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. A value is required for every open property.

Request

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

POST/v1/data_requests/:id/review

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 }. status is accepted, rejected or pending; comment, at most 2000 characters, is required on a rejection and ignored otherwise.

Request

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

POST/v1/data_requests/:id/apply

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:

  • applied once every value has landed — data_request.applied on both feeds, and the submitter is emailed;
  • changes_requested when 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.returned on the supplier's feed, and the submitter is emailed;
  • still submitted when 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

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

POST/v1/data_requests/:id/return

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

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

POST/v1/data_requests/:id/cancel

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

POST
/v1/data_requests/dreq_9f3c1b7e5a2d8460/cancel
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

TypeFires whenFeed
data_request.createdPOST /v1/data_requests opens a request. With a recipient, the same call also emits supplier.invited for the link it opens.Customer
data_request.receivedA 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.submittedThe 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.returnedThe customer returns the submission — a return, or an apply that leaves a rejected value behind.Supplier
data_request.appliedThe request reaches applied — by an apply, or by the submit of an auto_apply request.Both
data_request.cancelledThe 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

WhenWho
A request is opened over a linked supplierThe supplier team's owner and the person who accepted the link, once each
A request is opened with a recipientThe recipient — the supplier invitation, after the request has committed
The supplier submitsThe person who opened the request, told whether the answers landed, await review, or were held back
The customer applies (closing the request) or returns itThe 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:

CodeStatusMeaning
ability_missing403Create or apply without passports:write. param is passport. Refused before the request or passport is looked up.
ability_not_scoped_to_resource403On 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_permissions403On prefill, the key holds no passports:read.
team_suspended403Your 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_invalid400A 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_invalid400On 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_missing404A 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_missing404On create, the passport's level is not in its pinned schema version. param is passport.
link_revoked409The supplier named on create is a revoked link.
supplier_already_linked409The recipient address already holds an active link with your team.
data_request_not_open409Draft or submit on a request that is not open or changes_requested; cancel on one that is already terminal.
data_request_not_submitted409Review, apply or return on a request that is not submitted.
review_incomplete409Apply with nothing accepted, or return with nothing rejected.
passport_released409Apply onto a released passport. Take it out of release first — see passports.
ontology_locked409Apply 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_large413On 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.

Was this page helpful?