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:

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:

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

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.