Skip to main content

Proxy configuration

Two B2B Customer Registry (CRS) operations are answered by your own system, not by CRS: validating a customer's credit limit and creating a customer. CRS forwards them to your system, and you tell CRS where that system is, and how to sign in to it, with the proxy configuration described here.

This is CRS's own configuration, stored and read by CRS alone. It is separate from the logistics proxy configuration service: a configuration stored there is never read by CRS, and this one is never read by the logistics services. If your system serves both, configure it in both places.

The endpoints your system must implement are described in Tenant system endpoints.

Setting it​

PUT https://crs-api.retailsvc.com/api/v1/proxy-config
GET https://crs-api.retailsvc.com/api/v1/proxy-config

Test tenants use https://crs-api.retailsvc-test.com/api/v1.

  • One configuration per tenant. The tenant is taken from your token, never from the body or the path. The same configuration serves both forwarded operations: there is no per-operation or per-service key.
  • Permissions: crs.proxy-config.write to write and crs.proxy-config.read to read. The role crs.api has both; the role crs.readonly has only crs.proxy-config.read. Without the permission the call is refused with 403.
  • PUT answers 204 No Content and replaces what was stored, whether or not something was stored before. GET answers 404 when nothing is stored.

The B2B Customer Registry API reference has the full contract.

The body​

With HTTP Basic:

{
"baseUrl": "https://erp.example.com/crs",
"type": "basic",
"config": { "username": "hii-retail", "password": "s3cret" }
}

With OAuth 2.0 client credentials:

{
"baseUrl": "https://erp.example.com/crs",
"type": "oauth",
"config": {
"clientId": "hii-retail",
"clientSecret": "s3cret",
"authUrl": "https://login.example.com"
}
}
FieldWhat it holds
baseUrlthe base address of your system. CRS appends /credit-limit:validate or /customers:create to it, so include any prefix and version your system needs. Surrounding whitespace and trailing slashes are removed when it is stored
typebasic or oauth, in lower case
config.username, config.passwordfor basic: the credentials CRS sends to your system
config.clientId, config.clientSecretfor oauth: the client CRS signs in to your identity provider as
config.authUrlfor oauth: your identity provider's address. See OAuth: what authUrl may be

All three top-level fields are required, and so is every field of config for the chosen type. type decides which config shape is accepted:

  • a config with a field the type does not have (for example scope), a missing or empty field, or the shape of the other type is refused with 400 and the message config must be a valid instance of BasicProxyConfigDto (or OAuthProxyConfigDto);
  • any type other than basic or oauth, including BASIC, is refused with 400 and type must be one of the following values: basic, oauth;
  • any other top-level property is refused with 400 and property <name> should not exist.

A 400 stores nothing. Its body lists every problem found:

{
"statusCode": 400,
"message": ["type must be one of the following values: basic, oauth"],
"error": "Bad Request"
}

⚠ baseUrl and authUrl are not checked as addresses when you store them. Any non-empty text is accepted with 204, and a wrong address only shows up when a forwarded call fails. Use an absolute https:// URL, without a query string: CRS builds the address by appending the path to baseUrl as text.

⚠ Credentials are used exactly as you send them. Values inside config are not trimmed, so a username sent as " hii-retail " reaches your system with the spaces. Send them without surrounding whitespace.

Credentials are stored encrypted and are never returned by the API.

Reading it back​

GET returns the address and the type, and nothing else:

{
"baseUrl": "https://erp.example.com/crs",
"type": "oauth"
}

No field of config comes back, not even masked. baseUrl is returned as stored, so without a trailing slash.

To change one field, send the whole configuration again, credentials included. There is no partial update, and a PUT without the credentials is refused with 400. A PUT may also switch type, for example from basic to oauth.

When nothing is stored, GET answers:

{
"statusCode": 404,
"message": "Proxy is not configured for tenant",
"error": "Not Found"
}

Switching it off​

A stored configuration cannot be removed or switched off through the API. There is no DELETE, and baseUrl is required on every PUT. To send calls somewhere else, store a configuration with the other address.

Until a configuration is stored, the two forwarded operations answer 412 Precondition Failed and nothing is sent anywhere. See When your system cannot answer.

When a change takes effect​

On the next forwarded call. CRS does not keep the configuration in memory: every forwarded call reads the stored one, so a PUT applies straight away, including a first one.

Only OAuth access tokens are kept, as described below. They are kept per authUrl, clientId and clientSecret, so changing any of the three makes the next call use a token for the changed client.

OAuth: what authUrl may be​

CRS finds your token endpoint with OpenID Connect discovery, so authUrl takes one of two forms:

  • your identity provider's issuer address, such as https://login.example.com. CRS reads the discovery document at https://login.example.com/.well-known/openid-configuration (the path is appended to any path in authUrl), and the issuer in that document must equal authUrl, otherwise the call fails. A trailing slash on a bare host does not matter, but https://login.example.com/realm/ does not match an issuer of https://login.example.com/realm. Microsoft Entra ID (login.microsoftonline.com) and Azure AD B2C (*.b2clogin.com) issuers are compared by those providers' own discovery rules.
  • the address of the discovery document itself, any URL containing /.well-known/, such as https://login.example.com/.well-known/openid-configuration. CRS reads it as it is and does not compare the issuer.

🛑 A token endpoint is not accepted as authUrl. CRS always reads a discovery document first and requests tokens from the token_endpoint it names. An identity provider without OpenID Connect discovery cannot be used. (The logistics proxy configuration does accept a token endpoint; CRS does not.)

More rules for the identity provider:

  • authUrl and the discovered token endpoint must both be https.
  • The discovery document is read without following redirects, so authUrl must point at it directly.
  • Reading the discovery document and requesting a token each have a time limit of 30 seconds.

Requesting the token​

  • Grant: client credentials (grant_type=client_credentials). No scope or other parameter is sent.
  • Client authentication: HTTP Basic on the token request, with clientId and clientSecret form-URL-encoded before they are joined, as OAuth 2.0 specifies.
  • Response: a JSON object with a non-empty access_token, a token_type of Bearer (in any case) and an expires_in in seconds. A response without them fails the call.

If the discovery document or a token cannot be fetched, the call fails with a 5xx error and your system is not called.

Reusing the token​

A token is reused until 1 minute before it expires. Then CRS fetches another one. A token with an expires_in of 60 seconds or less is never reused, so every call fetches one.

  • ⚠ A token you revoke at your identity provider keeps being sent until it nears expiry. A 401 from your system does not make CRS fetch another token, and the call is not repeated: if the 401 has a JSON body, the caller gets that 401.
  • Your identity provider may see several token requests for the same client close together, and several valid tokens may be in use at the same time.

What every call to your system carries​

Every forwarded call is a POST to baseUrl plus the operation's path, with a JSON body. CRS sets these headers:

HeaderValue
AuthorizationBasic with base64 of username:password (the raw values, not URL-encoded), or Bearer with a token from your identity provider
Content-Typeapplication/json
Tenant-Idthe tenant the call is made for, taken from the caller's token. Match header names case-insensitively

CRS sends no correlation id, no idempotency key, and nothing about the store or workstation. The standard headers of the HTTP client, such as Host, Content-Length, User-Agent, Accept and Accept-Encoding, are also present.

Each call is made once. CRS does not repeat a call that fails.

Do not redirect. Redirects are followed, but a 301, 302 or 303 turns the POST into a GET without a body, and a redirect to another host drops the Authorization header. Point baseUrl at the final address.

Time limits. CRS sets no time limit of its own on the call to your system, so a slow answer keeps the caller, often a cashier at the till, waiting for it. A call that fails at the network level, including a timeout in the HTTP client, reaches the caller as 502 Bad Gateway, and a very slow one can also end as 504 Gateway Timeout. The OAuth limits above apply to your identity provider.

What the caller gets for each answer your system can give is on Tenant system endpoints.

If you know the logistics proxy configuration​

The two configurations look alike but behave differently:

CRS proxy configurationLogistics proxy configuration
Addresshttps://crs-api.retailsvc.com/api/v1/proxy-confighttps://logistics-proxy-config.retailsvc.com/api/v1/proxy-config?service=...
Scopeone per tenant, no service keyone per tenant and service key
Address of your systemtop-level baseUrlapiConfig.url
Permissionscrs.proxy-config.write, crs.proxy-config.readlpc.proxy-config.set, lpc.proxy-config.get
Malformed addressaccepted, fails at call timerefused with 400
authUrlissuer or discovery document onlyissuer or token endpoint
GET returnsbaseUrl and type onlythe configuration, secrets masked
Partial updateno: resend the credentials every timeyes, by sending back the masked secret
Switching offnot possibleleave out apiConfig
A change applieson the next callwithin 10 minutes
401 from your systempassed to the caller, not repeatednew token fetched, call repeated once