Skip to content
innovorder
⌘K

Customer identity

Customer Data

Access and manage customer-related data including e-wallet balances, bulk imports, PDF tickets, and custom metadata.

E-Wallet & Balance

Retrieve e-wallet automatic reload configuration and balance history for a customer. Balance history tracks all e-wallet transactions (credits, debits, refunds).

GET/customers/{customerId}/ewallet_automatic_reloadGet E-Wallet Auto-Reload Config

Retrieve the automatic e-wallet reload configuration for a customer.

Parameters

NameTypeRequiredDescription
customerIdintegerYesThe unique identifier of the customer.

Response

json
{
  "status": 200,
  "code": "customer_ewallet_automatic_reload_succeed",
  "message": "Configuration retrieved successfully.",
  "data": {
    "automaticEwalletReloadActive": true,
    "automaticEwalletReloadMinBalance": 500,
    "automaticEwalletReloadAmount": 2000,
    "automaticEwalletReloadCardId": 12345
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"customer_ewallet_automatic_reload_succeed"Machine-readable application code for the result.
messagestring"Configuration retrieved successfully."Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.automaticEwalletReloadActivebooleantrueThe automatic ewallet reload active value.
data.automaticEwalletReloadMinBalanceinteger500The automatic ewallet reload min balance value.
data.automaticEwalletReloadAmountinteger2000The automatic ewallet reload amount value.
data.automaticEwalletReloadCardIdinteger12345Identifier of the associated automatic ewallet reload card.
Balance History

The full reference for GET /customers/{customerId}/balance_history (parameters, includes, and response schema) lives in the E-Wallet Balance & Transactions section.
In addition to the parameters documented there, the endpoint supports include=order and a date-range filter using unix timestamps: applyDate[gte]=1700000000&applyDate[lte]=1710000000.

GET/customers/{customerId}/balance_history/countGet Balance History Count

Retrieve the total number of balance history entries for a customer. Useful for pagination.

Parameters

NameTypeRequiredDescription
customerIdintegerYesThe unique identifier of the customer.

Response

json
{
  "status": 200,
  "code": "balance_count_succeed",
  "message": "Balance count retrieved.",
  "data": {
    "total": 142
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"balance_count_succeed"Machine-readable application code for the result.
messagestring"Balance count retrieved."Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.totalinteger142Total number of matching records.

Customer Import

Bulk import customers from a file. The endpoint accepts a file upload and processes it asynchronously, streaming progress updates back to the client via chunked transfer encoding.

Streaming Response

This endpoint uses Transfer-Encoding: chunked to stream progress updates during the import. The final chunk contains the standard JSON response with import results (customers created, updated, and unchanged).

POST/customers/importImport Customers from File

Upload a CSV/Excel file to bulk import customers for a brand. Requires a file in multipart/form-data format.

Parameters

NameTypeRequiredDescription
brandIdintegerYesThe brand ID to import customers into.
isMultiBadgebooleanNoIf true, the file format supports multiple badges per customer.
autoImportGuestHierarchybooleanNoIf true, automatically creates guest group hierarchy from the file data.

Request Body

json
multipart/form-data with a 'file' field containing the CSV or Excel file.

Response

json
{
  "status": 200,
  "code": "customers_import_create_succeed",
  "message": "Import completed successfully.",
  "data": {
    "customersCreated": 15,
    "customersUpdated": 3,
    "customersUnchanged": 82
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"customers_import_create_succeed"Machine-readable application code for the result.
messagestring"Import completed successfully."Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.customersCreatedinteger15The customers created value.
data.customersUpdatedinteger3The customers updated value.
data.customersUnchangedinteger82The customers unchanged value.

POST/customers/import_phoenixImport Phoenix Customer File

Upload a Phoenix-formatted customer file for a brand. The endpoint streams text progress chunks and ends with the standard JSON result payload.

Parameters

NameTypeRequiredDescription
brandIdintegerYesBrand that owns the imported customers.

Request Body

json
file=[binary]
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeRequiredExampleDescription
filebinaryYes[binary]Phoenix-formatted customer file uploaded as multipart/form-data.

Response

json
{
  "status": 200,
  "code": "customers_import_create_succeed",
  "message": "Customers have been imported successfully.",
  "data": {
    "customersCreated": 15,
    "customersUpdated": 3,
    "customersUnchanged": 82,
    "customersError": [
      {
        "line": 7,
        "email": "invalid@example.com",
        "invalidFields": [
          "email"
        ]
      }
    ],
    "customersWarn": [
      {
        "line": 8,
        "email": "duplicate@example.com",
        "invalidFields": [
          "email"
        ]
      }
    ],
    "otherWarnings": [
      {
        "type": "pricingRule",
        "list": [
          "UNKNOWN-RULE"
        ]
      }
    ],
    "validationErrors": []
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"customers_import_create_succeed"Machine-readable application code for the result.
messagestring"Customers have been imported successfully."Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.customersCreatedinteger15The customers created value.
data.customersUpdatedinteger3The customers updated value.
data.customersUnchangedinteger82The customers unchanged value.
data.customersErrorarray[…]List of customers error entries.
data.customersError[]object{…}Object containing customers error fields.
data.customersError[].lineinteger7The line value.
data.customersError[].emailstring"invalid@example.com"Email address.
data.customersError[].invalidFieldsarray[…]List of invalid fields entries.
data.customersError[].invalidFields[]string"email"The invalid fields value.
data.customersWarnarray[…]List of customers warn entries.
data.customersWarn[]object{…}Object containing customers warn fields.
data.customersWarn[].lineinteger8The line value.
data.customersWarn[].emailstring"duplicate@example.com"Email address.
data.customersWarn[].invalidFieldsarray[…]List of invalid fields entries.
data.customersWarn[].invalidFields[]string"email"The invalid fields value.
data.otherWarningsarray[…]List of other warnings entries.
data.otherWarnings[]object{…}Object containing other warnings fields.
data.otherWarnings[].typestring"pricingRule"The type value.
data.otherWarnings[].listarray[…]List of list entries.
data.otherWarnings[].list[]string"UNKNOWN-RULE"The list value.
data.validationErrorsarray[]List of validation errors entries.

Customer Base Export

Export a filtered customer base directly as CSV or XLSX. If a large export cannot complete within the synchronous request window, request the asynchronous report instead and retrieve it from report generation.

GET/customers/exportExport Customer Base File

Returns a CSV or XLSX attachment. The export is scoped to the requested brand and supports the same filters as the customer list.

Parameters

NameTypeRequiredDescription
brandIdintegerYesBrand whose customers are exported.
fileTypestringYesOutput format: csv or xlsx.
restaurantIdsstringNoComma-separated restaurant IDs used to filter the export.
searchstringNoFree-text customer search filter.
groupsstringNoComma-separated customer-group filters.
mustDisplayArchivedCustomersbooleanNoInclude archived customers when true.

Response

json
Binary attachment. Content-Type is text/csv for csv or application/vnd.openxmlformats-officedocument.spreadsheetml.sheet for xlsx; Content-Disposition contains the filename.

GET/customers/export/asyncRequest Asynchronous Customer Base Export

Creates a report-generation request for the same export filters. Use this when the synchronous export times out or for large customer bases.

Parameters

NameTypeRequiredDescription
brandIdintegerYesBrand whose customers are exported.
fileTypestringYesRequested output format: csv or xlsx.
restaurantIdsstringNoComma-separated restaurant IDs used to filter the export.
searchstringNoFree-text customer search filter.
groupsstringNoComma-separated customer-group filters.
mustDisplayArchivedCustomersbooleanNoInclude archived customers when true.

Response

json
{
  "status": 200,
  "code": "customer_base_export_request_created",
  "message": "Customer base export request is created successfully."
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"customer_base_export_request_created"Machine-readable application code for the result.
messagestring"Customer base export request is created successfully."Human-readable result message. Do not use this value for program logic.

Retrieve Generated Reports

An asynchronous export is made available through report generation. Brand and restaurant users see only reports they created. Admin and brand-group users must provide brandId when they do not have a brand context, and can include impersonatedUserId to see reports created by that user.

GET/report_generationList Generated Reports

Lists generated report files in the caller scope. Customer-base export reports become expired after their retention window; use expired to avoid requesting an unavailable download.

Parameters

NameTypeRequiredDescription
brandIdintegerNoBrand context required for an admin or brand-group user who has no own brand.
impersonatedUserIdintegerNoOptional impersonated user whose reports are included for an authorized admin or brand-group caller.

Response

json
{
  "status": 200,
  "code": "report_generations_succeed",
  "message": "The list of report generations have been found.",
  "data": [
    {
      "reportGenerationId": 812,
      "name": "customer-base-export-3325-2026-07-11.xlsx",
      "createdAt": "Fri Jul 11 2026 09:15:00 GMT+0000 (Coordinated Universal Time)",
      "reportType": "customer_base_export",
      "status": "succeed",
      "expired": false
    }
  ]
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"report_generations_succeed"Machine-readable application code for the result.
messagestring"The list of report generations have been found."Human-readable result message. Do not use this value for program logic.
dataarray[…]Endpoint-specific response payload.
data[]object{…}Endpoint-specific response payload.
data[].reportGenerationIdinteger812Identifier of the associated report generation.
data[].namestring"customer-base-export-3325-2026-07-11.xlsx"The name value.
data[].createdAtstring"Fri Jul 11 2026 09:15:00 GMT+0000 (Coordinated Universal Time)"Timestamp when this resource was created.
data[].reportTypestring"customer_base_export"The report type value.
data[].statusstring"succeed"HTTP status code returned by the API.
data[].expiredbooleanfalseThe expired value.

GET/report_generation/{reportGenerationId}/signed-urlGet Generated Report Download URL

Returns a time-limited signed URL for a generated report after verifying that the report belongs to the requested brand and to the caller (or authorized impersonated user). Expired or missing files cannot be downloaded.

Parameters

NameTypeRequiredDescription
reportGenerationIdintegerYesGenerated report identifier returned by the list endpoint.
brandIdintegerNoBrand context required for an admin or brand-group user who has no own brand.
impersonatedUserIdintegerNoOptional impersonated user whose report is being downloaded by an authorized caller.

Response

json
{
  "status": 200,
  "code": "report_generations_succeed",
  "message": "The list of report generations have been found.",
  "data": {
    "url": "https://storage.googleapis.com/<signed-report-url>"
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"report_generations_succeed"Machine-readable application code for the result.
messagestring"The list of report generations have been found."Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.urlstring"https://storage.googleapis.com/<signed-report-url>"The url value.

POST/customers/{customerId}/unarchiveUnarchive Customer

Restores an archived customer. Brand and brand-group users may only restore a customer in a brand they are authorized to manage.

Parameters

NameTypeRequiredDescription
customerIdintegerYesArchived customer to restore.

Response

json
{
  "status": 200,
  "code": "customer_unarchive_succeed",
  "message": "Customer has been successfully unarchived.",
  "data": {
    "success": true
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"customer_unarchive_succeed"Machine-readable application code for the result.
messagestring"Customer has been successfully unarchived."Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.successbooleantrueThe success value.

Customer Tickets

Generate PDF receipts for individual orders or export a customer's receipt history as a PDF. Bulk exports can be counted and paginated before downloading to keep large histories manageable.

Single Ticket PDF

GET/orders/tickets/{orderId}Get Ticket PDF

Generate and download a PDF receipt for a specific order. Returns the file directly as application/pdf.

Parameters

NameTypeRequiredDescription
orderIdstringYesThe unique identifier of the order.

Response

json
Binary PDF file (Content-Type: application/pdf). The response is an attachment; read the filename from the Content-Disposition response header.

Bulk PDF Export

First count the selected customer history, then request a PDF. For very large histories, uselimit and offset to retrieve bounded batches. A date range must include bothstartDate and endDate.

GET/orders/tickets/export/countCount Tickets Available for Export

Returns the number of customer tickets matching the optional inclusive ISO-date range.

Parameters

NameTypeRequiredDescription
customerIdintegerYesCustomer whose tickets are counted.
startDatestringNoISO 8601 start of the export range. Required together with endDate.
endDatestringNoISO 8601 end of the export range. Required together with startDate.

Response

json
{
  "count": 142
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
countinteger142Total number of matching records.

POST/orders/tickets/exportDownload Customer Ticket History PDF

Generates a PDF attachment for the selected customer history. The X-Total-Count response header contains the total matching records before pagination; Content-Disposition contains the filename.

Parameters

NameTypeRequiredDescription
customerIdintegerYesCustomer whose tickets are exported.
startDatestringNoISO 8601 start of the export range. Required together with endDate.
endDatestringNoISO 8601 end of the export range. Required together with startDate.
limitintegerNoPositive maximum number of tickets included in this PDF batch.
offsetintegerNoZero-based number of matching tickets to skip before this batch.

Response

json
Binary PDF file (Content-Type: application/pdf). Read the total matching ticket count from X-Total-Count and the filename from Content-Disposition.

Customer Metadata

Metadata entries belong to a customer and a brand. Each entry exposes customerMetadataId, customerId, brandId, namespace, data, createdAt and updatedAt. Older entries can have a null namespace. Use the explicit entry endpoints below for new integrations; the historical upsert and delete contracts remain available.

Authentication and scope

Send an authenticated bearer token. Every operation checks access to the brand and verifies that the customer belongs to it. Entry operations also verify that the entry belongs to that same customer and brand. The new /entries mutations accept Admin and Brand accounts only. Restaurant accounts have read-only metadata controls in the backoffice. The historical metadata routes retain their Admin, Brand, Restaurant and POS role allowlist for compatibility, subject to the brand-access checks; this does not grant those roles access to the new mutations.

Text, JSON and concurrency

  • mode is required on entry creation and update: text or json. The request data is always a string, including an empty string in text mode.
  • Text mode preserves the value literally. JSON mode parses the string once and stores its native JSON type: object, array, number, boolean, string or JSON null. Without a business schema, validation means valid JSON syntax.
  • For a JSON object, use JSON mode and serialize the object once into the request's string-valued data field, as in the creation example below. For the literal text {"enabled":true}, use text mode. JSON null is sent as {"mode":"json","data":"null"}.
  • Existing JSON-encoded strings remain readable. Depending on the storage/transport layer, returned data may be JSON text; do not recursively parse arbitrary strings or infer the write mode from a JSON-looking value. Preserve text values as text when editing; the backoffice can format legacy JSON strings for display.
  • Read the entry first. Send its exact updatedAt in the JSON body of PUT or DELETE as the expected version. On 409, reload, reconcile the changes and submit the fresh version; do not blindly overwrite.
  • The namespace is required, trimmed, non-empty and at most 255 characters on creation. It cannot be renamed by PUT. Updates/deletions use the returned customerMetadataId as the path metadataId, including for old entries whose namespace is null.

GET/brands/{brandId}/customers/{customerId}/metadataGet Customer Metadata

Read all entries, including automatically managed provenance. Use updatedAt from this response as the expected version for entry updates and deletions.

Parameters

NameTypeRequiredDescription
brandIdintegerYesBrand owning the customer.
customerIdintegerYesCustomer in that brand.

Response

json
{
  "status": 200,
  "code": "customer_metadata_fetched",
  "message": "The customer metadata has been fetched.",
  "data": [
    {
      "customerMetadataId": 1234,
      "customerId": 5337578,
      "brandId": 100,
      "namespace": "preferences",
      "data": {
        "preferredLanguage": "fr",
        "enabled": true
      },
      "createdAt": "2026-09-21T09:00:00.000Z",
      "updatedAt": "2026-09-21T09:00:00.000Z"
    }
  ]
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"customer_metadata_fetched"Machine-readable application code for the result.
messagestring"The customer metadata has been fetched."Human-readable result message. Do not use this value for program logic.
dataarray[…]Endpoint-specific response payload.
data[]object{…}Endpoint-specific response payload.
data[].customerMetadataIdinteger1234Identifier of the associated customer metadata.
data[].customerIdinteger5337578Identifier of the customer.
data[].brandIdinteger100Identifier of the brand.
data[].namespacestring"preferences"The namespace value.
data[].dataobject{…}Endpoint-specific response payload.
data[].data.preferredLanguagestring"fr"The preferred language value.
data[].data.enabledbooleantrueThe enabled value.
data[].createdAtstring"2026-09-21T09:00:00.000Z"Timestamp when this resource was created.
data[].updatedAtstring"2026-09-21T09:00:00.000Z"Timestamp when this resource was last updated.

POST/brands/{brandId}/customers/{customerId}/metadata/entriesCreate a Metadata Entry

Admin/Brand only. Explicit creation, not an upsert: an existing namespace returns 409 customer_metadata_conflict. The reserved provenance namespace cannot be created manually. Success returns 200.

Parameters

NameTypeRequiredDescription
brandIdintegerYesBrand owning the customer.
customerIdintegerYesCustomer in that brand.
namespacestringYesJSON body: unique non-empty namespace, trimmed, maximum 255 characters.
modestringYesJSON body: text or json.
datastringYesJSON body: literal text or serialized JSON according to mode.

Request Body

json
{
  "namespace": "preferences",
  "mode": "json",
  "data": "{\"preferredLanguage\":\"fr\",\"enabled\":true}"
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
namespacestring"preferences"The namespace value.
modestring"json"The mode value.
datastring"{\"preferredLanguage\":\"fr\",\"enabled\":true}"Endpoint-specific response payload.

Response

json
{
  "status": 200,
  "code": "customer_metadata_saved",
  "message": "The customer metadata has been saved.",
  "data": {
    "customerMetadataId": 1234,
    "customerId": 5337578,
    "brandId": 100,
    "namespace": "preferences",
    "data": {
      "preferredLanguage": "fr",
      "enabled": true
    },
    "createdAt": "2026-09-21T09:00:00.000Z",
    "updatedAt": "2026-09-21T09:00:00.000Z"
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"customer_metadata_saved"Machine-readable application code for the result.
messagestring"The customer metadata has been saved."Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.customerMetadataIdinteger1234Identifier of the associated customer metadata.
data.customerIdinteger5337578Identifier of the customer.
data.brandIdinteger100Identifier of the brand.
data.namespacestring"preferences"The namespace value.
data.dataobject{…}Endpoint-specific response payload.
data.data.preferredLanguagestring"fr"The preferred language value.
data.data.enabledbooleantrueThe enabled value.
data.createdAtstring"2026-09-21T09:00:00.000Z"Timestamp when this resource was created.
data.updatedAtstring"2026-09-21T09:00:00.000Z"Timestamp when this resource was last updated.

PUT/brands/{brandId}/customers/{customerId}/metadata/entries/{metadataId}Update a Metadata Entry

Admin/Brand only. Replace the content without renaming the namespace. A stale updatedAt returns 409. An unchanged value is a no-op.

Parameters

NameTypeRequiredDescription
brandIdintegerYesBrand owning the customer.
customerIdintegerYesCustomer in that brand.
metadataIdintegerYesPositive customerMetadataId returned by GET.
modestringYesJSON body: text or json.
datastringYesJSON body: replacement content.
updatedAtstring (ISO 8601)YesJSON body: exact version from the latest read.

Request Body

json
{
  "mode": "json",
  "data": "{\"preferredLanguage\":\"en\",\"enabled\":true}",
  "updatedAt": "2026-09-21T09:00:00.000Z"
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
modestring"json"The mode value.
datastring"{\"preferredLanguage\":\"en\",\"enabled\":true}"Endpoint-specific response payload.
updatedAtstring"2026-09-21T09:00:00.000Z"Timestamp when this resource was last updated.

Response

json
{
  "status": 200,
  "code": "customer_metadata_saved",
  "message": "The customer metadata has been saved.",
  "data": {
    "customerMetadataId": 1234,
    "customerId": 5337578,
    "brandId": 100,
    "namespace": "preferences",
    "data": {
      "preferredLanguage": "en",
      "enabled": true
    },
    "createdAt": "2026-09-21T09:00:00.000Z",
    "updatedAt": "2026-09-21T09:05:00.000Z"
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"customer_metadata_saved"Machine-readable application code for the result.
messagestring"The customer metadata has been saved."Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.customerMetadataIdinteger1234Identifier of the associated customer metadata.
data.customerIdinteger5337578Identifier of the customer.
data.brandIdinteger100Identifier of the brand.
data.namespacestring"preferences"The namespace value.
data.dataobject{…}Endpoint-specific response payload.
data.data.preferredLanguagestring"en"The preferred language value.
data.data.enabledbooleantrueThe enabled value.
data.createdAtstring"2026-09-21T09:00:00.000Z"Timestamp when this resource was created.
data.updatedAtstring"2026-09-21T09:05:00.000Z"Timestamp when this resource was last updated.

DELETE/brands/{brandId}/customers/{customerId}/metadata/entries/{metadataId}Delete a Metadata Entry

Admin/Brand only. Delete exactly one entry after checking its version. Send updatedAt in a JSON request body, not in the query string. The provenance entry cannot be deleted.

Parameters

NameTypeRequiredDescription
brandIdintegerYesBrand owning the customer.
customerIdintegerYesCustomer in that brand.
metadataIdintegerYesPositive customerMetadataId returned by GET.
updatedAtstring (ISO 8601)YesJSON body: exact version from the latest read.

Request Body

json
{
  "updatedAt": "2026-09-21T09:05:00.000Z"
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
updatedAtstring"2026-09-21T09:05:00.000Z"Timestamp when this resource was last updated.

Response

json
{
  "status": 200,
  "code": "customer_metadata_deleted",
  "message": "The customer metadata has been deleted.",
  "data": {}
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"customer_metadata_deleted"Machine-readable application code for the result.
messagestring"The customer metadata has been deleted."Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.

Entry error responses

  • 400 customer_metadata_invalid: invalid JSON content or invalid provenance context. Missing or malformed request fields can instead fail schema validation with HTTP 400.
  • 409 customer_metadata_conflict: duplicate namespace on POST, or stale version on PUT/DELETE.
  • 403 customer_metadata_forbidden: manual mutation of the reserved namespace. Authentication, role and brand authorization failures also apply and may use the API's standard permission errors.
  • 404 customer_metadata_missing: customer or entry not found in the requested brand/customer scope.

Historical integration contracts

These routes keep their string payloads and do not accept the new mode/version fields. Prefer the entry endpoints when you need explicit creation or conflict detection.

POST/brands/{brandId}/customers/{customerId}/metadataSave Customer Metadata (Historical Upsert)

Create or overwrite the entry in the same namespace. data is a required string; namespace is optional or null, maximum 255 characters. No updatedAt conflict check. innovorder.provenance is protected.

Parameters

NameTypeRequiredDescription
brandIdintegerYesBrand owning the customer.
customerIdintegerYesCustomer in that brand.

Request Body

json
{
  "namespace": "external_ids",
  "data": "{\"externalId\":\"EXT-99887\"}"
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
namespacestring"external_ids"The namespace value.
datastring"{\"externalId\":\"EXT-99887\"}"Endpoint-specific response payload.

Response

json
{
  "status": 200,
  "code": "customer_metadata_saved",
  "message": "The customer metadata has been saved.",
  "data": {
    "customerMetadataId": 1235,
    "customerId": 5337578,
    "brandId": 100,
    "namespace": "external_ids",
    "data": "{\"externalId\":\"EXT-99887\"}",
    "createdAt": "2026-09-21T09:00:00.000Z",
    "updatedAt": "2026-09-21T09:00:00.000Z"
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"customer_metadata_saved"Machine-readable application code for the result.
messagestring"The customer metadata has been saved."Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.customerMetadataIdinteger1235Identifier of the associated customer metadata.
data.customerIdinteger5337578Identifier of the customer.
data.brandIdinteger100Identifier of the brand.
data.namespacestring"external_ids"The namespace value.
data.datastring"{\"externalId\":\"EXT-99887\"}"Endpoint-specific response payload.
data.createdAtstring"2026-09-21T09:00:00.000Z"Timestamp when this resource was created.
data.updatedAtstring"2026-09-21T09:00:00.000Z"Timestamp when this resource was last updated.

DELETE/brands/{brandId}/customers/{customerId}/metadataDelete Customer Metadata (Historical Contract)

Delete entries in the optional namespace query filter. Without that filter, delete all non-reserved entries and retain innovorder.provenance. Explicitly targeting the reserved namespace is forbidden. No updatedAt conflict check.

Parameters

NameTypeRequiredDescription
brandIdintegerYesBrand owning the customer.
customerIdintegerYesCustomer in that brand.
namespacestringNoQuery parameter, maximum 255 characters. Omit to delete non-reserved entries; provenance is always retained.

Response

json
{
  "status": 200,
  "code": "customer_metadata_deleted",
  "message": "The customer metadata has been deleted.",
  "data": {}
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"customer_metadata_deleted"Machine-readable application code for the result.
messagestring"The customer metadata has been deleted."Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.

Optional Customer Provenance

innovorder.provenance is reserved and managed by the API. Do not write it through any metadata endpoint. Instead, authenticated staff/integration callers may include an optional provenance object in POST /brands/{brandId}/customers, PUT /customers/{customerId} or PUT /brands/{brandId}/customers/loader. The loader accepts Admin/Brand accounts. Customer self-service callers cannot submit an external provenance context.

Backward compatibility: existing creation, update and loader payloads remain valid without provenance. Omitting it does not activate tracking, erase an existing origin or stop tracking a customer that already has valid provenance. For HTTP, omit the field when unused; its nested fields are required only when it is supplied.

Provenance request fields

  • source (required): id and label, non-empty strings up to 255 characters; channel, non-empty string up to 64 characters. Supply a stable integration ID and a readable source label.
  • entryMode (required): non-empty string up to 64 characters, for example form, import or manual. Channel and mode are generic strings, not enumerations.
  • reference (optional): external request/import reference, up to 255 characters, or null.
  • actors (required): array of up to 10 declared actors; it may be empty. Each actor requires non-empty id and displayName (up to 255 characters), and role (up to 64). Roles such as requester and approver distinguish the participants. Names are snapshots, not live lookups.
  • at, authenticatedActor and the stored document's version are assigned by the API, not accepted as provenance request fields.

Example fragment to add to an otherwise valid customer creation/update/loader body:

json
{
  "provenance": {
    "source": {
      "id": "client-portal",
      "label": "Client portal",
      "channel": "portal"
    },
    "entryMode": "form",
    "reference": "request-123",
    "actors": [
      {
        "id": "portal:user:42",
        "displayName": "Alice Dupont",
        "role": "requester"
      },
      {
        "id": "portal:user:84",
        "displayName": "Theo Martin",
        "role": "approver"
      }
    ]
  }
}

An EDI connector supplies its own source, for example { id: "edi-connector", label: "EDI import", channel: "edi" }, with entryMode: "import". For manual entry by an approver, use entryMode: "manual" and an actor with role: "approver"; this contract does not introduce a new portal workflow. The authenticated account is recorded separately from these declared participants.

Stored document and lifecycle

The metadata entry contains a versioned document with optional creation and lastModification events. Example decoded data:

json
{
  "version": 1,
  "creation": {
    "at": "2026-09-21T09:00:00.000Z",
    "source": {
      "id": "client-portal",
      "label": "Client portal",
      "channel": "portal"
    },
    "entryMode": "form",
    "reference": "request-123",
    "actors": [
      {
        "id": "portal:user:42",
        "displayName": "Alice Dupont",
        "role": "requester"
      },
      {
        "id": "portal:user:84",
        "displayName": "Theo Martin",
        "role": "approver"
      }
    ],
    "authenticatedActor": {
      "id": "innovorder:user:12",
      "displayName": "Portal integration",
      "role": "operator"
    }
  }
}
  • Only an explicit provenance context initializes tracking. A new customer gets a creation event. For an existing customer, the first effective change with context records only lastModification; the API never reconstructs its origin.
  • After activation, effective profile, student/pricing/validity/status, badge and non-reserved metadata changes update lastModification, even without a new external context. The creation event stays unchanged. Identical reimports and unchanged metadata values do not replace the last trace.
  • The API supplies the server timestamp and available authenticated identity. Generic changes use an Innovorder/API source; staff and self-service customers have distinct actor identities. Declared requester/approver actors are claims from the authenticated integration, not independently authenticated accounts.
  • Changing another metadata entry does not activate provenance. Reserved internal writes do not recursively create a new trace. Data and provenance are written in the same transaction, with writes serialized per customer.
  • Credentials/authentication, orders and wallet movements are excluded. This is creation plus latest modification, not a complete audit history. Direct maintenance SQL/model writes are outside application tracking.
  • The backoffice displays Traceability only when the namespace contains a valid version-1 document with at least one event. Missing, empty or invalid provenance produces no section. Its metadata row is read-only and marked as automatically managed.

HTTP, Pub/Sub and rollout

Customer commands can carry the same optional provenance context through HTTP and the import Pub/Sub pipeline. The Avro customer command adds a nullable provenance field with default null; the consumer supports existing serialized commands as well as new ones. Old producers do not need to populate it. This is a command context, not a direct write to the reserved metadata namespace.

Deploy the compatible API and import consumer before enabling provenance in producers, then deploy the backoffice. No new table, historical backfill or full event history is required.