Partner endpoints.
Complete endpoint reference for partner-scoped agent operations.
All partner endpoints are prefixed with /api/agent/v1/partner and require a partner-scoped key (rl_agent_).
Common patterns
All partner endpoints share these behaviors:
- Multi-tenant isolation. A partner key can access resources created by that partner and no other
- Canonical API contracts. The platform validates requests against the live agent schema, permission gates, and ownership checks
- JSON request/response. Send and receive
application/json - Pagination. List endpoints support
?page=1&per_page=25query parameters
Health check
GET /api/agent/v1/health
Available to all key roles. Returns key identity, scopes, and owner information. Use this to verify your key works before making other requests.
No scope required.
Clubs
Clubs organize a partner's staff and operations. Most resources (cards, staff) belong to a club.
List clubs
GET /api/agent/v1/partner/clubs
Scope: read or write:clubs
Get club
GET /api/agent/v1/partner/clubs/{id}
Scope: read or write:clubs
Create club
POST /api/agent/v1/partner/clubs
Scope: write:clubs
{
"name": "Downtown Location",
"is_active": true
}
Update club
PUT /api/agent/v1/partner/clubs/{id}
Scope: write:clubs
Delete club
DELETE /api/agent/v1/partner/clubs/{id}
Scope: write:clubs
Loyalty cards
Loyalty cards define point-earning rules and associated rewards.
List cards
GET /api/agent/v1/partner/cards
Scope: read or write:cards
Get card
GET /api/agent/v1/partner/cards/{id}
Scope: read or write:cards
Create card
POST /api/agent/v1/partner/cards
Scope: write:cards
Update card
PUT /api/agent/v1/partner/cards/{id}
Scope: write:cards
Delete card
DELETE /api/agent/v1/partner/cards/{id}
Scope: write:cards
Rewards
Rewards are what members save up for. Rewards can be linked to one or more loyalty cards.
List rewards
GET /api/agent/v1/partner/rewards
Scope: read or write:rewards
Get reward
GET /api/agent/v1/partner/rewards/{id}
Scope: read or write:rewards
Create reward
POST /api/agent/v1/partner/rewards
Scope: write:rewards
Update reward
PUT /api/agent/v1/partner/rewards/{id}
Scope: write:rewards
Delete reward
DELETE /api/agent/v1/partner/rewards/{id}
Scope: write:rewards
Transactions
Transactions handle the core earn-and-burn flow. POS integrations spend most of their calls here.
List transactions
GET /api/agent/v1/partner/transactions
Scope: read or write:transactions
Lists transactions for your partner account, newest first. Supports the following query parameters:
| Parameter | Type | Description |
|---|---|---|
member_identifier |
string | Filter by member (UUID, email, or unique_identifier) |
card_id |
UUID | Filter by a specific loyalty card |
event |
string | Filter by event type (e.g., staff_credited_points_for_purchase) |
from |
date | Start date (Y-m-d) |
to |
date | End date (Y-m-d) |
per_page |
int | Results per page (default 25, max 100) |
Record a purchase (award points)
POST /api/agent/v1/partner/transactions/purchase
Scope: write:transactions
{
"card_id": "uuid-of-loyalty-card",
"member_identifier": "[email protected]",
"purchase_amount": 24.50,
"staff_id": "uuid-of-staff-member",
"note": "Coffee and pastry"
}
Member identification. The member_identifier field accepts multiple formats:
- Member UUID
- Email address
- Member number
- Unique identifier
You can pass any of these formats and the API will resolve the member. This flexibility matters for POS systems that may have nothing but a customer email or loyalty number.
You can also pass a points field (integer) to override the automatic point calculation. This is useful for custom promotions or manual adjustments. The card's max_points_per_purchase still caps the override after tier multipliers, so you can never exceed the configured maximum.
On a retry-safe purchase, points also has an absolute ceiling of 2147483647, the largest value a single award can be recorded as. A larger override is refused with 422, and so is a sale whose calculated award would land past it once tier and bonus-window multipliers are applied: the check runs before the sale is recorded, so it is a request to correct, never a retry to repeat. A card whose welcome bonus is configured beyond that ceiling is refused the same way, since the bonus is a second award the first sale would have to record.
The staff_id is optional. It attributes the transaction to a specific staff member for reporting. Without it, the platform attributes the transaction to "System". A staff member you name must belong to your partner account and have access to the card's club. Naming one who does not is refused either way, but the answer differs: a retry-safe purchase returns 404 before anything is recorded, while a purchase without source_reference returns 422 TRANSACTION_FAILED.
Retry-safe purchases
Add source_reference and the sale is recorded once, however many times the request arrives:
{
"card_id": "uuid-of-loyalty-card",
"member_identifier": "[email protected]",
"purchase_amount": "24.50",
"currency": "EUR",
"source_completed_at": "2026-08-26T09:41:07+02:00",
"source_reference": "pos:store-1:till-3:98441",
"note": "Coffee and pastry"
}
| Field | Rules |
|---|---|
source_reference |
Optional. Turns on retry-safe recording. Up to 128 characters, case-sensitive, starting with a letter or digit, then letters, digits, ., _, :, or -. Include platform, store, till, and sale id. Never an email address, phone number, customer name, or credential. |
currency |
Required with source_reference. Three uppercase letters, and it must match the card's currency exactly. Amounts are never converted. |
source_completed_at |
Required with source_reference. An ISO 8601 instant with whole seconds, written either with a numeric offset (2026-08-26T09:41:07+02:00) or with the UTC Z designator (2026-08-26T07:41:07Z). The two are equivalent, including on a retry, so a queue may re-serialize one as the other. Fractional seconds (2026-08-26T07:41:07.000Z) are rejected; trim them if your till emits them. So is any offset beyond ±14:00, which no real time zone uses. For a new sale the time may be at most 7 days old and 5 minutes in the future. It also sets the transaction time, so a delayed sale earns by the bonus window that was in force when it happened, and its points expire from that moment. |
purchase_amount |
Send it as a string with a retry-safe purchase ("24.50") so the exact amount survives, and omit it only when you send points instead. |
Identity is your business plus the reference, so two businesses may use the same reference and two tills inside one business must not. Replacing, revoking, or deleting an Integration API key does not reset a recorded sale.
What is kept after a correction or an erasure. When a recorded sale is corrected away, or a member removes their relationship with your business or erases their account, the sale's own facts go with it: the points, the balance, the amount, the currency, the link to the transaction, and the protected copy of the request. What remains is your business, a one-way digest of the sale reference, and the fact that the reference was settled.
That digest is kept for the lifetime of the business, for one reason: a till
that retries an erased sale has to be told the sale is settled (410), or
erasure would become a way to make one purchase pay out twice. It is not
used for anything else. No profiling, no marketing, no analytics. It is
pseudonymous rather than anonymous: it is derived from a reference you chose,
so anyone holding that reference can recompute it. Deleting the business
removes it along with everything else.
Presentation is ignored when the app compares a retry with the original: 24.5 and 24.50, +00:00 and Z for the same instant, a different time zone for the same instant, a different key order, and a different note all still count as the same sale. The sale's own facts are not: member_identifier, staff_id, and points are all part of what identifies the sale, so a retry must repeat them exactly. Sending a different member_identifier returns 409 even when it resolves to the same member, and so does a retry that drops the staff_id the first call carried. Worth checking if a queue replays your requests without the cashier context.
Responses:
| Situation | HTTP | Body |
|---|---|---|
| Recorded | 201 |
The purchase facts, plus currency, source_completed_at, source_reference, replayed: false, transaction_status: active |
| Identical request, already recorded | 200 |
The original frozen facts, replayed: true, and the current transaction_status (active or voided) |
| Same reference, different sale facts | 409 |
PURCHASE_REFERENCE_CONFLICT, retry_strategy: no_retry |
| Same reference, still being processed | 409 |
PURCHASE_REFERENCE_IN_FLIGHT, retry_strategy: backoff, Retry-After: 2 |
| Temporary failure while recording | 503 |
PURCHASE_RETRYABLE_FAILURE, retry_strategy: backoff. Retry the identical request. |
| Sale record's app key is missing | 503 |
PURCHASE_CLAIM_KEY_UNAVAILABLE, retry_strategy: contact_support. Nothing is awarded or changed; the operator restores the key from backup. |
| Original purchase corrected or erased | 410 |
PURCHASE_REFERENCE_CONSUMED, retry_strategy: no_retry. The sale stays settled and no member facts are returned. |
| Currency differs from the card | 422 |
PURCHASE_CURRENCY_MISMATCH, and no sale is recorded |
| Source time outside the window | 422 |
SOURCE_COMPLETED_AT_OUT_OF_RANGE, and no sale is recorded. An identical retry of an already recorded sale is unaffected. |
A replay answers from the stored record, so it survives edits to the card, a deactivated member, a deleted staff member, and a replaced Integration API key. Without source_reference the endpoint keeps its original behavior: every call awards points again.
Redeem a reward (deduct points)
POST /api/agent/v1/partner/transactions/redeem
Scope: write:rewards
{
"card_id": "uuid-of-loyalty-card",
"reward_id": "uuid-of-reward",
"member_identifier": "[email protected]",
"staff_id": "uuid-of-staff-member"
}
The staff_id is optional. Without it, the platform attributes the redemption to "System". This enables headless integrations (Shopify, WooCommerce) that do not have staff accounts.
Successful redemption responses include transaction_id, points_deducted, member_balance, new_balance, reward_id, reward, card_id, and member_id.
Deduct points (custom deduction)
POST /api/agent/v1/partner/transactions/deduct
Scope: write:transactions
{
"card_id": "uuid-of-loyalty-card",
"member_identifier": "[email protected]",
"points": 500,
"note": "Gift card redemption - $50 Amazon card",
"reference": "giftcard:order:12345",
"staff_id": "uuid-of-staff-member"
}
Deducts a custom number of points from a member's card balance. The deduction consumes points in FIFO order (oldest first). Deductions require a note for the audit trail.
Use this for off-platform rewards like gift cards or external services, without needing a reward object.
The reference field is optional. It links the deduction to an external system for reconciliation.
The staff_id is optional. If you omit it, the platform records the transaction as "Partner".
A deduction fails with INSUFFICIENT_POINTS if the member does not have enough points. The tier system ignores deductions.
Successful deduction responses include transaction_id, points_deducted, member_balance, new_balance, card_id, and member_id.
Members
Member endpoints are read for partner keys, no writes. The agent API cannot create or modify members. They self-register through the platform.
List and detail return only members who have interacted with your business: earned points, collected a stamp, redeemed a voucher, or used a pass. A member who joined a card but has no interaction yet answers not-found, indistinguishable from an unknown id. The transaction endpoints accept that member's identifier regardless. See First-time customers.
List members
GET /api/agent/v1/partner/members
Scope: read
Get member
GET /api/agent/v1/partner/members/{id}
Scope: read
Get member balance
GET /api/agent/v1/partner/members/{id}/balance/{cardId}
Scope: read
Returns the member's point balance for a specific loyalty card.
Member list/detail responses expose the hardened public payload, nothing more:
id, unique_identifier, name, email, locale, currency, time_zone, last_login_at, created_at, updated_at, avatar, is_anonymous.
Achievements
A read-only view of the partner's achievements program. Program settings change in the partner dashboard, not through the API. Both endpoints require the achievements feature (achievements_permission, from the plan or the per-partner override); without it they answer 403.
Get the program
GET /api/agent/v1/partner/achievements
Scope: read
Returns the program (status of active, paused, or inactive, whether a history import is running, activated_at, week_starts_on, and the catalog version), the configured rules (per achievement: key, name, group, threshold, enabled, and the reward's public envelope of type, cap, and window, never the linked product), and aggregate insights while the program is active: members with an achievement, awards in the last 30 days, weekly-run and near-milestone counts, and reward usage. Insights are aggregates only, never member listings.
Get a member's achievements
GET /api/agent/v1/partner/members/{id}/achievements
Scope: read
Returns one member's progress at this business (loyalty days, current and best weekly run, the last qualifying week, distinct loyalty options used, first and last activity) and their paginated earned achievements, each with key, name, group, origin (live, or backfill for an achievement recognized by the history import), earned date, and a safe reward state (none, pending, delivered, or failed). Only members with a relationship at this business resolve; anyone else answers not-found. Voided achievements never appear.
Stamp cards
Stamp cards work like digital punch cards. Members collect stamps and earn a reward when the card is complete.
List stamp cards
GET /api/agent/v1/partner/stamp-cards
Scope: read or write:stamps
Get stamp card
GET /api/agent/v1/partner/stamp-cards/{id}
Scope: read or write:stamps
Create stamp card
POST /api/agent/v1/partner/stamp-cards
Scope: write:stamps
Update stamp card
PUT /api/agent/v1/partner/stamp-cards/{id}
Scope: write:stamps
Delete stamp card
DELETE /api/agent/v1/partner/stamp-cards/{id}
Scope: write:stamps
Add stamps
POST /api/agent/v1/partner/stamp-cards/{id}/stamps
Scope: write:stamps
Award stamps to a member on a specific stamp card.
A single call adds up to one full card (stamps_required) when the card has no per-transaction cap, or up to the configured cap otherwise. The absolute ceiling is 100.
Redeem stamp reward
POST /api/agent/v1/partner/stamp-cards/{id}/redeem
Scope: write:stamps
Redeem the stamp card reward when a member has collected all required stamps.
Use canonical create/update fields: stamps_expire_days and requires_physical_claim.
Vouchers
Vouchers provide instant-value discounts through promotional codes.
List vouchers
GET /api/agent/v1/partner/vouchers
Scope: read or write:vouchers
Get voucher
GET /api/agent/v1/partner/vouchers/{id}
Scope: read or write:vouchers
Create voucher
POST /api/agent/v1/partner/vouchers
Scope: write:vouchers
Update voucher
PUT /api/agent/v1/partner/vouchers/{id}
Scope: write:vouchers
Delete voucher
DELETE /api/agent/v1/partner/vouchers/{id}
Scope: write:vouchers
Validate voucher code
POST /api/agent/v1/partner/vouchers/validate
Scope: write:vouchers
Check if a voucher code is valid and redeemable without redeeming it.
Use canonical voucher fields: type, value, valid_from, valid_until, max_uses_total, and max_uses_per_member.
code is optional on create. The platform generates one when you omit it.
Validate request body:
codemember_identifierclub_idorder_amount(optional, minor units)
Validate response fields:
validvoucher_idcodenametypevaluecurrencydiscount_amountcappedoriginal_amountfinal_amounttimes_usedremaining_usesvalid_until
Redeem voucher
POST /api/agent/v1/partner/vouchers/{id}/redeem
Scope: write:vouchers
Redeem request body:
member_identifierorder_amount(optional, minor units)order_reference(optional)
Redeem response fields:
voucher_idcodetypemember_iddiscount_amountpoints_awardedremaining_usesredemption_id
Prepaid passes
Prepaid products can hold counted visits, time-limited unlimited visits, or money for the issuing business. Templates are prepaid-passes; sold instances are member-passes. The calls below default to visit format 1. For money, use format 2.
List pass templates
GET /api/agent/v1/partner/prepaid-passes
Scope: read or write:passes
Get pass template
GET /api/agent/v1/partner/prepaid-passes/{id}
Scope: read or write:passes
Create pass template
POST /api/agent/v1/partner/prepaid-passes
Scope: write:passes
Set uses_total for a counted pass, or omit it and set validity_days (1 to 366) for an unlimited pass.
Update pass template
PUT /api/agent/v1/partner/prepaid-passes/{id}
Scope: write:passes
Delete pass template
DELETE /api/agent/v1/partner/prepaid-passes/{id}
Scope: write:passes
Soft-deletes the template so future sales stop. Passes already sold keep working.
Sell a pass
POST /api/agent/v1/partner/prepaid-passes/{id}/sell
Scope: write:passes
Issues a sold instance to a member. Body: member_identifier (required), optional price_paid (cents; defaults to the template price), optional staff_id (a staff member in the pass's club). With no staff attached, the ledger entry records source: agent_api plus the Integration API key id.
List a member's passes
GET /api/agent/v1/partner/members/{id}/passes
Scope: read or write:passes
Returns the member's sold passes in your clubs. Use it to find a member-pass id before scanning or undoing.
Get a sold pass
GET /api/agent/v1/partner/member-passes/{memberPassId}
Scope: read or write:passes
Returns the sold pass with its ledger history.
Scan a visit
POST /api/agent/v1/partner/member-passes/{memberPassId}/scan
Scope: write:passes
Deducts one or more visits. Body: optional quantity (default 1), optional staff_id. Counted passes lose quantity visits; unlimited passes record the visit only.
Undo the last visit
POST /api/agent/v1/partner/member-passes/{memberPassId}/undo
Scope: write:passes
Writes an offsetting ledger entry and restores the balance. Fails when there is no visit to undo.
Monetary passes
Add ?pass_format=2 to pass lists and detail calls to receive typed records. Without it, lists contain only visit passes and an owned monetary detail returns 409 MONETARY_PASS_FORMAT_REQUIRED. For an issued pass, format 2 includes balance_type, is_unlimited, and a money object with integer initial_minor, remaining_minor, reserved_refund_minor, and available_minor amounts plus currency. A null visit count never identifies an unlimited monetary pass. Invalid or empty formats return 422.
Monetary product writes and actions require both literal scopes, write:passes and write:pass-money. The admin scope alone does not suffice. Review the issuing Business details and fixed translated terms in the business dashboard, then enable monetary sales. Terms and retention periods are not editable. A monetary template uses balance_type: "money", an integer face_value_minor, and an equal price in the same currency. Follow the generated request schema; visit counts and validity must be null.
Record an external sale through the same /prepaid-passes/{id}/sell?pass_format=2 route. Supply the member identifier, payment_received: true, amount_minor, currency, terms_version, external_reference, and external_source. Record the payment at the counter or in the external payment system first. Reward Loyalty does not charge the member.
To spend €37.50 from a EUR pass:
POST /api/agent/v1/partner/member-passes/{memberPassId}/spend?pass_format=2
X-Agent-Key: rl_agent_...
Idempotency-Key: 6db65634-d279-4904-91e3-3fe56d0a825a
Content-Type: application/json
{
"amount_minor": 3750,
"currency": "EUR",
"external_reference": "COUNTER-2026-0042",
"external_source": "pos_receipt"
}
Amounts must be JSON integers in the currency's minor unit. A numeric string or a decimal is invalid. Keep the exact body and operation key after a timeout. A retry returns the recorded result after checking the current key and authority again. Reusing that key with a different body returns 409 IDEMPOTENCY_CONFLICT. A scoped 410 RECORD_REMOVED means an earlier consumed result is no longer available; never replace its key to repeat the financial action. Privacy removal can scrub a consumed result while preserving its financial history. The application does not age out monetary records or make their operation keys reusable after a retention period.
Reverse a complete spend with POST /partner/member-passes/{memberPassId}/transactions/{transactionId}/reverse?pass_format=2, a fresh Idempotency-Key, and reason_code set to entry_error or purchase_cancelled. The original spend can be reversed once. Visit /scan and /undo actions cannot change money balances.
Refund preparation, confirmation, cancellation, and financial-record corrections remain Partner/admin web actions. There are no payment-confirming Agent tools. See staff use and business oversight.
Tiers
Membership tiers define VIP levels with multipliers and benefits.
List tiers
GET /api/agent/v1/partner/tiers
Scope: read or write:tiers
Get tier
GET /api/agent/v1/partner/tiers/{id}
Scope: read or write:tiers
Create tier
POST /api/agent/v1/partner/tiers
Scope: write:tiers
Use the canonical tier threshold field: points_threshold.
Update tier
PUT /api/agent/v1/partner/tiers/{id}
Scope: write:tiers
Delete tier
DELETE /api/agent/v1/partner/tiers/{id}
Scope: write:tiers
Staff
Manage staff members who process transactions at partner locations.
List staff
GET /api/agent/v1/partner/staff
Scope: read or write:staff
Get staff member
GET /api/agent/v1/partner/staff/{id}
Scope: read or write:staff
Create staff member
POST /api/agent/v1/partner/staff
Scope: write:staff
Use the canonical staff assignment field: club_id.
Staff list/detail responses expose:
id, club_id, club_name, name, email, locale, time_zone, number_of_times_logged_in, last_login_at, created_at, updated_at, avatar.
Update staff member
PUT /api/agent/v1/partner/staff/{id}
Scope: write:staff
Delete staff member
DELETE /api/agent/v1/partner/staff/{id}
Scope: write:staff
Apple Wallet settings
| Method | Endpoint | Scope |
|---|---|---|
GET |
/partner/apple-wallet/{type}/{id} |
read or write:wallet |
PUT |
/partner/apple-wallet/{type}/{id} |
write:wallet |
Use loyalty, stamp, offer, or prepaid for {type} and an owned item UUID for {id}. PUT accepts enabled and optional appearance settings; repeated identical requests return meta.result: "unchanged". These endpoints do not return signed member passes or signing credentials. See the Apple Wallet API for the fields, availability checks, and errors.
Related topics
- Scopes & Permissions: Which scopes are required for each endpoint
- Error Handling: Understanding error responses
- Authentication: Key format and security model