Getting started with the Partner API
How authentication works: two custom headers
The Partner API does not use OAuth 2.0 or OpenID Connect (OIDC). Authentication is a pair of static headers that you receive when your partnership is provisioned:
| Header | Value | Notes |
|---|---|---|
X-Client-Id | Your partner client identifier | Identifies which partner is calling. Safe to log. |
X-Client-Secret | Your partner client secret | Treat as a credential: never log it, never embed it in client-side code, never commit it to a repo. |
Every request must carry both headers. Calls without them are rejected.
Treat X-Client-Secret as a credential: never log it, never embed it in client-side code, and never commit it to a repo.
How to obtain credentials
Your X-Client-Id and X-Client-Secret are issued during partner onboarding. If you do not yet have them, contact your LuxID Account manager - they are not self-service through the LuxID Console for the time being.
How to rotate the secret
If your secret is ever exposed (logged in error, leaked through a code commit, etc.), request a rotation from your LuxID Account manager. Until rotation completes, treat the exposed secret as compromised and assume an attacker could call the API in your name.
Base URL and environments
All environments use the same shape (host + /services + /luxid-partner-api); only the host changes. As a partner you integrate against UAT first, then Production.
| Environment | Base URL | Availability |
|---|---|---|
| UAT (integration and testing) | https://api-uat.luxid.lu/services/luxid-partner-api | Partner-facing |
| Production | https://api.luxid.lu/services/luxid-partner-api | Partner-facing |
All endpoints below this base URL. For example, the "list applications" endpoint at path /applications is reached as:
GET https://api-uat.luxid.lu/services/luxid-partner-api/applications
Only HTTPS is supported. Plain HTTP calls are rejected.
Request and response format
All requests and responses are JSON-encoded.
| Direction | Header | Value |
|---|---|---|
| Request body | Content-Type | application/json |
| Response body | Accept | application/json (also the default if you omit the header) |
A complete first call
The smallest end-to-end call you can make is "list all applications managed by my partnership":
curl -sS \
-H "X-Client-Id: $LUXID_CLIENT_ID" \
-H "X-Client-Secret: $LUXID_CLIENT_SECRET" \
https://api-uat.luxid.lu/services/luxid-partner-api/applications
A successful response looks like:
{
"applications": [
{
"externalId": "app-abc-123",
"apiIdentifier": "my-client-id-in-oidc",
"name": "MyApp"
}
]
}
Two fields on each application matter, and they are easy to mix up:
externalIdis the path identifier used throughout the Partner API. When other endpoints expect{applicationExtId}in the URL, this is what they mean.apiIdentifieris the OIDCclient_idthat this application uses in the authentication layer. You will see it in theaudclaim of ID tokens. It is not what the Partner API expects in path parameters.
If you confuse the two, you typically get a 404 with a Not found error.
The standard error shape
Most application-level errors come back as JSON with type and message:
{
"type": "NOT_FOUND",
"message": "Application not found: applicationExtId"
}
The HTTP status carries the broad category; type and message give the specific reason.
| Status | Meaning | Typical causes |
|---|---|---|
400 | Functional error | Malformed request, invalid field, business-rule violation. Read message. |
401 | Unauthorized | Missing or invalid X-Client-Id / X-Client-Secret. Returned by the API gateway (see shape below). |
403 | Forbidden | Your credentials are valid, but the resource is not yours to access (wrong partner, wrong group, etc.). |
404 | Not found | The path identifier (applicationExtId, subscriberExtId, groupName, ...) does not match any resource. |
Depending on which layer rejects the request, you may see two other shapes:
-
Authentication failures (API gateway). Missing or invalid credentials return HTTP
401with a gateway body, not thetype/messageshape:{"httpCode": 401, "httpMessage": "Unauthorized", "moreInformation": "Invalid authentication."}A missing header reports
"Missing X-Client-Id in header.". An unrecognised URL path returns HTTP404in the same gateway shape ("No resources match requested URI."). -
Request-validation failures. A malformed query or path parameter can return an RFC 9457 (formerly RFC 7807) problem body:
{"type": "about:blank", "title": "Bad Request", "status": 400, "detail": "Failed to convert 'from' with value: '...'", "instance": "/luxid-partner-api/events/search"}
Network-layer 5xx errors can happen and should be retried with exponential backoff.
Looking up users by email: MD5 of the lowercase address
Some endpoints take an account lookup parameter as {md5HashOfLowercaseEmail} rather than the raw email. Accounts are looked up by the MD5 hash of the lowercased email, computed with no leading or trailing whitespace.
Generate the hash with whatever your platform offers (md5sum on Linux, hashlib.md5(...).hexdigest() in Python, the crypto module in Node.js, etc.). MD5 is used here for compactness and case-insensitive lookup; it is not a security feature. The hash is enumerable and must be protected exactly like the email address itself - treat it as functionally equivalent to the email for privacy and security purposes, never expose it in logs or client-side code, and do not rely on it to obscure the underlying address.
Identifiers you will see
The Partner API uses several distinct identifiers. Knowing which one each endpoint expects saves a lot of confusion:
| Identifier | What it is | Where it appears |
|---|---|---|
applicationExtId | LuxID's stable id for one of your applications | Path parameter on application, subscription, and group endpoints |
applicationInstanceExtId | LuxID's stable id for one specific instance of an application | Inside the Application response under instances[] |
apiIdentifier | The OIDC client_id of the application | Inside the Application response, also in OIDC token aud claim |
subscriberExtId | LuxID's stable id for a user in the context of one of your applications | Path parameter on subscription and claim endpoints |
md5HashOfLowercaseEmail | MD5 hash of the user's lowercased email | Path parameter on account-based lookup endpoints |
groupName | A partner-managed group name (string) | Path parameter on group endpoints |
A user (LuxID Account) gets a subscriberExtId per partner application when they first subscribe to it. The same account therefore has a different subscriberExtId in two different applications you manage.
What is a "subscriber"?
A subscriber is a LuxID Account that has an active subscription to one of your applications. In practice, this happens the first time the user successfully signs in to your application through LuxID and consents to share their data. From that moment, LuxID tracks the relationship between this account and your application, gives it a stable subscriberExtId, and records a Subscription with the date the relationship began.
A subscriber stops being a subscriber when the subscription is deleted - either by the user from account.luxid.lu (opens in a new tab) under Applications, or by you through the Partner API's delete subscription endpoint.
Pagination
Endpoints that return potentially large collections expose page and pageSize query parameters and return a paginated envelope:
{
"page": 0,
"pageSize": 10,
"total": 142,
"items": [ ... ]
}
page is zero-based. pageSize defaults to 10 if omitted. The Event Hub endpoint is the main consumer of this pattern today (see Event Hub).
Rate limits
The Partner API has rate limits applied per X-Client-Id. If you exceed them, calls are rejected until the window resets. The specific limits are not published - they are not part of the OpenAPI document and are not communicated alongside your credentials, because they may be adjusted dynamically. Plan your back-end to back off and retry on 4xx/5xx responses with sensible jitter.
Where to go next
You now have everything you need to call any endpoint. Pick the capability page that matches what you want to do: