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.
| Entity | Path | Permission |
|---|---|---|
| Customer | /api/v1/external-ingest/customers | crs.customer.write |
| Trusted agent | /api/v1/external-ingest/agents | crs.agent.write |
| Project | /api/v1/external-ingest/projects | crs.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.
Before you startβ
- Base URL:
https://crs-api.retailsvc.com/api/v1. Tenant ids ending intestusehttps://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.*.writepermission of each entity you push, as in the table above. The predefined rolecrs.apiholds 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 with400. 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:
| Entity | Key |
|---|---|
| Customer | externalCustomerId + businessUnitGroup |
| Trusted agent | externalAgentId + externalCustomerId + businessUnitGroup |
| Project | externalProjectId + externalCustomerId + businessUnitGroup |
- Keys are unique per business unit group, not per tenant. The same
externalCustomerIdin 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-1or a projectP-1. - An agent or project always belongs to its customer's group. The
businessUnitGroupyou 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-1andACME-1are different keys. An id that is empty or only whitespace is refused with400.
π 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
PUTwithout the group creates a second customer with no group, next to the one you meant; - a
PATCHorDELETEwithout the group, and an agent or project call for a grouped customer without the group, answers404.
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:
| Found | Result | Status |
|---|---|---|
| No customer with this key | the customer is created | 201 Created |
| An active customer | the customer is replaced with the body | 200 OK |
| A deleted customer | the customer is restored under its original customerId and replaced with the body | 201 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
nullfor an optional field is refused with400. The one exception isloyalty, which acceptsnull. - 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 as0701234567is refused with400. - Fields without a value are left out of the answer; they are never returned as
null.promotions,licensesandadditionalInputsare 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"
}
fromDateandtoDateare calendar dates (yyyy-mm-dd, no time).statusis not stored: the returnedstatusis worked out when the project is read, from today's date in UTC. It isUpcomingbeforefromDate,ExpiredaftertoDate, andActiveotherwise.referenceIdis your own reference for the project, such as its address id in your ERP.PUTreplaces the project: aname,addressLine2,zipCode,city,referenceId,fromDateortoDateyou leave out is cleared, and leaving outrestrictionsremoves 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
DELETEanswers404. - A later
PUTwith the same key restores it, under the samecustomerId,agentIdorprojectIdit had, and answers201. The body of thatPUTbecomes the record, as with any otherPUT. - 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:
PUTits 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.