Skip to main content

External ingest

Use external ingest when your back office (ERP, CRM) is the master of your B2B customer data and the B2B Customer Registry (CRS) should hold a copy of it for the till. Your system pushes customers, their trusted agents and their projects, and addresses every record by your own ids. You never have to store or look up a CRS id to keep the copy in sync.

Every write goes to one of three collection paths. There is no id in the URL: the record is identified by the keys in the JSON body.

EntityPathPermission
Customer/api/v1/external-ingest/customerscrs.customer.write
Trusted agent/api/v1/external-ingest/agentscrs.agent.write
Project/api/v1/external-ingest/projectscrs.project.write

Each path takes PUT (create or replace), PATCH (update an existing record) and DELETE. The B2B Customer Registry API reference has the full schemas; this page explains how the calls fit together.

External ingest uses your own keys. Your back office first PUTs the customer, keyed by externalCustomerId and businessUnitGroup, then its agents and projects, keyed by their own external id plus the customer's keys; an agent or project whose customer does not exist gets 404. CRS creates or updates the record and returns its own id. Later PATCH and DELETE calls send the same keys in the body, and the keys themselves never change.

Before you start​

  • Base URL: https://crs-api.retailsvc.com/api/v1. Tenant ids ending in test use https://crs-api.retailsvc-test.com/api/v1.
  • Token: every call carries Authorization: Bearer <token>. For a back-office integration this is normally an OCMS client-credentials token. The tenant is always taken from the token; there is no tenant field in any body.
  • Permissions: the crs.*.write permission of each entity you push, as in the table above. The predefined role crs.api holds all of them. See Permissions and roles.
  • Business unit groups: if your customers belong to business unit groups, those groups must exist in Business Unit Management before you push a customer into them (see Business unit groups).
  • Body: send Content-Type: application/json. A body sent with another content type is not read as JSON and is answered with 400. Request bodies are limited to 1 MB.

Order of operations​

Push the customer first, then its agents and projects. An agent or a project is attached to a customer that already exists in CRS: if the customer is unknown, the agent or project PUT answers 404 "Customer not found" and nothing is written.

Delete in the opposite order. A customer that still has active agents or projects cannot be deleted (409 "Customer has active agents or projects"). Delete its agents and projects first, then the customer. Agents and projects that are already deleted do not block it.

Keys​

Each record is identified by your ids plus the business unit group:

EntityKey
CustomerexternalCustomerId + businessUnitGroup
Trusted agentexternalAgentId + externalCustomerId + businessUnitGroup
ProjectexternalProjectId + externalCustomerId + businessUnitGroup
  • Keys are unique per business unit group, not per tenant. The same externalCustomerId in two groups is two different customers, and a customer without a group is a third one. "No group" is a scope of its own, and keys are unique within it too.
  • Agent and project ids only have to be unique within their customer. Two customers can each have an agent A-1 or a project P-1.
  • An agent or project always belongs to its customer's group. The businessUnitGroup you send on an agent or project is used to find the customer, so it must be the customer's group.
  • Ids are trimmed of surrounding whitespace and then matched exactly, including case: acme-1 and ACME-1 are different keys. An id that is empty or only whitespace is refused with 400.

πŸ›‘ Send the same businessUnitGroup on every call for a record. It is part of the key, not a property of the record. If you leave it out, CRS looks for the record without a group:

  • a customer PUT without the group creates a second customer with no group, next to the one you meant;
  • a PATCH or DELETE without the group, and an agent or project call for a grouped customer without the group, answers 404.

Customers​

Create or replace: PUT​

PUT https://crs-api.retailsvc.com/api/v1/external-ingest/customers
{
"externalCustomerId": "10042",
"businessUnitGroup": "b2b-north",
"name": "Nordbygg AB",
"phone": "+46 70-123 01 99",
"address": "Industrivagen 12, 171 48 Solna",
"customerType": "CREDIT",
"status": "Active",
"requireAgent": true,
"requireProject": false,
"creditLimit": {
"total": 50000,
"available": 32500.5,
"versionTimestamp": "2026-10-26T08:00:00Z"
}
}

Only externalCustomerId and creditLimit are required. CRS looks the customer up by its key:

FoundResultStatus
No customer with this keythe customer is created201 Created
An active customerthe customer is replaced with the body200 OK
A deleted customerthe customer is restored under its original customerId and replaced with the body201 Created

The answer is the stored customer, including the customerId that CRS assigned:

{
"customerId": "0b7f6a52-3c1e-4d8f-9a63-2f5d1c7e8b40",
"externalCustomerId": "10042",
"name": "Nordbygg AB",
"phone": "+46701230199",
"address": "Industrivagen 12, 171 48 Solna",
"businessUnitGroup": "b2b-north",
"requireAgent": true,
"promotions": [],
"licenses": [],
"creditLimit": {
"total": 50000,
"available": 32500.5,
"versionTimestamp": "2026-10-26T08:00:00.000Z"
},
"requireProject": false,
"additionalInputs": [],
"status": "Active",
"customerType": "CREDIT"
}

PUT replaces the whole customer. Every optional field you leave out is cleared, the lists become empty and status goes back to Active, so a PUT without status unblocks an IsCreditBlocked customer. Send the full record every time; see Writing a customer.

  • To clear a field, leave it out. Sending null for an optional field is refused with 400. The one exception is loyalty, which accepts null.
  • Phone numbers must be international numbers with a country code. They are stored in E.164 form, so "+46 70-123 01 99" comes back as "+46701230199". A national number such as 0701234567 is refused with 400.
  • Fields without a value are left out of the answer; they are never returned as null. promotions, licenses and additionalInputs are always present.

What each field means for the till is described in Customer.

The credit limit and versionTimestamp​

creditLimit holds total, available and versionTimestamp, all required. The amounts are JSON numbers of 0 or more (decimals allowed, a numeric string such as "10" is 400), and available cannot be greater than total.

The stored credit limit is replaced only when the incoming versionTimestamp is strictly newer than the stored one. An older or equal timestamp leaves the stored credit limit as it is, without an error: the rest of the body is still applied and the answer is 200. So set versionTimestamp to the time your system changed the credit limit, not the time you send the message, and a late or repeated message can never roll a credit limit back. See Credit limit.

⚠ Only the credit limit is protected this way. The other fields of an older message still replace what is stored, so send customer changes in the order they happened.

⚠ When a PUT restores a deleted customer, the credit limit is always taken from the request, whatever its timestamp.

Send versionTimestamp as a full ISO 8601 date-time with Z or an offset, for example 2026-10-26T08:00:00Z or 2026-10-26T10:00:00+02:00. The answer always gives the time in UTC.

Update: PATCH​

PATCH https://crs-api.retailsvc.com/api/v1/external-ingest/customers

PATCH takes the same key fields (externalCustomerId, and businessUnitGroup when the customer has one) and the fields you send. It never creates a customer: an unknown or deleted customer answers 404 "Customer not found".

Use PUT for customers. Your system holds the complete customer, and a PUT with the full record keeps the copy in CRS exactly in line with your system.

Delete: DELETE​

DELETE https://crs-api.retailsvc.com/api/v1/external-ingest/customers
{
"externalCustomerId": "10042",
"businessUnitGroup": "b2b-north"
}

The answer is 204 No Content. See Deleting and restoring.

Trusted agents​

Create or replace: PUT​

PUT https://crs-api.retailsvc.com/api/v1/external-ingest/agents
{
"externalAgentId": "A-7",
"externalCustomerId": "10042",
"businessUnitGroup": "b2b-north",
"name": "Erik Lund",
"requireIdentification": true,
"phone": "+46 70 765 43 21",
"email": "erik.lund@nordbygg.example"
}

externalAgentId, externalCustomerId and name (1 to 40 characters) are required. The customer must exist and not be deleted, or the answer is 404 "Customer not found". The agent is then created (201), replaced (200) or, if it was deleted, restored under its original agentId (201).

{
"agentId": "b71e4c08-95a3-4f2d-8e61-0c9d3a7f5e24",
"customerId": "0b7f6a52-3c1e-4d8f-9a63-2f5d1c7e8b40",
"businessUnitGroup": "b2b-north",
"externalAgentId": "A-7",
"name": "Erik Lund",
"requireIdentification": true,
"phone": "+46707654321",
"email": "erik.lund@nordbygg.example"
}

The answer carries the customer's customerId, not its externalCustomerId.

As for customers, PUT replaces the agent: a requireIdentification, phone, email or identityNumber you leave out is cleared.

Change some fields: PATCH​

PATCH takes the agent's key and the fields to change: name, requireIdentification, phone, email, identityNumber. Fields you leave out keep their value.

⚠ An agent PATCH cannot remove a value. Leaving a field out and sending it as null both keep the stored value. To remove an agent's phone, email or identity number, send the agent with PUT without that field.

An unknown or deleted agent answers 404 "Trusted agent not found".

Delete: DELETE​

{
"externalAgentId": "A-7",
"externalCustomerId": "10042",
"businessUnitGroup": "b2b-north"
}

externalCustomerId is required here too, because an agent id is only unique within its customer. The answer is 204 No Content.

See Trusted agent for how agents are used at the till.

Projects​

Create or replace: PUT​

PUT https://crs-api.retailsvc.com/api/v1/external-ingest/projects
{
"externalProjectId": "P-2026-118",
"externalCustomerId": "10042",
"businessUnitGroup": "b2b-north",
"name": "Solna Strand, block C",
"addressLine1": "StrandvΓ€gen 12",
"zipCode": "171 54",
"city": "Solna",
"referenceId": "ADDR-5531",
"fromDate": "2026-09-01",
"toDate": "2027-06-30",
"restrictions": [
{
"type": "WHITELIST",
"propertyName": "department",
"allowedValues": ["building-materials", "tools"],
"allowIfMissing": false
}
]
}

externalProjectId, externalCustomerId and addressLine1 are required. The customer must exist and not be deleted, or the answer is 404 "Customer not found". The project is then created (201), replaced (200) or, if it was deleted, restored under its original projectId (201).

{
"projectId": "5c2d8e71-0a4b-4f96-b3e8-7d1f6a9c2e05",
"customerId": "0b7f6a52-3c1e-4d8f-9a63-2f5d1c7e8b40",
"externalCustomerId": "10042",
"externalProjectId": "P-2026-118",
"businessUnitGroup": "b2b-north",
"name": "Solna Strand, block C",
"addressLine1": "StrandvΓ€gen 12",
"zipCode": "171 54",
"city": "Solna",
"referenceId": "ADDR-5531",
"restrictions": [
{
"type": "WHITELIST",
"propertyName": "department",
"allowedValues": ["building-materials", "tools"],
"allowIfMissing": false
}
],
"fromDate": "2026-09-01",
"toDate": "2027-06-30",
"status": "Active"
}
  • fromDate and toDate are calendar dates (yyyy-mm-dd, no time). status is not stored: the returned status is worked out when the project is read, from today's date in UTC. It is Upcoming before fromDate, Expired after toDate, and Active otherwise.
  • referenceId is your own reference for the project, such as its address id in your ERP.
  • PUT replaces the project: a name, addressLine2, zipCode, city, referenceId, fromDate or toDate you leave out is cleared, and leaving out restrictions removes all restrictions.
  • The restriction types and how checkout applies them are described in Project restrictions.

Change some fields: PATCH​

PATCH takes the project's key and the fields to change: name, addressLine1, addressLine2, zipCode, city, referenceId, restrictions, fromDate, toDate. Fields you leave out keep their value. restrictions is replaced as a whole: "restrictions": [] removes them all, and "restrictions": null is refused with 400.

An unknown or deleted project answers 404 "Project not found".

Delete: DELETE​

{
"externalProjectId": "P-2026-118",
"externalCustomerId": "10042",
"businessUnitGroup": "b2b-north"
}

The answer is 204 No Content.

See Project for how projects are used at the till.

Keys are locked​

Your external ids and the businessUnitGroup never change on an existing record. On PATCH and DELETE they only identify the record. A PUT with a different id or group does not rename or move a record: it addresses a different record, and creates it if it does not exist.

To move a customer to another group, or to change its customer number, delete it under the old key and push it under the new one, then push its agents and projects again under the new key. The record under the new key gets a new customerId.

Deleting and restoring​

  • Delete is soft. A deleted record can no longer be read, searched for or changed, and a second DELETE answers 404.
  • A later PUT with the same key restores it, under the same customerId, agentId or projectId it had, and answers 201. The body of that PUT becomes the record, as with any other PUT.
  • There is no cascade. Deleting a customer is refused while it has active agents or projects, and restoring a customer does not bring them back: PUT its agents and projects again after the customer.
  • An agent or project cannot be restored while its customer is deleted (404 "Customer not found"). Restore the customer first.

⚠ DELETE carries a JSON body. The keys go in the body, not in the URL or the query string. Some HTTP clients and proxies drop the body of a DELETE; the call then fails with 400.

Business unit groups​

businessUnitGroup is optional. When you use it, it is the id of a business unit group in the B2B Customer Registry hierarchy (group type b2b-customer-registry) in Business Unit Management, with at least one business unit assigned to it directly.

When a customer or project is created or restored in a group that CRS does not know, the answer is 409 "Business Unit Group does not exist, or is not part of Customer Registry type" and nothing is written. Set up the group and assign its business units first. See Business unit group.

Writing a customer or project also needs write access to its group, or to all groups for a record without one. OCMS client tokens have access to every group of their tenant. See Who may read and write which records.

The CRS ids​

CRS gives every record its own id when it is created: customerId, agentId and projectId. The id never changes: updates and restores keep it. Your back office does not need it to stay in sync, because every ingest call is keyed by your ids.

You need the CRS id for the endpoints that address one record directly, for example:

CallUse
GET /customers/{customerId}read one customer
GET /agents/{agentId}, GET /projects/{projectId}read one agent or project
GET /projects/{projectId}/restrictionsread a project's restrictions
PATCH and DELETE on /customers/{customerId}, /agents/{agentId}, /projects/{projectId}change or delete one record by id

These endpoints work on the same records as ingest. Keep the id from the ingest answer if you need them. To find an id from your own id, search instead: GET /customers?externalCustomerId=10042, GET /agents?externalAgentId=A-7&externalCustomerId=10042 or GET /projects?externalProjectId=P-2026-118&externalCustomerId=10042. Search matches exactly and across all groups, so it can return one record per group. It needs the crs.<entity>.read permission, not write.

Records created through the id-based endpoints, or created at the till through your own system, can be updated through ingest too, by their external ids, as long as a project's group is its customer's group. Ingest uses one businessUnitGroup both to find the customer and as the project's key, so a project whose group differs from its customer's cannot be addressed through ingest.

Status codes​

StatusWhenWhat your system does
200 OKPUT updated an active record; PATCH succeedednothing
201 CreatedPUT created a record, or restored a deleted onekeep the returned id if you need it
204 No ContentDELETE succeedednothing
400 Bad Requestthe body is invalid: a missing or unknown property, a null on PUT, a wrong formatfix the message; do not resend it unchanged
403 Forbiddenthe token is missing or invalid, lacks the crs.*.write permission, or (for customers and projects) has no access to the customer's or project's groupfix the token or its roles
404 Not FoundPATCH or DELETE: the record is unknown or deleted. Agent or project PUT: the customer is unknown or deletedcheck the keys, including businessUnitGroup; push the customer first
409 Conflictsee belowsee below

CRS answers 403 for a missing or invalid token, never 401.

409 messageWhenWhat your system does
Business Unit Group does not exist, or is not part of Customer Registry typecustomer or project PUT creates or restores a record in an unknown groupset up the group in Business Unit Management, then resend
Customer external key conflict, Trusted agent external key conflict, Project external key conflictanother write created the same key at the same momentresend the PUT: it now finds the record and updates it
Customer has active agents or projectscustomer DELETEdelete the agents and projects, then the customer

A 400 lists what is wrong, by property:

{
"statusCode": 400,
"message": ["phone must be a valid phone number"],
"error": "Bad Request"
}

Other errors carry a single message:

{
"statusCode": 404,
"message": "Customer not found",
"error": "Not Found"
}

Branch on the status code, not on the error body; see When access is denied.

Retries and repeated messages​

Writes are synchronous: when you get the answer, the record is stored as the answer shows.

  • PUT is safe to repeat. The same PUT again gives 200 and the same id. Several identical PUTs at the same moment still produce one record: one gets 201, the others 200. After a timeout or a 5xx, resend the same PUT.
  • PATCH never creates. A 404 on PATCH means the record is not there; send it with PUT instead.
  • A repeated DELETE answers 404. If you retry a DELETE whose first answer you did not receive, treat a 404 as done.
  • Order matters for everything except the credit limit. A late message can never roll back a credit limit with a newer versionTimestamp, but its other fields replace what is stored.
  • Do not retry 400 or 403 unchanged. They do not resolve by themselves.