Customers API Overview
The Customers API gives partners access to customer data — name, contact details, customer type, primary address, organization/contact relationships, and a search-by-email-or-mobile lookup.
All request/response examples use placeholder values; field names and types match what the API returns.
Base URL
Section titled “Base URL”https://<store-url>/public/customers/v1<store-url> is the base URL issued to your store, per environment (sandbox / production).
See Getting Started → Base URL for the full list of hosts.
All paths below are relative to it.
Authentication
Section titled “Authentication”Every request must be authenticated. You need two credentials on each call:
| Header | Value | Purpose |
|---|---|---|
Authorization |
Bearer <access_token> |
OAuth 2.0 JWT access token |
Ocp-Apim-Subscription-Key |
<your-subscription-key> |
Identifies your subscription at the API gateway |
- The access token must carry the
customer:readscope. A token without it is rejected with403 Forbidden. - Tokens are short-lived (typically 30–60 minutes). Request a new token when the old one expires.
- For details on obtaining a token, supported authorities, and the claims involved, see Getting Started → Authentication.
Beyond the scope check, per-customer access control applies: you only see customers your
account is entitled to. On Get customer by ID and
Get the organization a contact buys under,
requesting a customer you aren’t entitled to returns 403 Forbidden.
Search customers instead filters silently: a match you aren’t entitled to
is just missing from the result array, not reported as an error — see the note there.
Conventions
Section titled “Conventions”- Format: JSON only (
Accept: application/json). Field names are camelCase. - Optional fields are omitted from the payload entirely when not applicable — not serialized
as
null.email,mobile,primaryAddress,organizationNumber, andorganizationDatamay all be absent from a response. customerId,name,customerType,country,isB2B, andisPublicare never omitted and nevernull— every response has all six, always.customerTypeis always"Person"or"Organization"— every customer has one of the two.namecomes back as an empty string ("") when we have no value on record;customerId,country,isB2B, andisPublicare always meaningful —customerIdis never empty,countryis always a real, uppercase ISO 3166-1 alpha-2 country code, and the booleans are always computed, never “no value on record.” See Response model.primaryAddress’s own fields follow a different rule onceprimaryAddressitself is present:streetLine1,postalCode,city, andcountryare never omitted and nevernull, but — unlike the top-levelcountryabove — fall back to an empty string ("") when we have no value on record. OnlystreetLine2inside it is independently optional.- Each response carries an
X-Trace-Idheader. Quote it when contacting support — it lets us correlate your request with our logs.
Endpoints
Section titled “Endpoints”| Method | Path | Scope | Description |
|---|---|---|---|
GET |
/customers/v1/{customerId} |
customer:read |
Retrieve a single customer by ID |
GET |
/customers/v1/{contactId}/organization |
customer:read |
Get the organization a contact buys under |
GET |
/customers/v1/{organizationId}/contacts |
customer:read |
Get the contacts who can buy under an organization |
GET |
/customers/v1 |
customer:read |
Search customers by email and/or mobile |
1. Get customer by ID
Section titled “1. Get customer by ID”GET /customers/v1/{customerId}Retrieves a single customer by their unique ID.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
customerId |
string (GUID) | yes | The unique identifier of the customer. |
Responses
| Status | Meaning |
|---|---|
200 OK |
Customer found and you are entitled to see it. Body is { "customer": ... } — see Response model. |
400 Bad Request |
customerId was empty or not a valid GUID. |
401 Unauthorized |
Missing or invalid token. |
403 Forbidden |
Token lacks customer:read, or you are not entitled to this customer. |
404 Not Found |
No customer exists with that GUID — or it exists but isn’t visible (see note below). |
500 Internal Server Error |
Unexpected server error. |
404doesn’t distinguish “doesn’t exist” from “exists but isn’t visible.” A customer that has been archived or removed returns the same404 Not Foundas a GUID that was never valid. The response deliberately doesn’t reveal which case you hit.
Example response (200 OK)
{ "customer": { "customerId": "11111111-1111-1111-1111-111111111111", "name": "Ada Lovelace", "email": "ada.lovelace@example.com", "mobile": "91234567", "customerType": "Person", "isB2B": false, "isPublic": true, "country": "NO", "primaryAddress": { "streetLine1": "Enigmavegen 1", "postalCode": "0001", "city": "Oslo", "country": "NO" } }}A business customer looks the same, with isB2B: true, customerType: "Organization", and
(when on record) organizationNumber/organizationData populated — see
Response model. Any optional field not on record is omitted, not null
— except customerId, name, customerType, country, isB2B, and isPublic, which are never
omitted (see Conventions).
Example response (404 Not Found)
{ "type": "<docs-base-url>/errors/not-found", "title": "Not Found", "status": 404, "detail": "Customer '11111111-1111-1111-1111-111111111111' either does not exist or is archived or removed.", "instance": "/customers/v1/11111111-1111-1111-1111-111111111111", "code": "ERR_NOTFOUND", "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"}2. Get the organization a contact buys under
Section titled “2. Get the organization a contact buys under”GET /customers/v1/{contactId}/organizationRetrieves the organization associated with a contact — for example, the company an employee or customer contact buys under.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
contactId |
string (GUID) | yes | The customer ID of the contact (same value accepted by GET /customers/v1/{customerId}). |
Responses
| Status | Meaning |
|---|---|
200 OK |
Organization found and you are entitled to see it. Body is { "organization": ... }, the customer shape describing the organization. |
400 Bad Request |
contactId was empty or not a valid GUID. |
401 Unauthorized |
Missing or invalid token. |
403 Forbidden |
Token lacks customer:read. |
404 Not Found |
Contact has no associated organization — or one exists but isn’t visible to you (see note below). |
500 Internal Server Error |
Unexpected server error. |
404doesn’t distinguish “no organization” from “exists but you can’t see it” from “not a contact at all.”contactIdisn’t checked againstcustomerType— passing an Organization’s own ID, or a Person who isn’t a contact of any organization, produces the same404 Not Foundas a genuine contact with no organization, or one you aren’t entitled to read. The response deliberately doesn’t reveal which case you hit.
Example response (200 OK)
{ "organization": { "customerId": "22222222-2222-2222-2222-222222222222", "name": "Rubber Duck Enterprises AS", "customerType": "Organization", "isB2B": true, "isPublic": false, "country": "NO", "organizationNumber": "923609016", "organizationData": { "subOrganizationName": "Rubber Duck Enterprises", "subOrganizationNumber": "923609016-1", "organizationCountry": "NO", "organizationIds": {} } }}Example response (404 Not Found)
{ "type": "<docs-base-url>/errors/not-found", "title": "Not Found", "status": 404, "detail": "Contact '11111111-1111-1111-1111-111111111111' has no associated organization.", "instance": "/customers/v1/11111111-1111-1111-1111-111111111111/organization", "code": "ERR_NOTFOUND", "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"}3. Get the contacts who can buy under an organization
Section titled “3. Get the contacts who can buy under an organization”GET /customers/v1/{organizationId}/contactsRetrieves the persons associated with an organization. Empty array if the organization has none.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
organizationId |
string (GUID) | yes | The customer ID of the organization (same value accepted by GET /customers/v1/{customerId}). |
Responses
| Status | Meaning |
|---|---|
200 OK |
List of contacts you’re entitled to see. Body is { "contacts": [...] }, an array of customer objects — empty if the organization has no contacts, or none are visible. |
400 Bad Request |
organizationId was empty or not a valid GUID. |
401 Unauthorized |
Missing or invalid token. |
403 Forbidden |
Token lacks customer:read, or you are not entitled to this organization. |
500 Internal Server Error |
Unexpected server error. |
If you’re entitled to read the organization, you get every contact under it. An organization with no contacts returns
200 OKwith an empty array, not404.
Example response (200 OK)
{ "contacts": [ { "customerId": "11111111-1111-1111-1111-111111111111", "name": "Ada Lovelace", "email": "ada.lovelace@example.com", "customerType": "Person", "isB2B": false, "isPublic": true, "country": "NO" }, { "customerId": "33333333-3333-3333-3333-333333333333", "name": "Grace Hopper", "customerType": "Person", "isB2B": false, "isPublic": true, "country": "" } ]}4. Search customers
Section titled “4. Search customers”GET /customers/v1?email={email}&mobile={mobile}&customerType={customerType}&pageSize={pageSize}&continuationToken={continuationToken}Searches customers by email and/or mobile, scoped to your own store. At least one of email or
mobile is required.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
email |
string | one of email/mobile |
Email address to search for. At most 254 characters, must be a valid email address. |
mobile |
string | one of email/mobile |
Mobile number to search for. 5–14 digits, no formatting characters (no +, spaces, or dashes). |
customerType |
string | no | Filter results to Person or Organization. Applied after the search, so it doesn’t narrow what counts as a match on email/mobile. |
pageSize |
integer | no | Page size, 1–100. Null uses the server default. |
continuationToken |
string | no | Opaque token from a previous response, used to fetch the next page. |
Responses
| Status | Meaning |
|---|---|
200 OK |
List of matching customers you’re entitled to see. Body is { "customers": [...], "continuationToken": ... } — see Pagination. |
400 Bad Request |
Neither email nor mobile was set, one was set but malformed, or pageSize is outside 1–100. |
401 Unauthorized |
Missing or invalid token. |
403 Forbidden |
Token lacks customer:read. |
500 Internal Server Error |
Unexpected server error. |
Search is always scoped to your own store.
Unlike Get customer by ID, a match you aren’t entitled to never produces a
403here — it’s silently dropped from thecustomersarray instead. Each match is checked individually, so a search can return fewer results than it found, with no way to tell from the response alone that anything was filtered out.
Example response (200 OK)
{ "customers": [ { "customerId": "11111111-1111-1111-1111-111111111111", "name": "Ada Lovelace", "email": "ada.lovelace@example.com", "mobile": "91234567", "customerType": "Person", "isB2B": false, "isPublic": true, "country": "NO" } ], "continuationToken": null}Pagination (search customers)
Section titled “Pagination (search customers)”Search is cursor-based, not page-numbered:
- A response includes
continuationTokenwhen more results may be available, ornullat the end. - To fetch the next page, repeat the same request and pass the token back as the
continuationTokenquery parameter. - Per-customer access control is applied after paging. Because of this, a page may contain
fewer than
pageSizecustomers, or be empty, while still returning a non-nullcontinuationToken— see the entitlement note above. An empty page does not mean you are done; keep followingcontinuationTokenuntil it isnull.
Example response (400 Bad Request)
{ "type": "<docs-base-url>/errors/validation-failed", "title": "Validation Failed", "detail": "Some fields have validation errors", "status": 400, "instance": "/customers/v1", "code": "ERR_VALIDATION_0001", "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", "errors": [ { "fieldName": "Email", "message": "At least one of Email or Mobile must be set." } ]}Response model
Section titled “Response model”Every endpoint wraps its result in an envelope object, matching Orders’ and KomplettPc’s convention — none of these endpoints return a bare object or array at the top level:
| Endpoint | Envelope |
|---|---|
| Get customer by ID | { "customer": Customer } |
| Get the organization a contact buys under | { "organization": Customer } |
| Get the contacts who can buy under an organization | { "contacts": Customer[] } |
| Search customers | { "customers": Customer[], "continuationToken": string | null } |
All four wrap the same underlying shape — a single customer/organization/contact object, described once below as Customer.
Customer
| Field | Type | Description |
|---|---|---|
customerId |
string | The customer’s (or organization’s, or contact’s) unique identifier. Never null, never omitted, never empty. |
name |
string | Name. Empty string ("") when we have none on record — never null, never omitted. |
email |
string | Email address. Omitted if not on record. |
mobile |
string | Mobile phone number. Omitted if not on record. |
customerType |
"Person" | "Organization" |
Customer type. Every customer has one of these two values. Never null, never omitted. |
isB2B |
boolean | Whether this is a business customer. Never null, never omitted. |
isPublic |
boolean | Whether the organization is a public-sector entity (government, municipality, public school, kindergarten, etc.). Always false for "Person" customers. Never null, never omitted. |
country |
string | Uppercase ISO 3166-1 alpha-2 country code. Never null, never omitted, never empty. |
primaryAddress |
Address | The customer’s primary address, if on record. Omitted if not. |
organizationNumber |
string | Populated only when customerType is "Organization". Omitted otherwise. |
organizationData |
OrganizationData | Populated only when customerType is "Organization". Omitted otherwise. |
Address
Section titled “Address”Present only when primaryAddress itself is present.
| Field | Type | Description |
|---|---|---|
streetLine1 |
string | Street address line 1. Empty string when we have none on record — never null, never omitted once primaryAddress is present. |
streetLine2 |
string | Street address line 2. Omitted if not on record. |
postalCode |
string | Postal code. Empty string when we have none on record — never null, never omitted once primaryAddress is present. |
city |
string | City. Empty string when we have none on record — never null, never omitted once primaryAddress is present. |
country |
string | Uppercase ISO 3166-1 alpha-2 country code. Empty string when we have none on record — never null, never omitted once primaryAddress is present. |
OrganizationData
Section titled “OrganizationData”Present only when organizationData itself is present. Every field is omitted if not on
record.
| Field | Type | Description |
|---|---|---|
subOrganizationName |
string | Sub-organization name, if any. |
subOrganizationNumber |
string | Sub-organization number, if any. |
organizationCountry |
string | Organization’s uppercase ISO 3166-1 alpha-2 country code, if any. |
organizationIds |
object (string → string) | Additional organization identifiers, keyed by id type — see Organization identifier types. |
Organization identifier types
Section titled “Organization identifier types”Which key(s) can appear in organizationIds depends on the organization’s country. The top-level
organizationNumber field always holds the country’s primary registration number; whether that
same value is also duplicated into organizationIds varies by country and isn’t predictable
from one country’s behavior:
- Norway:
organizationIdsis typically empty — the number lives only in the top-levelorganizationNumberfield. - Denmark:
organizationNumberandorganizationIds["CVR"]hold the same value — the number is duplicated into both places. - Sweden:
organizationNumberandorganizationIds["OrganizationNumber"]hold the same value, same duplication pattern as Denmark.VATis only added separately when it differs from the organization number.
| Country | Key(s) in organizationIds |
Format |
|---|---|---|
| Norway (NO) | OrganizationNumber |
9 digits |
| Sweden (SE) | VAT, OrganizationNumber |
VAT only present when it differs from organizationNumber |
| Denmark (DK) | CVR |
8 digits |
| Finland (FI) | BusinessID |
1234567-8 (7 digits + hyphen + check digit) |
| Poland (PL) | NIP, REGON, KRS |
NIP: 10 digits · REGON: 9 or 14 digits · KRS: 10 digits |
| Lithuania (LT) | CompanyCode, PVM |
CompanyCode: 7 or 9 digits · PVM: LT + 9 or 12 digits |
| Latvia (LV) | CompanyRegistrationNumber |
11 digits, starts with 4 |
| Ireland (IE) | CRO, TRN |
CRO: 6–8 digits, may include a leading letter · TRN: 7 digits + 1–2 letters |
| Estonia (EE) | CompanyRegistrationCode, VAT |
CompanyRegistrationCode: 8 digits |
| Germany (DE) | VAT, HRB |
HRB: HRB + 1–5 digits |
Errors
Section titled “Errors”All errors use the RFC 9457 / RFC 7807 Problem Details JSON format.
Problem Details
| Field | Type | Description |
|---|---|---|
type |
string (URI) | Identifies the error category. |
title |
string | Short, human-readable summary of the category. |
detail |
string | Human-readable explanation specific to this occurrence. |
status |
integer | HTTP status code. |
instance |
string | The request path that produced the error. |
code |
string | Stable machine-readable error code (see table below). |
traceId |
string | Correlation id; also returned in the X-Trace-Id header. Quote it to support. |
Validation errors (400) include an additional errors array, one entry per invalid field:
{ "type": "<docs-base-url>/errors/validation-failed", "title": "Validation Failed", "detail": "Some fields have validation errors", "status": 400, "instance": "/customers/v1/not-a-guid", "code": "ERR_VALIDATION_0001", "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", "errors": [ { "fieldName": "CustomerId", "message": "CustomerId must be a valid GUID." } ]}Status / code reference
| HTTP status | code |
When it happens |
|---|---|---|
400 Bad Request |
ERR_VALIDATION_0001 |
A required id/query field was missing or malformed. See errors[]. |
401 Unauthorized |
— | Missing, malformed, or expired token / wrong auth scheme. |
403 Forbidden |
ERR_ACCESS |
Token lacks customer:read, or (endpoints 1 and 3 only) you aren’t entitled to the requested customer/organization. |
404 Not Found |
ERR_NOTFOUND |
No customer exists with the given id, or it/its relationship isn’t visible to you (see each endpoint above for specifics). |
500 Internal Server Error |
InternalServerError |
Unexpected server error. Retry later; quote the trace id if it persists. |
Quick start
Section titled “Quick start”curl -s "https://<store-url>/public/customers/v1/11111111-1111-1111-1111-111111111111" \ -H "Authorization: Bearer <access_token>" \ -H "Ocp-Apim-Subscription-Key: <subscription_key>" \ -H "Accept: application/json"curl -s "https://<store-url>/public/customers/v1?email=ada.lovelace%40example.com" \ -H "Authorization: Bearer <access_token>" \ -H "Ocp-Apim-Subscription-Key: <subscription_key>" \ -H "Accept: application/json"