Partner API Integration Guide
This guide complements the API reference with the details you need to build and run an integration.
Environments and going live
| Environment | Base URL | Purpose |
|---|---|---|
| UAT | https://partner.api.lmplus-uat.com/v2 |
Building and testing your integration |
| Production | https://partner.api.lesmillsplus.com/v2 |
Live members |
UAT sends real messages. Join emails and SMS messages sent from UAT are delivered to the addresses and numbers you supply, and the join links they contain open the UAT LES MILLS+ site. Only use recipients you control or have permission to contact while testing.
UAT and production use separate credentials and separate data. A token issued by one environment is not valid in the other, and members created in UAT do not exist in production. To go live, switch the base URL and credentials; no other changes are required.
In the API reference, use the server selector on each endpoint to choose which environment the “Try it” panel calls.
Authentication
Every endpoint except /token requires a bearer token. Request one with your client
credentials, using either the OAuth2 client-credentials format or the JSON format.
curl -X POST https://partner.api.lmplus-uat.com/v2/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d "grant_type=client_credentials"
Use the returned access_token on subsequent requests:
curl https://partner.api.lmplus-uat.com/v2/partner/$PARTNER_ID/subscriptions \
-H "Authorization: Bearer $ACCESS_TOKEN"
Tokens expire after expires_in seconds. Cache the token and request a new one shortly
before it expires, rather than requesting a token for every call.
Choosing a join option
All three operations create an invitation for a member, identified by your externalId,
and produce a single-use join link. They differ in who delivers the link.
| Operation | Delivery | Notes |
|---|---|---|
POST /partner/{partnerId}/join-link |
You deliver the returned link yourself | Most flexible; use it to embed the link in your own app, email or messaging |
POST /partner/{partnerId}/send-join-email |
LES MILLS+ emails the link to sendTo |
Optional bcc and friendlyPartnerName |
POST /partner/{partnerId}/send-join-sms |
LES MILLS+ texts the link to mobileNumber |
Must be enabled for your partner account. Numbers must be in international format with a supported country code (+61, +64, +1, +44, +971, +966, +49) |
externalId may contain letters, numbers, hyphens and underscores (up to 50 characters).
Links are valid for expiryDays (1–365, default 30). Pass a sku to
choose the product the member receives, and metadata to store your own data against the
subscription; it is returned when you retrieve the subscription.
Subscription lifecycle
Retrieved subscriptions report two statuses:
| Field | Value | Meaning |
|---|---|---|
subscriptionStatus |
invited |
A join link has been issued; the member has not yet joined |
active |
The member has joined and has access | |
cancelled |
The subscription has been cancelled | |
activationStatus |
pending |
The join link has been issued and not yet used |
redeemed |
The join link has been used | |
cancelled |
The join link was cancelled | |
expired |
The join link expired before it was used |
The two statuses describe different things:
-
subscriptionStatusis the state of the member's subscription to LES MILLS+: whether they have been invited, are active, or have cancelled. -
activationStatusis the state of the join link (activation code) issued for that member: whether it is waiting to be used, has been used, or has been cancelled or expired. It is also the value you filter on withactivationStatuswhen listing subscriptions.
For example, a member who has just been sent a link is invited with a
pending link. Once they use it they become active, and the link is
redeemed.
A subscription stays active until you cancel it and cannot be reactivated; to bring a member back, issue a new join link.
Code expiry
What it is
Every join link carries an activation code, and every code has an expiry date. You set it when you
request the link with expiryDays (1–365 days; 30 if you leave it out). Once the
date passes, the link can no longer be used to join and the code's
activationStatus changes to expired.
What it's used for
Expiry limits how long a member has to use a link, so links you have emailed or texted don't stay
valid indefinitely. Choose expiryDays to match how quickly you expect members to join.
Billing
We bill for codes in the pending, redeemed and
expired states, so an expired code is still billed. Refreshing an expired code does not
cost anything extra.
Refreshing an expired code
To give a member more time, send the same request again, using join-link,
send-join-email or send-join-sms. The externalId and
sku must both match the original code. If you didn't send a
sku originally, don't send one now. The existing code is reused rather than replaced:
- The member gets the same link as before.
-
The expiry date is reset from today using the
expiryDaysyou send (or the 30 day default), and the code'sactivationStatusreturns topending. -
With
send-join-emailandsend-join-smsthe link is delivered to the member again.
POST /partner/{partnerId}/join-link
{
"externalId": "30393-30000",
"expiryDays": 30
}
A refresh is rejected with 400 ("Member is currently active.") if the member has
already joined with that code. If either the externalId or the sku doesn't
match, the old code is not refreshed: a new externalId is a different member, and a
different sku
creates a separate code for that product.
Cancelling subscriptions
-
By default a cancellation is scheduled and the response is
202 Accepted. Setimmediate=trueto cancel straight away. - Cancelling a subscription that is already cancelled also returns
202. -
Subscriptions with a redeemed fixed-term activation code cannot be cancelled; the
API returns
400. -
Cancellation is processed asynchronously. Use
cancelRequestedAtandcancelledAton the subscription to track progress.
Listing and pagination
GET /partner/{partnerId}/subscriptions returns up to limit subscriptions
(1–100, default 100), newest first. When more results exist the response includes a
nextPageToken. Pass it unchanged as the nextPageToken query parameter to
get the next page. When the response has no nextPageToken, you have reached the last
page.
To sync changes, filter with updatedAfter / updatedBefore (ISO 8601
timestamps) and activationStatus (one value, or a comma-separated list such as
cancelled,expired).
Errors
Errors return a JSON body with a message. Validation errors also list the offending
fields:
{
"message": "Data in the event is not valid",
"invalid-params": [
{ "name": "limit", "reason": "The limit can have value in between 1 and 100 only" }
]
}
| Status | Meaning | What to do |
|---|---|---|
400 |
The request is invalid, or the operation is not allowed |
Fix the request using invalid-params and message; do not retry
unchanged
|
401 |
Missing, invalid or expired token, or the feature is not enabled for your account | Request a new token; if it persists, contact your LM+ representative |
404 |
No subscription exists for that externalId |
Check the ID. Remember to URI-encode special characters in the path |
429 |
Rate limit or daily quota exceeded | Retry with backoff; see below |
5xx |
Something went wrong on our side | Retry with backoff |
Rate limits and retries
Limits apply per partner API key:
| Limit | Value |
|---|---|
| Sustained rate | 500 requests per second |
| Burst | 1,000 requests |
| Daily quota | 160,000 requests per day |
Requests over the limit receive 429 Too Many Requests. Retry 429 and
5xx responses with exponential backoff (for example 5s, 10s, 20s) and a capped number
of attempts. Do not retry 4xx responses other than 429 without changing
the request.
Getting help
For questions about your integration, or to request LES MILLS+ brand assets, contact us through the partner support form.