Download OpenAPI specification:
Brand-scoped API for partners building CRM, sync, and loyalty integrations.
Use this reference when you need to keep myne in sync with your own systems—CRM, website, middleware, or POS extensions—without waiting for a packaged connector.
Create credentials in myne at API Credentials (brand admins).
Then pick how you will call Connect: HTTP Basic (this brand, no token) or OAuth (Bearer token for this brand, or another brand that Allows the client).
Brand hierarchy: Every resource path is scoped to one Brand. Some brands sit in a Head office Organisation (Head office group). Discover that on Get credential scope and brand identity (GET /v1/me) or Get brand bootstrap configuration (GET …/brand): read response.brand.organisation (or the brand object in the Brand response). It names role, head_office_brand, and sibling_brands. Solo brands return organisation: null. Paths do not become org-wide—use those ids only as context (for example Collect Across Venues stamp labels).
Terms in bold, or linked to the glossary, are defined at the bottom of this page (for example Surface, External source, API Credentials).
Base URL: https://connect.myne.network (production) or https://connect.dev.myne.network (development).
Authentication: HTTP Basic or Bearer (POST /v1/token).
Create a Client ID and secret in myne at API Credentials while signed in as a brand admin. The Client Secret is shown once when you create or rotate.
Send Authorization: Basic … or Authorization: Bearer … on resource calls to connect.myne.network /v1. The HTTP Basic and OAuth sections walk through each method.
Do not rely on HTTP clients following redirects for authenticated calls — many strip Authorization on cross-origin redirects. Connect v1 routes respond in place (no auth redirect).
Response shape: Successful resource calls return JSON with message (status text) and response (payload). Resource errors return { "message": "…" } with an appropriate HTTP status. POST /v1/token success is { access_token, token_type, expires_in, brand_id } (plus refresh_token for authorization-code); token errors are { error, error_description }.
Field naming: All request and response JSON fields, and query parameters, use snake_case (for example brand_id, amount_in_cents, has_more).
Ready-made files for tooling and import:
connect.myne.networkImport the collection and environment into Postman (or use Import → Link with these URLs). Set client_id and client_secret from API Credentials. Run Get credential scope (Basic GET /v1/me) or Get an access token (this brand) then Get credential scope with Bearer. For other brands, authorize in the browser, then Get an access token (authorization code) and Refresh an access token. Authorize cannot run inside Postman.
Expand a term for its definition. How-to for a call lives on that operation (and its section)—not here.
The business using myne—your organisation in the product, and the tenant for every Connect route.
Call GET /v1/me first to learn your brand_id, then pass it in the path on other routes. API Credentials for the creating brand are scoped to that brand only. Other brands must Allow the same Client ID at /oauth/authorize.
GET /v1/me and GET …/brand return partner-safe brand fields. When the brand is in a Head office group, see Organisation (Head office group) on brand.organisation. Paths stay scoped to the authenticated brand — org fields are context only.
A Head office group of brands: one Head office brand and one or more venue brands that share customers and can run cross-venue offers.
Where to see it: After auth, call GET /v1/me (read response.brand.organisation) or GET /v1/brands/{brand_id}/brand (same object on the brand in the response array). Solo brands return organisation: null.
Fields: - id / name — the organisation - role — venue or head_office for the brand you authenticated as - head_office_brand — parent Head office (id + name; same as this brand when role is head_office) - sibling_brands — peer venue brands (id + name; excludes this brand and the Head office)
Resource paths do not become organisation-scoped. Use these ids to understand relationships (for example Collect Across Venues stamp labels on evaluate venue_progress). Head office Collect Across Venues that target a child brand can appear on that child’s promotion catalog and redeem path for that type only.
A specific venue or site customers visit—how you split offers, links, and reporting by place.
Use location_id to scope customer browse, sync payloads, and promotion lists to one venue.
Someone you recognise with a full profile—usually a name and contact details you can message or segment.
Customer IDs come from browse, sync, create/upsert, or your POS. Most loyalty and promotion calls need a customer id (or an external id you can resolve).
GET customer (by id or by external id) returns linked system ids in extended[], not a single top-level source/external_id. The profile includes pass id, Brand Points, and myne cashback. Visit totals sit on stats.
Spend or activity you can see before you have a full profile—or alongside one—when someone has not fully signed up.
Customer browse returns visitors only when you set include_visitors=true. Otherwise results are known customers.
A B2B account (company or organisation) linked to contacts in myne.
This is not the same as a location. A business may span multiple venues; a location is one physical site.
How well you know someone on a profile:
Filter customer browse with the connection query parameter. This is about relationship state on the profile—not plan usage metering named “connections”.
A named audience of customers in myne. Paths use filter-groups; the partner-facing name is always customer group.
Membership can be maintained by dynamic refresh, scheduled refresh, import replace, or automation-driven changes.
Live offers also have a Claimed - {offer name} group. Treat it as a read segment: membership is added when a customer claims the offer on the join page. Connect can list the group and its members; it cannot add members or assign a claim.
Connect lists which customer groups exist, which customers are in one group, and which customer groups a single customer belongs to. Scope browse or search with customer_group_id.
A named audience of businesses (B2B accounts) in myne—the account-side counterpart to a Customer group.
Membership can use the same patterns as customer groups. Business groups are configured in the product; Connect Brands does not currently expose dedicated business-group list routes.
How often someone tends to visit—New, Frequent, or Infrequent.
Returned on customer rows and available as a browse filter so you can tailor welcome paths vs loyalty depth.
A simple read on whether someone is active with your brand or drifting—useful for quick prioritisation on a profile.
Returned on customer profiles when myne has enough signal to classify them.
Timeline beyond the core customer or business fields: POS records, marketing activities, transactions, custom properties, and similar.
On GET customer, extended[] is the identity list (source + external_id per connected system). The /customers/{id}/extended route is the activity timeline, not that identity list.
Stored value on a partner loyalty programme (for example Wrapped/Giftit or myne Cashback).
Credit endpoints read balance, ledger history, and add value. A customer may have no linked credit yet—in that case balance reads as zero and there is nothing to top up until credit is linked.
GET customer cashback is the internal cashback ledger for the brand, not a live Wrapped or Giftit credit GET. Use the credit endpoints when you need that partner programme's current balance.
Brand Points balance and earn/redeem history when Brand Points is enabled for the brand.
Points endpoints mirror credit: balance, ledger, and top-up. Top-ups always need a reason for the staff ledger.
Apple Wallet or Google Wallet identifiers tied to a customer's credit or rewards pass.
GET customer returns pass_id (the loyalty pass code) with points and cashback. Wallet serials are also returned alongside balance on the credit endpoint.
The main write path for pushing contacts, businesses, links, and extended metadata into myne.
Send up to 50 typed items per request. Prefer sync for bulk imports; use single customer upsert for one-off registrations. See external_id and external_data.
external_id is your identifier for a record (for example a CRM id or website user id). external_data is any extra attributes you choose to store on the core row.
Lookups and promotion evaluate/apply resolve that same external_id under your External source. GET customer lists every known source and id in extended[] rather than a single top-level pair. For extra partner metadata, also write extended rows under your source.
The integration key (source) that owns a customer or business external_id and extended fields.
Each API Credentials client has one source, derived from the app label in Integrations. GET …/me returns it as source.
Do not invent a source, and do not send source on write requests. Writes always stamp your credential’s source. Lookups default to your source.
An embedded myne CRM panel inside your own app.
Mint a short-lived Bearer token from Connect, then load the widget for a specific customer or business.
Client ID and Client Secret for partner server-to-server access (formerly Custom API Basic).
Create them in myne at API Credentials while signed in as a brand admin. Copy the Client Secret when it is shown—it cannot be retrieved later.
Two uses, one Client ID: HTTP Basic or POST /v1/token with grant_type=client_credentials only reaches the creating brand. Other myne brands Allow the same Client ID in the browser (https://app.myne.network/oauth/authorize); that returns a code you exchange for tokens for that brand.
Resource calls accept Authorization: Basic … or Authorization: Bearer …. Access tokens last one hour; issuing another token does not cut earlier ones short. Authorization-code installs also return a refresh token. A brand can have more than one live refresh token for the same Client ID (for example two partner entities). Refreshing one does not invalidate the others. Revoke or rotate the secret stops them all.
An HTTPS destination myne POSTs when a subscribed brand event happens.
Create subscriptions with Connect (/v1/brands/{brand_id}/webhooks) or in Settings → Webhooks → Outgoing. The signing secret is returned only on create and rotate. Partners only see destinations their Custom API app created. Poll collection GETs with updated_since when you prefer pull over push.
Topics include customer create/update, transactions, group membership, venue check-in, connection-form submit, and promotion redeem. The customer.created and customer.updated payloads include connection_type — connected when the customer has joined loyalty, known when myne has the customer but they have not joined, and unknown for an anonymous website visitor. It matches the connection_type on the customer list and get-customer endpoints.
HMAC-SHA256 of {unix_timestamp}.{rawBody} sent as X-Myne-Signature: t=<unix>,v1=<hex>.
Also send X-Myne-Topic and X-Myne-Delivery-Id. Verify with crypto.timingSafeEqual. Treat delivery_id as an idempotency key so retries are safe.
Optional ISO-8601 timestamp on collection list and search routes. When set, only rows whose watermark is on or after that instant (UTC, inclusive) are returned. Omit the parameter for today's full list.
Most collections use updated_at. Credit and points ledgers use created_at. Invalid timestamps return 400.
An offer set up in myne. It states what the customer gets, when it can be used, and where it can appear.
Each promotion can also say whether more than one offer may sit on the same order. Connect list, get, search, and evaluate responses include image_url (the offer image, or null).
Collect Across Venues (venue_unlock) is a passport offer: the same action at each selected site or Head office child brand unlocks a reward. On register/ordering evaluate (lsk/lso), progress is in venue_progress; incomplete passports use VENUE_UNLOCK_NOT_ELIGIBLE. Head office offers of this type that target a child brand are visible on that child’s Connect catalog and redeem path (this type only).
Some offers use Buy X Get Y rules. Evaluate promotions applies those on the open order.
A promotion mechanic: the customer must buy quantity X of matching items, then quantity Y of matching items is discounted (often free).
When buy (X) and get (Y) use the same products or categories, the paid X units are reserved first. Y does not cancel X. Example: Buy 1 Get 1 Free needs two matching units — pay for the first, discount the second. Buy 3 Get 1 Free needs four matching units.
When buy and get are different products or categories, the customer still needs enough X on the order, and the discount applies only to matching Y lines.
If allow-repeat is on, each complete set of paid X plus discounted Y can apply again on the same order. Rolling Buy X Get Y can bank the buy side from earlier purchases in the period; then the current order only needs matching Y.
Where the sale happens. Examples: a join page, an ordering app, a register, or an online store.
Send surface to match that place: lsk for register, lso for ordering. Use the same value on evaluate, apply, and complete. Those open-order calls always need surface — do not send integration_type there.
On browse, customer list, and search you may send integration_type instead as a coarse list filter when you only need which promotions can show (ordering, listing, or ecommerce) from customer Show vs available groups, without knowing which app will evaluate the sale. Some availability values are marketing only and are not used on evaluate, apply, or complete.
Two audience rules on a promotion:
A customer can see an offer and still be unable to use it until they meet the available rules.
A separate redeem rule is join-page claim. When an offer requires it, evaluate returns non_redeemable_cause.code CLAIM_REQUIRED until the customer has claimed on the join page. That is not assigned by this API.
The product id for a line on the open order (cart.items[].metadata.pos_id). It comes from the catalogue at the place you are selling. Example: the register item id at a POS.
We need this on each line so we can match the order to the promotion rules. It is not your Custom API customer or product id.
Whether more than one promotion can sit on the same open order at once.
Each promotion has its own allow-stacking setting in myne. If stacking is allowed, lock each reward with the same order id. One complete call then records all of them.
Your id for the open order. Use a POS order id, or generate a unique id. In the API this field is named basket_id.
We need the same id on evaluate, apply, and complete so we treat them as one order. If paid sales already go to myne, write that same id onto the order with the prefix MYNE so myne can match the payment. If you send Complete promotions after the customer pays, you do not write that mark.
A paid purchase stored in myne after the customer has paid.
Before payment, the sale is an open order. Recording a promotion redemption does not create this purchase. The purchase is created when the POS or ordering system sends the sale.
A CRM timeline entry on a contact—manual notes and calls you log via Activities, plus other rows that appear in the staff activity feed.
This is distinct from Customer events (POST …/events), which are integration-sourced timeline posts stamped with your External source.
A staff follow-up work item in myne—title, status, due date, assignees, and linked contacts or businesses.
Connect can list and search tasks. Creating tasks via Connect is not available yet.
Start here. Create API Credentials in myne, then confirm which brand they reach before you call other routes.
You have one Client ID. Use it two ways:
Call Get credential scope and brand identity first. You do not pass brand_id on that path — the credential or token selects the brand. Use the returned brand_id on every other /v1/brands/{brand_id}/… route.
On that same response, read brand.organisation when present to see Head office / venue relationships (role, head_office_brand, sibling_brands). Details: Brand and Organisation (Head office group).
Send Authorization on every request to the final connect.myne.network /v1 URL. Do not rely on following redirects for auth.
Rotate secret keeps the same Client ID and invalidates outstanding access tokens. Partners do not create Auth0 apps.
Confirm your API Credentials (HTTP Basic or Bearer from POST /v1/token) and discover brand_id, partner-safe brand display settings, External source, and—when this brand is in a Head office group—brand.organisation (role, Head office parent, sibling venues).
Call this first when wiring a new integration. You do not pass brand_id on this path — the credential or access token selects the brand (creating brand for Basic / client_credentials; granted brand for authorization-code tokens).
Read response.brand.organisation to understand multi-brand relationships. Paths on other routes stay scoped to response.brand_id — see Brand and Organisation (Head office group) in the glossary.
Send Authorization: Basic or Authorization: Bearer on every request to the connect.myne.network /v1 URL. Do not rely on following redirects for auth.
See glossary: Brand, Organisation (Head office group), API Credentials, External source.
{- "message": "Authenticated successfully",
- "response": {
- "brand_id": 42,
- "client_id": "myne_api_7f3a2b1c",
- "credential_kind": "brand_basic_auth",
- "label": "CRM sync",
- "source": "crm-sync",
- "brand": {
- "id": 42,
- "name": "Harbour Cafe Co",
- "description": "Neighbourhood cafe group",
- "industry": "hospitality",
- "background_url": null,
- "primary_color": "#1A1A1A",
- "contrast_color": "#FFFFFF",
- "text_color": "#1A1A1A",
- "button_color": "#C45C26",
- "primary_heading": "Welcome back",
- "subheading": "Earn rewards every visit",
- "call_to_action": "Join now",
- "legal_name": "Harbour Cafe Co Pty Ltd",
- "contact_email": "hello@harbour.example.com",
- "business_address": "1 Harbour St, Sydney NSW 2000",
- "governing_state": "NSW",
- "organisation": {
- "id": 42,
- "name": "Harbour Group",
- "role": "venue",
- "head_office_brand": {
- "id": 100,
- "name": "Harbour Head Office"
}, - "sibling_brands": [
- {
- "id": 101,
- "name": "Harbour Bondi"
}, - {
- "id": 102,
- "name": "Harbour Newtown"
}
]
}
}
}
}Send your Client ID and secret on each request. There is no token step.
This only reaches the creating brand — the brand where you added the client in myne.
Open API Credentials as a brand admin. Add a client. Copy Client ID (myne_api_…) and Client Secret (shown once).
Call GET https://connect.myne.network/v1/me with:
Authorization: Basic base64(client_id:client_secret)
Read response.brand_id and response.source. Use that brand_id on every other path. If response.brand.organisation is present, note role, head_office_brand, and sibling_brands for multi-venue context.
Path brand_id must match the credential. Resource errors look like { "message": "…" }.
Mint a Bearer token, then send Authorization: Bearer {access_token} on resource calls. Access tokens last one hour. Issuing another access token does not invalidate earlier ones; they stay valid until that expiry, or until you rotate the secret or Revoke the grant.
Call POST https://connect.myne.network/v1/token (this Connect host only). JSON body is preferred. You can also send client_id and client_secret as HTTP Basic on that call, or as application/x-www-form-urlencoded.
Token errors use error / error_description (not the resource { message } envelope).
Use this when you only need the creating brand. There is no refresh token.
{ "grant_type": "client_credentials", "client_id": "myne_api_…", "client_secret": "…" }
Then call Get credential scope and brand identity with Authorization: Bearer {access_token}.
Use this when another brand Allows your Client ID. Each Allow and code exchange issues its own refresh token. Refreshing one token rotates only that token; other refresh tokens for the same brand stay valid.
On the client in API Credentials, save https redirect URLs (one per line, exact match). http localhost is allowed on development only. An empty redirect list cannot authorize.
Send a brand admin to authorize in the browser:
https://app.myne.network/oauth/authorize?client_id=myne_api_…&redirect_uri=https://partner.example/oauth/callback&response_type=code&state=YOUR_STATE
Development: https://app.dev.myne.network/oauth/authorize with the same query. They sign in, pick that brand, and Allow. Deny returns error=access_denied to your redirect. Unknown redirects are not sent to an unregistered host. PKCE S256 is accepted (code_challenge and code_challenge_method=S256).
Exchange the code (same redirect_uri, exact match). A code cannot be reused.
{ "grant_type": "authorization_code", "client_id": "…", "client_secret": "…", "code": "…", "redirect_uri": "…" }
The response includes access_token, refresh_token, expires_in, and the granted brand_id.
Refresh before the hour is up. That refresh token rotates: use the new refresh_token; the one you just used fails. Other refresh tokens for the same brand stay valid. Earlier access tokens stay valid until they expire.
{ "grant_type": "refresh_token", "client_id": "…", "client_secret": "…", "refresh_token": "…" }
Then call GET /v1/me or /v1/brands/{brand_id}/… with Bearer. Path brand_id must match the token.
Exchange API Credentials for a Connect access token. This route is on the Connect host only (POST /v1/token). Do not send a gateway JWT.
JSON body is preferred. client_id and client_secret may also be sent as HTTP Basic on this call, or as application/x-www-form-urlencoded.
grant_type=client_credentials — creating brand only. No refresh token.grant_type=authorization_code — requires code and redirect_uri (exact match). Returns refresh_token. Code cannot be reused. A second Allow for the same brand adds another refresh token; existing ones stay valid.grant_type=refresh_token — requires refresh_token; issues a new access token and rotates only the refresh token you sent. Other refresh tokens for the same brand stay valid. Earlier access tokens stay valid until they expire.Access tokens last 3600 seconds. Issuing another access token does not invalidate earlier ones. Use Authorization: Bearer {access_token} on resource routes. brand_id in the token body is the creating brand (client credentials) or the granted brand (authorization code / refresh).
Authorize (browser, staff admin login): https://app.myne.network/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&state={state}. PKCE S256 is accepted when code_challenge was sent on authorize.
See the OAuth section for the this-brand and other-brand walkthroughs.
| grant_type required | string Enum: "client_credentials" "authorization_code" "refresh_token" |
| client_id required | string |
| client_secret required | string |
| code | string |
| redirect_uri | string |
| refresh_token | string |
| code_verifier | string |
{- "grant_type": "client_credentials",
- "client_id": "myne_api_abc",
- "client_secret": "your-client-secret"
}{- "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
- "token_type": "Bearer",
- "expires_in": 3600,
- "brand_id": 42
}Partner-safe brand display settings—name, logos, colours, and contact fields.
GET /v1/me) after auth — read response.brand.GET /v1/brands/{brand_id}/brand) for the same brand object without credential metadata.When this brand is in a Head office group, organisation is present:
role — venue or head_officehead_office_brand — parent Head office (id + name)sibling_brands — peer venue brands (id + name; excludes this brand and the Head office)Solo brands return organisation: null. Resource paths stay scoped to this brand’s id — org fields are context only (see Organisation (Head office group)). For Collect Across Venues on evaluate, stamp brand ids on venue_progress match these ids.
Load partner-safe brand settings—display name, logos, colours, headings, contact fields, and organisation hierarchy when present.
This is the same brand object as response.brand on GET …/me. Use when you need brand config without credential metadata.
When the brand is in a Head office group, inspect organisation.role, organisation.head_office_brand, and organisation.sibling_brands. Solo brands return organisation: null. Paths stay brand-scoped.
Returns one brand object in response as a single-item array. Returns 404 when the brand is not found.
See glossary: Brand, Organisation (Head office group).
| brand_id required | integer |
{- "message": "Brands fetched successfully",
- "response": [
- {
- "id": 42,
- "name": "Harbour Cafe Co",
- "description": "Neighbourhood cafe group",
- "industry": "hospitality",
- "background_url": null,
- "primary_color": "#1A1A1A",
- "contrast_color": "#FFFFFF",
- "text_color": "#1A1A1A",
- "button_color": "#C45C26",
- "primary_heading": "Welcome back",
- "subheading": "Earn rewards every visit",
- "call_to_action": "Join now",
- "legal_name": "Harbour Cafe Co Pty Ltd",
- "contact_email": "hello@harbour.example.com",
- "business_address": "1 Harbour St, Sydney NSW 2000",
- "governing_state": "NSW",
- "organisation": {
- "id": 42,
- "name": "Harbour Group",
- "role": "venue",
- "head_office_brand": {
- "id": 100,
- "name": "Harbour Head Office"
}, - "sibling_brands": [
- {
- "id": 101,
- "name": "Harbour Bondi"
}, - {
- "id": 102,
- "name": "Harbour Newtown"
}
]
}
}
]
}Return every app connected to the brand—the same integrations staff see in myne Settings.
Each row includes id, access_name, app_name, label, source, category, and whether it matches your API credentials (is_self).
Secrets, OAuth tokens, and raw integration payloads are never included.
See glossary: External source, API Credentials.
| brand_id required | integer |
{- "message": "Integrations fetched successfully",
- "response": [
- {
- "id": 42,
- "access_name": "monday",
- "app_name": "monday",
- "label": "Monday CRM",
- "source": "monday-crm",
- "state": "connected",
- "category": "crm",
- "is_self": false
}, - {
- "id": 99,
- "access_name": "myne_api_abc123",
- "app_name": "myne_api_abc123",
- "label": "Partner sync",
- "source": "partner-sync",
- "state": "connected",
- "category": "custom",
- "is_self": true
}
]
}Return every venue for the brand—the same rows staff see in myne.
Use location IDs in customer browse filters, sync payloads, and promotion scoping.
See glossary: Location, Brand.
| brand_id required | integer |
| updated_since | string <date-time> ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400. |
{- "message": "Locations fetched successfully",
- "response": [
- {
- "id": 3,
- "brand_id": 42,
- "name": "Harbour Cafe — Circular Quay",
- "phone": "+61291234567",
- "address": "1 Alfred St, Sydney NSW 2000",
- "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
- "valid": true,
- "new_check_in_enabled": true
}
]
}Load one venue by location_id for the brand.
Returns partner-safe fields such as name, phone, address, uuid, valid, and new_check_in_enabled.
Use after listing locations or when resolving a venue from sync payloads. Returns 404 when the ID is not in this brand.
See glossary: Location.
| brand_id required | integer |
| location_id required | integer |
{- "message": "Location fetched successfully",
- "response": {
- "id": 3,
- "brand_id": 42,
- "name": "Harbour Cafe — Circular Quay",
- "phone": "+61291234567",
- "address": "1 Alfred St, Sydney NSW 2000",
- "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
- "valid": true,
- "new_check_in_enabled": true
}
}Resolve a myne customer from your CRM or PMS external_id without scanning browse results.
When source is omitted, lookup uses your Custom API app name (the label shown in Integrations)—the same source stamped when you synced or upserted the contact. Pass source explicitly only when you need IDs synced under another integration.
Returns the customer with connected-system identities (extended[]), pass id, Brand Points, myne cashback, lifetime stats, and loyalty join status (connection_type — connected when the customer has joined loyalty, known when not, unknown for an anonymous website visitor), or 404 when no match exists for this brand. connection_type matches the customer list and the get-customer-by-id endpoint. Identities are not a single top-level source/external_id. Cashback here is the internal cashback ledger, not a live Wrapped or Giftit credit GET.
Evaluate/apply use this same external_id path when you omit customer_id, but they do not load identities or loyalty.
See glossary: Customer, external_id and external_data, External source, Credit, Points.
| brand_id required | integer |
| external_id required | string Your integration's identifier for the customer. |
| source | string Origin system for the external_id. When omitted, defaults to your Custom API app name (Integrations label). Read-only filter for cross-integration lookups. |
{- "message": "Customer fetched successfully",
- "response": {
- "customer": {
- "id": 104823,
- "brand_id": 42,
- "location_id": 3,
- "phone": "+61412345678",
- "email": "jordan.lee@example.com",
- "created_at": "2025-02-15T01:23:45.000Z",
- "updated_at": "2026-06-21T03:15:00.000Z",
- "accepts_marketing": true,
- "first_name": "Jordan",
- "last_name": "Lee",
- "archived_at": null,
- "frequency": "Frequent",
- "engagement": "engaged",
- "connection_type": "connected",
- "pass_id": "ABC123XYZ",
- "points": 120,
- "cashback": 18.5
}, - "stats": {
- "transaction_count": 17,
- "total_spent": 542.5,
- "last_transaction_date": "2026-06-21T03:15:00.000Z"
}, - "extended": [
- {
- "source": "lightspeed",
- "external_id": "ext-abc123",
- "json_data": {
- "pos_id": "LS-98765"
}, - "updated_at": "2026-06-21T03:15:00.000Z"
}, - {
- "source": "monday-crm",
- "external_id": "crm-001",
- "json_data": { },
- "updated_at": "2026-05-01T00:00:00.000Z"
}
]
}
}Merge JSON extended fields for a customer identified by external_id.
Use this when you want partner metadata or alternate source ids stored under your Custom API app without relying only on top-level email/phone/name. Data is stored under your app source only—do not send source in the body.
If no customer exists for that external_id yet, one is created. You may optionally pass email, phone, first_name, and last_name on create.
See glossary: Customer, Extended profile, External source, external_id and external_data.
| brand_id required | integer |
| external_id required | string Your integration's identifier for the customer. |
required | object JSON fields to merge into customer_extended for your API app source. |
string <email> Optional. Used when creating the customer if they do not exist yet. | |
| phone | string |
| first_name | string |
| last_name | string |
{- "external_id": "string",
- "data": { },
- "email": "user@example.com",
- "phone": "string",
- "first_name": "string",
- "last_name": "string"
}{- "message": "Customer extended data upserted successfully",
- "response": {
- "customer_id": 104823,
- "external_id": "crm-001",
- "source": "monday-crm",
- "created_customer": true
}
}Return the top-level contact-extended fields your app has declared for this brand under your External source.
This is the partner-declared catalog only (not staff-added extras). Values still live in customer_extended.data via extended upsert; declaring properties does not validate or strip undeclared keys on write.
See glossary: Customer, Extended profile, External source.
| brand_id required | integer Brand id from your credential scope. |
{- "message": "Customer extended properties fetched successfully",
- "response": {
- "source": "pocketpass",
- "properties": [
- {
- "key": "pocketpass_link",
- "type": "link",
- "label": "Pocketpass link"
}, - {
- "key": "pocketpass_installed",
- "type": "boolean",
- "label": "Pocketpass installed"
}
]
}
}Full-replace the top-level contact-extended fields your app stands behind for this brand.
Send properties as an array of { key, type, label?, description? } (max 50). Types: string, number, boolean, link, date. Keys must match ^[a-z][a-z0-9_]*$ (max 64) and the top-level JSON keys you write on extended upsert. Empty properties: [] clears your partner-declared catalog for this source.
Source is always your Custom API app—do not send source. This does not change how extended upsert merges JSON.
See glossary: Customer, Extended profile, External source.
| brand_id required | integer Brand id from your credential scope. |
required | Array of objects <= 50 items Full replace list for this brand + your External source. Empty array clears your partner-declared catalog. |
{- "properties": [
- {
- "key": "pocketpass_link",
- "type": "link",
- "label": "Pocketpass link",
- "description": "URL to the member portal"
}, - {
- "key": "pocketpass_installed",
- "type": "boolean",
- "label": "Pocketpass installed"
}
]
}{- "message": "Customer extended properties replaced successfully",
- "response": {
- "source": "pocketpass",
- "properties": [
- {
- "key": "pocketpass_link",
- "type": "link",
- "label": "Pocketpass link"
}, - {
- "key": "pocketpass_installed",
- "type": "boolean",
- "label": "Pocketpass installed"
}
]
}
}Create or update a customer from your website, CRM, or PMS using external_id.
Send top-level profile fields when you have them (email, phone, name). Source is always your Custom API app—do not send source.
Send connection_type: "connected" when the person is already a loyalty member in your app — myne marks them as joined (requires an email or phone on the request, or one already stored for the customer). Omitting it, or sending known / unknown, never changes their connection status.
That external_id is what you use later for promotion evaluate/apply and by-external-id lookups under your External source. For additional partner ids or opaque metadata, also write extended data (POST …/customers/by-external-id/extended or sync contact_extended).
Prefer this for single registrations; use Sync batch for bulk imports.
See glossary: Customer, external_id and external_data, Sync batch.
| brand_id required | integer |
| external_id required | string Your website or CRM identifier for the customer. |
string <email> | |
| phone | string |
| first_name | string |
| last_name | string |
| marketing_opt_in_email | boolean Marketing email opt-in stored on the customer profile. |
| connection_type | string Enum: "connected" "known" "unknown" Optional loyalty connection status. |
object Optional opaque attributes stored on the customer. |
{- "external_id": "string",
- "email": "user@example.com",
- "phone": "string",
- "first_name": "string",
- "last_name": "string",
- "marketing_opt_in_email": true,
- "connection_type": "connected",
- "traits": { }
}{- "message": "Customer upserted successfully",
- "response": {
- "customer_id": 104823,
- "external_id": "web-user-001",
- "source": "monday-crm",
- "created": true
}
}Search and page through customers for a brand. Use cursor from response.next_cursor for the next page.
Filter by location, Connection state, or Frequency. Set include_visitors=true to include unidentified Visitor records.
Pass updated_since (ISO-8601, compared in UTC) to poll only customers changed on or after that timestamp.
Typical first step before loading a single profile or extended timeline.
See glossary: Customer, Visitor, Connection state, Frequency, Engagement.
| brand_id required | integer ID of the brand (tenant) to browse customers for. |
| limit | integer [ 1 .. 100 ] Default: 15 Page size. Clamped to 1–100. |
| cursor | string Opaque pagination cursor returned as |
| query | string Free-text search across name, email, phone, and wallet/credit identifiers. |
| include_visitors | boolean Default: false Accepts |
| sort_by | string Default: "total_spent" Enum: "last_seen" "transaction_count" "total_spent" "tenure" "frequency" "engagement" Sort key for ranking customers. |
| sort_direction | string Default: "asc" Enum: "asc" "desc" Sort direction. |
| locations | string Comma-separated business/location IDs to restrict the browse to (e.g. |
| connection | string Enum: "connected" "known" "unknown" Filter by connection state. |
| frequency | string Enum: "frequent" "infrequent" "new" Filter by brand frequency bucket. |
| engagement | string Filter by engagement classification when available. |
| customer_group_id | integer Restrict browse to members of one customer group. |
| search_fields | string Comma-separated search field keys to limit free-text |
| updated_since | string <date-time> ISO-8601 timestamp. When set, only customers with updated_at on or after this instant are returned (compared in UTC). |
{- "message": "Customer records fetched successfully (known only)",
- "response": {
- "data": [
- {
- "id": 104823,
- "external_data": null,
- "first_name": "Jordan",
- "last_name": "Lee",
- "email": "jordan.lee@example.com",
- "phone": "+61412345678",
- "location_id": null,
- "brand_id": 42,
- "uuid": "c0b1f7a4-2e3d-4f5a-9b6c-7d8e9f0a1b2c",
- "predicted_name": null,
- "type": "customer",
- "transaction_count": 17,
- "total_spent": 542.5,
- "tenure": 412,
- "latest_transaction": "2026-06-21T03:15:00.000Z",
- "last_seen": "2026-06-21T03:15:00.000Z",
- "frequency": "Frequent",
- "engagement": "engaged",
- "connection_type": "connected"
}
], - "has_more": true,
- "next_cursor": "1|0|2026-06-21T03:15:00.000Z|104823",
- "total_count": 1287
}
}Append an event to the customer timeline (for example a form submission or in-app action).
Requires event_name. Optional: event_data, event_time, location_id, and external_id for idempotency.
Events are stamped with your External source from credentials. This is not the same as recording a CRM Activity (notes and calls).
See glossary: Activity, External source.
| brand_id required | integer |
| customer_id required | integer |
| event_name required | string Event label on the customer timeline (e.g. form_submitted). |
| event_time | string <date-time> When the event occurred (defaults to now). |
object Arbitrary JSON payload for the event. | |
| event_value | number |
| location_id | integer |
| external_id | string Optional partner event id for correlation. |
| is_visible_to_customer | boolean Default: false |
{- "event_name": "string",
- "event_time": "2019-08-24T14:15:22Z",
- "event_data": { },
- "event_value": 0,
- "location_id": 0,
- "external_id": "string",
- "is_visible_to_customer": false
}{- "message": "Customer event recorded successfully",
- "response": {
- "id": 9001,
- "event_name": "form_submitted",
- "event_time": "2026-07-10T08:00:00.000Z",
- "source": "monday-crm",
- "external_id": "evt-abc"
}
}Same as recording a customer event, but resolve the person with your website external_id instead of customer_id.
Pass external_id (contact) plus event_name. Use event_external_id for the event’s own id.
Lookup uses your credential External source.
See glossary: external_id and external_data, External source, Activity.
| external_id | string Contact external_id. Prefer the body field; query is accepted for convenience. |
| external_id required | string Your website or CRM identifier for the customer. |
| event_name required | string Event label on the customer timeline (e.g. form_submitted). |
| event_time | string <date-time> |
object | |
| event_value | number |
| location_id | integer |
| event_external_id | string Optional partner id for this event (not the contact external_id). |
| is_visible_to_customer | boolean Default: false |
{- "external_id": "string",
- "event_name": "string",
- "event_time": "2019-08-24T14:15:22Z",
- "event_data": { },
- "event_value": 0,
- "location_id": 0,
- "event_external_id": "string",
- "is_visible_to_customer": false
}{- "message": "Customer event recorded successfully",
- "response": {
- "id": 9001,
- "customer_id": 104823,
- "event_name": "form_submitted",
- "event_time": "2026-07-10T08:00:00.000Z",
- "source": "monday-crm",
- "external_id": "evt-abc"
}
}Search and page through customers using a JSON body with the same filters as GET …/customers (cursor, limit, query, locations, sort_by, sort_direction, connection, frequency, engagement, customer_group_id, search_fields, updated_since, include_visitors).
Prefer GET …/customers for simple query-string browse; use this when filters are easier to express in JSON.
See glossary: Customer, Visitor, Connection state.
| brand_id required | integer |
| cursor | string |
| limit | integer [ 1 .. 100 ] Default: 15 |
| query | string |
| include_visitors | boolean Default: false |
| sort_by | string Default: "total_spent" Enum: "last_seen" "transaction_count" "total_spent" "tenure" "frequency" "engagement" |
| sort_direction | string Default: "asc" Enum: "asc" "desc" |
Array of integers or string | |
| connection | string Enum: "connected" "known" "unknown" |
| frequency | string Enum: "frequent" "infrequent" "new" |
| engagement | string |
| customer_group_id | integer |
| search_fields | Array of strings |
| updated_since | string <date-time> |
{- "cursor": "string",
- "limit": 15,
- "query": "string",
- "include_visitors": false,
- "sort_by": "last_seen",
- "sort_direction": "asc",
- "locations": [
- 0
], - "connection": "connected",
- "frequency": "frequent",
- "engagement": "string",
- "customer_group_id": 0,
- "search_fields": [
- "string"
], - "updated_since": "2019-08-24T14:15:22Z"
}{- "message": "Customer records fetched successfully (known only)",
- "response": {
- "data": [
- {
- "id": 104823,
- "external_data": null,
- "first_name": "Jordan",
- "last_name": "Lee",
- "email": "jordan.lee@example.com",
- "phone": "+61412345678",
- "location_id": 3,
- "brand_id": 42,
- "uuid": "c0b1f7a4-2e3d-4f5a-9b6c-7d8e9f0a1b2c",
- "predicted_name": null,
- "type": "customer",
- "transaction_count": 17,
- "total_spent": 542.5,
- "tenure": 412,
- "latest_transaction": "2026-06-21T03:15:00.000Z",
- "last_seen": "2026-06-21T03:15:00.000Z",
- "frequency": "Frequent",
- "engagement": "engaged",
- "connection_type": "connected"
}
], - "has_more": true,
- "next_cursor": "1|0|2026-06-21T03:15:00.000Z|104823",
- "total_count": 1287
}
}Load one customer profile with connected-system identities (extended[]), pass id, Brand Points, myne cashback, lifetime transaction stats, and loyalty join status (connection_type).
connection_type is connected when the customer has joined loyalty, known when myne has the customer but they have not joined, and unknown for an anonymous website visitor. It matches the connection_type on the customer list.
Identities are listed in extended[], not as a single top-level source/external_id. Cashback is the internal cashback ledger, not a live Wrapped or Giftit credit GET. Visit totals sit on stats.
Use the customer_id from browse, sync batch, or your POS. Returns 404 when the ID is not in this brand.
See glossary: Customer, External source, Credit, Points.
| brand_id required | integer |
| customer_id required | integer |
{- "message": "Customer fetched successfully",
- "response": {
- "customer": {
- "id": 104823,
- "brand_id": 42,
- "location_id": 3,
- "phone": "+61412345678",
- "email": "jordan.lee@example.com",
- "created_at": "2025-02-15T01:23:45.000Z",
- "updated_at": "2026-06-21T03:15:00.000Z",
- "accepts_marketing": true,
- "first_name": "Jordan",
- "last_name": "Lee",
- "archived_at": null,
- "frequency": "Frequent",
- "engagement": "engaged",
- "connection_type": "connected",
- "pass_id": "ABC123XYZ",
- "points": 120,
- "cashback": 18.5
}, - "stats": {
- "transaction_count": 17,
- "total_spent": 542.5,
- "last_transaction_date": "2026-06-21T03:15:00.000Z"
}, - "extended": [
- {
- "source": "lightspeed",
- "external_id": "ext-abc123",
- "json_data": {
- "pos_id": "LS-98765"
}, - "updated_at": "2026-06-21T03:15:00.000Z"
}, - {
- "source": "monday-crm",
- "external_id": "crm-001",
- "json_data": { },
- "updated_at": "2026-05-01T00:00:00.000Z"
}
]
}
}Fetch a customer's timeline: external POS records, marketing activities, and transactions.
Paginate with limit and offset. Set payments=true to include payment line items on each transaction.
Use when you need history beyond the summary fields on the main customer endpoint.
See glossary: Extended profile, Customer.
| brand_id required | integer |
| customer_id required | integer |
| limit | integer [ 1 .. 100 ] Default: 25 Maximum timeline records to return per collection (activities, transactions). |
| offset | integer [ 0 .. 10000 ] Default: 0 Zero-based offset into the timeline. |
| payments | boolean When true, include payment detail objects inside each transaction record. Defaults to false. |
{- "message": "Customer extended data fetched successfully",
- "response": {
- "external_records": [ ],
- "activities": [
- {
- "type": "event",
- "source": "lightspeed",
- "created_at": "2026-06-21T03:15:00.000Z",
- "data": {
- "name": "checkin"
}
}
], - "transactions": [
- {
- "type": "transaction",
- "id": 5501,
- "amount": 42.5,
- "transaction_date": "2026-06-21T03:15:00.000Z",
- "payments": [ ]
}
], - "pagination": {
- "limit": 25,
- "offset": 0
}
}
}Customer groups and which customers belong to them. Membership may refresh dynamically, on a schedule, via import replace, or through automation-driven changes.
Return every active customer group for the brand (id and name), including Claimed - … groups for live offers.
Use customer group IDs when exporting membership or scoping customer browse (customer_group_id). Deleted customer groups are omitted. Membership may be maintained by dynamic refresh, scheduled refresh, import replace, or automation-driven changes—see glossary. Claimed-group membership is join-page claim, not an API write.
See glossary: Customer group.
| brand_id required | integer |
| updated_since | string <date-time> ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400. |
{- "message": "Filter groups fetched successfully",
- "response": [
- {
- "id": 12,
- "group_name": "VIP"
}, - {
- "id": 7,
- "group_name": "Recent visitors"
}
]
}Page through known customers who belong to one customer group, including Claimed - … groups (read-only membership from join-page claim).
Uses the same cursor and limit pagination as customer browse. Pass sort_by and sort_direction for ranking within the customer group.
See glossary: Customer group, Customer.
| brand_id required | integer |
| group_id required | integer |
| limit | integer [ 1 .. 100 ] Default: 15 |
| cursor | string Pagination cursor from response.next_cursor. |
| sort_by | string Default: "total_spent" Enum: "last_seen" "transaction_count" "total_spent" "tenure" "frequency" "engagement" |
| sort_direction | string Default: "asc" Enum: "asc" "desc" |
| updated_since | string <date-time> ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400. |
{- "message": "Customer records fetched successfully (known only)",
- "response": {
- "data": [
- {
- "id": 104823,
- "external_data": null,
- "first_name": "Jordan",
- "last_name": "Lee",
- "email": "jordan.lee@example.com",
- "phone": "+61412345678",
- "location_id": 3,
- "brand_id": 42,
- "uuid": "c0b1f7a4-2e3d-4f5a-9b6c-7d8e9f0a1b2c",
- "predicted_name": null,
- "type": "customer",
- "transaction_count": 17,
- "total_spent": 542.5,
- "tenure": 412,
- "latest_transaction": "2026-06-21T03:15:00.000Z",
- "last_seen": "2026-06-21T03:15:00.000Z",
- "frequency": "Frequent",
- "engagement": "engaged",
- "connection_type": "connected"
}
], - "has_more": true,
- "next_cursor": "1|0|2026-06-21T03:15:00.000Z|104823",
- "total_count": 1287
}
}Return the customer groups a customer currently belongs to.
Useful for checking offer eligibility, personalising messaging, or syncing customer-group membership to an external CRM.
See glossary: Customer group, Show vs available.
| brand_id required | integer |
| customer_id required | integer |
| updated_since | string <date-time> ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400. |
{- "message": "Successfully fetched customer filter groups",
- "response": [
- {
- "id": 12,
- "group_name": "VIP"
}, - {
- "id": 7,
- "group_name": "Recent visitors"
}
]
}Look up a customer's credit balance and Wallet pass identifiers (Apple Wallet and Google Wallet).
Balance is fetched live from the loyalty partner when credit is linked. Otherwise giftcard_code is null and balance is 0.
See glossary: Credit, Wallet pass.
| brand_id required | integer |
| customer_id required | integer |
{- "message": "ok",
- "response": {
- "giftcard_code": "ABC123XYZ",
- "balance": 75,
- "balance_fetched_at": "2026-06-21T03:15:00.000Z",
- "apple_wallet": [
- {
- "pass_type_identifier": "pass.com.myne.network.gift",
- "serial_number": "abc123def456"
}
], - "google_wallet": [
- {
- "issuer_id": "3388000000022345678",
- "class_suffix": "giftcard_v1",
- "object_suffix": "104823"
}
]
}
}Add stored value to a customer's linked credit.
Send amount and reason in the body. Reason is stored for audit and appears in the credit ledger. note is accepted as a legacy alias for reason for one release.
Supply idempotency_key when retrying so duplicate credits are not applied. The customer must already have linked credit.
See glossary: Credit.
| brand_id required | integer |
| customer_id required | integer |
| amount required | number Dollar amount of credit to add. |
| reason required | string Required reason for the credit transaction (shown in the staff ledger). |
| note | string Deprecated alias for reason. Prefer reason. |
| idempotency_key | string Optional client-supplied key for safe retries. |
{- "amount": 10,
- "reason": "Birthday bonus",
- "idempotency_key": "credit-topup-001"
}{- "message": "ok",
- "response": {
- "giftcard_code": "ABC123XYZ",
- "balance": 85,
- "loyalty_system": "wrapped",
- "customer_giftcard_id": 567,
- "adjusted_at": "2026-06-21T03:15:00.000Z"
}
}List credit ledger entries for a customer, newest first: amount, running balance_after, occurred_at, reason, and the transaction or payment that triggered the entry when applicable.
Use to reconcile top-ups added through POST …/credit or earned through purchases against myne's records.
See glossary: Credit.
| brand_id required | integer |
| customer_id required | integer |
| updated_since | string <date-time> ISO-8601 timestamp. When set, only rows with created_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400. |
{- "message": "ok",
- "response": {
- "balance": 75,
- "unit": "dollars",
- "entries": [
- {
- "id": 901,
- "amount": 10,
- "balance_after": 75,
- "occurred_at": "2026-06-21T03:15:00.000Z",
- "reason": "Staff credit — Birthday bonus",
- "unit": "dollars",
- "transaction_id": null,
- "payment_id": null
}
]
}
}Return a customer's Brand Points balance and the brand's points label (for example "Stars").
active is false and balance is 0 when Brand Points is not enabled for this brand.
See glossary: Points.
| brand_id required | integer |
| customer_id required | integer |
{- "message": "ok",
- "response": {
- "balance": 1250,
- "points_label": "Stars",
- "active": true
}
}Add Brand Points to a customer.
Send points_amount and reason in the body. Reason is stored for audit and appears in the points ledger. note is accepted as a legacy alias for reason for one release.
Supply idempotency_key when retrying so duplicate top-ups are not applied. Returns 400 when Brand Points is not enabled for this brand.
See glossary: Points.
| brand_id required | integer |
| customer_id required | integer |
| points_amount required | integer Number of points to add. |
| amount | integer Deprecated alias for points_amount. |
| reason required | string Required reason for the top-up (shown in the staff ledger). |
| note | string Deprecated alias for reason. Prefer reason. |
| idempotency_key | string Optional client-supplied key for safe retries. |
{- "points_amount": 100,
- "reason": "Compensation for delayed order"
}{- "message": "ok",
- "response": {
- "loyalty_system": "brand_points",
- "points_amount": 100,
- "balance_after": 1350,
- "adjusted_at": "2026-06-21T03:15:00.000Z"
}
}List points ledger entries for a customer, newest first: amount, running balance_after, occurred_at, reason, and the transaction or payment that triggered the entry when applicable.
Empty when Brand Points is not enabled for this brand.
See glossary: Points.
| brand_id required | integer |
| customer_id required | integer |
| updated_since | string <date-time> ISO-8601 timestamp. When set, only rows with created_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400. |
{- "message": "ok",
- "response": {
- "balance": 75,
- "unit": "points",
- "entries": [
- {
- "id": 901,
- "amount": 10,
- "balance_after": 75,
- "occurred_at": "2026-06-21T03:15:00.000Z",
- "reason": "Staff credit — Birthday bonus",
- "unit": "points",
- "transaction_id": null,
- "payment_id": null
}
]
}
}Product catalog browse, search, and category metadata—including surface-specific external IDs.
Page through the brand's sellable catalog.
Filter by location_id to include products scoped to one venue (unscoped products still match). Use free-text search to narrow by name or description.
Product rows include partner-safe fields. Use get-by-id or by-external-id when you need integration extended[] payloads—especially surface-specific POS ids before evaluate.
See glossary: Location, Order line pos_id, Surface.
| brand_id required | integer |
| page | integer >= 1 Default: 1 |
| limit | integer [ 1 .. 100 ] Default: 20 |
| search | string |
| location_id | integer Include products scoped to this location (unscoped products still match). |
| updated_since | string <date-time> ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400. |
{- "message": "Products fetched successfully",
- "response": {
- "products": [
- {
- "id": 501,
- "name": "Flat white",
- "description": "Double-shot flat white",
- "price": 4.5,
- "is_option": false,
- "sku": "FW-REG",
- "image_url": null,
- "created_at": "2025-11-01T00:00:00.000Z",
- "updated_at": "2026-06-01T00:00:00.000Z"
}
], - "total": 1,
- "limit": 20,
- "offset": 0,
- "page": 1
}
}Load one catalog item by myne product_id.
Returns core fields plus extended[] integration payloads (source and external_id per connected app).
Use after browse or when resolving promotion rule references. For register evaluate, pick the extended id that matches your Surface (see Order line pos_id).
See glossary: Order line pos_id, Surface, External source.
| brand_id required | integer |
| product_id required | integer |
{- "message": "Product fetched successfully",
- "response": {
- "id": 501,
- "name": "Flat white",
- "description": "Double-shot flat white",
- "price": 4.5,
- "is_option": false,
- "sku": "FW-REG",
- "image_url": null,
- "created_at": "2025-11-01T00:00:00.000Z",
- "updated_at": "2026-06-01T00:00:00.000Z",
- "extended": [
- {
- "source": "lightspeed-k",
- "external_id": "456",
- "json_data": { },
- "updated_at": "2026-06-01T00:00:00.000Z"
}
]
}
}Resolve a myne product from your POS or menu external_id without paging browse results.
When source is omitted, lookup uses your Custom API app name (Integrations label). Pass source explicitly to read IDs synced under another integration.
See glossary: external_id and external_data, External source.
| brand_id required | integer |
| external_id required | string |
| source | string Integration source key; defaults to your credential source. |
{- "message": "Product fetched successfully",
- "response": {
- "id": 501,
- "name": "Flat white",
- "description": "Double-shot flat white",
- "price": 4.5,
- "is_option": false,
- "sku": "FW-REG",
- "image_url": null,
- "created_at": "2025-11-01T00:00:00.000Z",
- "updated_at": "2026-06-01T00:00:00.000Z",
- "extended": [
- {
- "source": "integration_api",
- "external_id": "POS-456",
- "json_data": { },
- "updated_at": "2026-06-01T00:00:00.000Z"
}
]
}
}Search the catalog with a JSON body: optional search text, category_ids, source (integration filter), page, and limit.
Prefer GET /products for simple browse; use this when you need category or source scoping in one request.
| brand_id required | integer |
| search | string |
| category_ids | Array of integers |
| source | string Filter to products synced under one integration source. |
| page | integer >= 1 Default: 1 |
| limit | integer [ 1 .. 100 ] Default: 20 |
| updated_since | string <date-time> ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (UTC, inclusive). |
{- "search": "string",
- "category_ids": [
- 0
], - "source": "string",
- "page": 1,
- "limit": 20,
- "updated_since": "2019-08-24T14:15:22Z"
}{- "message": "Products fetched successfully",
- "response": {
- "products": [
- {
- "id": 501,
- "name": "Flat white",
- "description": "Double-shot flat white",
- "price": 4.5,
- "is_option": false,
- "sku": "FW-REG",
- "image_url": null,
- "created_at": "2025-11-01T00:00:00.000Z",
- "updated_at": "2026-06-01T00:00:00.000Z"
}
], - "total": 1,
- "limit": 20,
- "offset": 0,
- "page": 1
}
}Page through product categories for the brand. Optional source filter narrows to categories from one integration.
Each row includes product_count when available. Use category ids in POST /products/search or when reading enriched promotion rules.
| brand_id required | integer |
| page | integer >= 1 Default: 1 |
| limit | integer [ 1 .. 100 ] Default: 20 |
| search | string |
| source | string |
| updated_since | string <date-time> ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400. |
{- "message": "Categories fetched successfully",
- "response": {
- "categories": [
- {
- "id": 10,
- "name": "Coffee",
- "description": "Espresso-based drinks",
- "external_id": "cat-coffee",
- "product_count": 12,
- "created_at": "2025-11-01T00:00:00.000Z",
- "updated_at": "2026-06-01T00:00:00.000Z"
}
], - "total": 1,
- "limit": 20,
- "offset": 0,
- "page": 1
}
}Load one product category by id with product_count.
Returns partner-safe fields; extended[] is reserved for future category integration payloads.
| brand_id required | integer |
| category_id required | integer |
{- "message": "Category fetched successfully",
- "response": {
- "id": 10,
- "name": "Coffee",
- "description": "Espresso-based drinks",
- "external_id": "cat-coffee",
- "product_count": 12,
- "created_at": "2025-11-01T00:00:00.000Z",
- "updated_at": "2026-06-01T00:00:00.000Z",
- "extended": [ ]
}
}This section covers offers in myne: listing what is set up, checking who can see or use them, and recording a reward on an open sale at a POS or ordering app.
Read the life cycle below first. Then open Evaluate promotions, Apply a promotion, or Complete promotions for the fields of that one call. Linked words open the matching glossary definition.
A Promotion is the offer. A Surface is where the sale happens (register, ordering, join page, and so on). Show vs available is the difference between who can see an offer and who can use it.
Join-page claim is not an API call. Some offers must be claimed on the join page before they can be redeemed. Evaluate still returns those offers, marked unavailable with CLAIM_REQUIRED. Apply and complete reject with the same code. You can list Claimed customer groups and their members; you cannot add members or assign a claim through this API.
Collect Across Venues (venue_unlock) is a passport offer: the same action at each selected site (or each selected child brand under Head office) unlocks a reward. On lsk / lso evaluate, incomplete passports return venue_progress and non_redeemable_cause.code VENUE_UNLOCK_NOT_ELIGIBLE. Browse and get with a child-brand brand_id include Head office Collect Across Venues that target that brand (evaluate-time resolve for this type only — not general Head office promotion sync). Apply and complete use the same promotion id. Stamp brand_id values on venue_progress match ids from brand.organisation on Get credential scope and brand identity or Get brand bootstrap configuration — see Organisation (Head office group).
To list which promotions can show — for the brand catalog or for one customer — without an open order, use Browse promotions, List promotions for a customer, or Search promotions. Optional integration_type on those list calls is only a coarse filter when you do not yet know which app will evaluate the sale (ordering, listing, or ecommerce). It does not replace surface. Do not send integration_type on evaluate, apply, or complete. Those open-order calls always need surface (lsk or lso).
The expected life cycle is: check what fits, take the money off, apply (lock) the reward, then record the redemption when the customer has paid. You take the money off in your POS. myne does not change the ticket.
Use one way to record the redemption:
Follow these five steps in order.
|
1. Check what fits Ask which rewards fit this open order |
→ |
2. Take the money off You change the ticket in your POS |
→ |
3. Apply (lock the reward) Send Apply a promotion for this open order |
→ |
4. Mark the order Only if paid sales already go to myne |
→ |
5. Record the redemption When paid: myne records it, or you send Complete |
Before the customer pays, the sale is an open order. After the customer pays, myne can store a Transaction. Recording a promotion redemption does not create that purchase. The purchase is created when the POS or ordering system sends the sale.
Call Evaluate promotions while the order is open. We need the Customer (or the id you already synced under your External source), each line’s product id on each line for this Surface, and where you are selling (lsk for register, lso for ordering).
Send your order id as well, so later calls use the same order. myne sends that id back unchanged.
If the offer depends on venue, send a location id that matches how this place of sale names venues.
What happens: myne returns which rewards fit this customer and these lines, and how much to take off. This does not lock a reward.
For Buy X Get Y offers, paid X units are reserved before Y is discounted — Buy 1 Get 1 Free needs two matching units on the order.
You need the discount amount and method from step 1. Apply that change on the ticket in your POS or ordering system. myne does not change the ticket for you.
(Optional: expand the detail block below for how that looks on a register versus ordering.)
Call Apply a promotion. We need the same order id as step 1, the customer, the Surface (lsk or lso), and which Promotion they are using (promotion_id).
What happens: the promotion is applied (locked) on this open order. It is not redeemed yet. Points, cashback payment, and the redemption on the customer record wait until step 5. This does not take payment and it does not change the ticket.
Promotion stacking: if the promotions allow it, apply more than one promotion on the same order. Use the same order id each time. One later complete call records every locked reward on that order.
Skip this step if you will send Complete promotions when the customer pays.
If this POS already sends paid sales into myne, mark the open order so myne can match the payment to the locked reward. Write the basket_id and MYNE reference onto the order: prefix MYNE, value = the order id from step 3. Do not write the promotion id.
If you skip this mark and you also skip Complete promotions, the customer can pay and myne will not record the redemption.
To move from locked to redeemed, use one of these paths:
lsk or lso), and the paid product lines (not the discount line). One call records every locked reward on that order. Discount amounts in the request are optional and are for reporting only. They do not cause the redemption. If you send the same complete again, myne records nothing extra. If the venue does not match the offer, myne can return success with nothing redeemed.Recording the redemption does not create the paid purchase in myne. That Transaction is created when the POS or ordering system sends the sale.
After step 1, myne tells you how to take money off for that place of sale:
The promotion id is not a POS product id. Use it only when you Apply a promotion (step 3).
To list what can show without an open order, open Browse promotions or List promotions for a customer (optional integration_type — customer groups only, no app). For an open order, open Evaluate promotions (step 1), Apply a promotion (step 3), and Complete promotions (step 5, when you record the redemption yourself). Those three always need surface (lsk or lso). Do not send integration_type on them.
List promotions configured for a brand.
Filter by location_id, paginate with page and limit, or narrow with search.
Pass surface (connection_pages, meandu, lso, or lsk) when you already know the app. Optional integration_type is only for listing what can show without an open order and without knowing which app will later evaluate the sale: ordering → meandu, listing / ecommerce → connection_pages. Eligibility still follows customer Show vs available groups. Do not send integration_type on evaluate, apply, or complete — those basket calls always need surface (lsk or lso). pos is not a substitute for a register or ordering app.
Each promotion includes image_url (the offer image, or null), availability[] (the surfaces it may appear on) and enriched requirements/targets with product and category names plus extended[].
See glossary: Promotion, Surface, Location.
| brand_id required | integer |
| location_id | integer Restrict to promotions for one location (includes brand-wide promotions). |
| page | integer >= 1 Default: 1 |
| limit | integer [ 1 .. 100 ] Default: 20 |
| search | string |
| valid_only | boolean Default: false When true, only promotions within their valid date range are returned. |
| surface | string Enum: "connection_pages" "meandu" "lso" "lsk" "shopify" Restrict to promotions visible on one channel. |
| integration_type | string Enum: "ordering" "listing" "ecommerce" "pos" List filter only — not a basket field. Use to return which promotions can show from customer Show vs available groups, in a coarse context (ordering, listing, or ecommerce), without knowing which app will evaluate the sale. Do not send on evaluate, apply, or complete. Those open-order calls always need surface (lsk or lso). ordering → meandu; listing and ecommerce → connection_pages. |
| updated_since | string <date-time> ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400. |
{- "message": "Promotions fetched successfully",
- "response": {
- "promotions": [
- {
- "id": 901,
- "brand_id": 42,
- "name": "Free regular coffee",
- "description": "One regular coffee on us",
- "discount_type": "free_item",
- "applies_to": "in_store",
- "channel": "pos",
- "location_id": null,
- "valid_from": "2026-01-01T00:00:00.000Z",
- "valid_until": "2026-12-31T23:59:59.000Z",
- "created_at": "2025-12-01T00:00:00.000Z",
- "updated_at": "2026-06-01T00:00:00.000Z",
- "availability": [
- "connection_pages",
- "lsk",
- "lso",
- "meandu",
- "meandu_connect",
- "shopify"
], - "requirements": {
- "products": [ ],
- "categories": [
- {
- "id": 10,
- "name": "Coffee",
- "extended": [ ]
}
]
}, - "targets": {
- "products": [
- {
- "id": 501,
- "name": "Flat white",
- "extended": [
- {
- "source": "lightspeed-k",
- "external_id": "456",
- "json_data": { },
- "updated_at": null
}
]
}
], - "categories": [ ]
}
}
], - "total": 1,
- "limit": 20,
- "offset": 0,
- "page": 1
}
}Return promotions visible to a customer and whether each is redeemable now.
Each row includes eligibility.visibility_state (unlocked or locked) using myne Show vs available customer group rules, plus image_url, availability[] and enriched catalog references.
The response also includes the customer's current points_balance and cashback_balance_in_cents so you do not need a separate points or credit call. Pay with Cashback promotions also include cashback_balance_in_cents on the row (wallet balance, not a cart discount).
Pass surface when you know the app (same meaning as list promotions). Hidden promotions are omitted. Optional integration_type lists what this customer can see from Show vs available groups without an open order and without choosing the evaluate app (ordering, listing, or ecommerce). Do not send it on evaluate, apply, or complete.
See glossary: Promotion, Surface, Show vs available, Customer.
| brand_id required | integer |
| customer_id required | integer |
| valid_only | boolean Default: true When true (default), omit expired or not-yet-valid promotions. |
| surface | string Enum: "connection_pages" "meandu" "lso" "lsk" "shopify" Restrict to promotions visible on one channel. |
| integration_type | string Enum: "ordering" "listing" "ecommerce" "pos" List filter only — not a basket field. Use to return which promotions can show from customer Show vs available groups, in a coarse context (ordering, listing, or ecommerce), without knowing which app will evaluate the sale. Do not send on evaluate, apply, or complete. Those open-order calls always need surface (lsk or lso). ordering → meandu; listing and ecommerce → connection_pages. |
| updated_since | string <date-time> ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400. |
{- "message": "Customer promotions fetched successfully",
- "response": {
- "promotions": [
- {
- "id": 901,
- "brand_id": 42,
- "name": "Free regular coffee",
- "description": "One regular coffee on us",
- "discount_type": "free_item",
- "applies_to": "in_store",
- "channel": "pos",
- "location_id": null,
- "valid_from": "2026-01-01T00:00:00.000Z",
- "valid_until": "2026-12-31T23:59:59.000Z",
- "created_at": "2025-12-01T00:00:00.000Z",
- "updated_at": "2026-06-01T00:00:00.000Z",
- "availability": [
- "connection_pages",
- "lsk",
- "lso",
- "meandu",
- "meandu_connect",
- "shopify"
], - "requirements": {
- "products": [ ],
- "categories": [
- {
- "id": 10,
- "name": "Coffee",
- "extended": [ ]
}
]
}, - "targets": {
- "products": [
- {
- "id": 501,
- "name": "Flat white",
- "extended": [
- {
- "source": "lightspeed-k",
- "external_id": "456",
- "json_data": { },
- "updated_at": null
}
]
}
], - "categories": [ ]
}, - "eligibility": {
- "visibility_state": "locked",
- "unlock_display_text": "Visit 2 more times this month"
}
}, - {
- "id": 880,
- "brand_id": 42,
- "name": "Pay with Cashback",
- "description": "Spend store credit on this order.",
- "discount_type": "free_item",
- "applies_to": "in_store",
- "channel": "pos",
- "location_id": null,
- "valid_from": "2026-01-01T00:00:00.000Z",
- "valid_until": "2026-12-31T23:59:59.000Z",
- "created_at": "2025-12-01T00:00:00.000Z",
- "updated_at": "2026-06-01T00:00:00.000Z",
- "availability": [
- "connection_pages",
- "lsk",
- "lso",
- "meandu",
- "meandu_connect",
- "shopify"
], - "requirements": {
- "products": [ ],
- "categories": [
- {
- "id": 10,
- "name": "Coffee",
- "extended": [ ]
}
]
}, - "targets": {
- "products": [
- {
- "id": 501,
- "name": "Flat white",
- "extended": [
- {
- "source": "lightspeed-k",
- "external_id": "456",
- "json_data": { },
- "updated_at": null
}
]
}
], - "categories": [ ]
}, - "eligibility": {
- "visibility_state": "unlocked",
- "unlock_display_text": null
}, - "rules": {
- "promotion_type": "pay_with_cashback"
}, - "cashback_balance_in_cents": 2500
}
], - "total": 2,
- "points_balance": 120,
- "cashback_balance_in_cents": 2500
}
}Load one promotion with enrichment.
availability lists the canonical Surfaces it may appear on. image_url is the offer image (null when none is set). Requirements and targets include product and category names plus extended[] resolved from the promotion's rules.
See glossary: Promotion, Surface.
| brand_id required | integer |
| promotion_id required | integer |
{- "message": "Promotion fetched successfully",
- "response": {
- "id": 901,
- "brand_id": 42,
- "name": "Free regular coffee",
- "description": "One regular coffee on us",
- "discount_type": "free_item",
- "applies_to": "in_store",
- "channel": "pos",
- "location_id": null,
- "valid_from": "2026-01-01T00:00:00.000Z",
- "valid_until": "2026-12-31T23:59:59.000Z",
- "created_at": "2025-12-01T00:00:00.000Z",
- "updated_at": "2026-06-01T00:00:00.000Z",
- "availability": [
- "connection_pages",
- "lsk",
- "lso",
- "meandu",
- "meandu_connect",
- "shopify"
], - "requirements": {
- "products": [ ],
- "categories": [
- {
- "id": 10,
- "name": "Coffee",
- "extended": [ ]
}
]
}, - "targets": {
- "products": [
- {
- "id": 501,
- "name": "Flat white",
- "extended": [
- {
- "source": "lightspeed-k",
- "external_id": "456",
- "json_data": { },
- "updated_at": null
}
]
}
], - "categories": [ ]
}
}
}Search promotions with filters not available on the browse endpoint: category_ids and product_ids (any match against a promotion's rules), surface, optional integration_type (coarse list filter only — not for basket calls), valid_only, and free-text search.
Each result is enriched the same way as GET …/promotions/{promotion_id}.
See glossary: Promotion, Surface.
| brand_id required | integer |
| search | string |
| location_id | integer |
| product_ids | Array of integers |
| category_ids | Array of integers |
| surface | string Enum: "connection_pages" "meandu" "lso" "lsk" "shopify" |
| integration_type | string List filter only — not a basket field. Use to return which promotions can show from customer Show vs available groups, in a coarse context (ordering, listing, or ecommerce), without knowing which app will evaluate the sale. Do not send on evaluate, apply, or complete. Those open-order calls always need surface (lsk or lso). ordering → meandu; listing and ecommerce → connection_pages. |
| valid_only | boolean |
| page | integer >= 1 Default: 1 |
| limit | integer [ 1 .. 100 ] Default: 20 |
| updated_since | string <date-time> ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (UTC, inclusive). |
{- "search": "string",
- "location_id": 0,
- "product_ids": [
- 0
], - "category_ids": [
- 0
], - "surface": "connection_pages",
- "integration_type": "string",
- "valid_only": true,
- "page": 1,
- "limit": 20,
- "updated_since": "2019-08-24T14:15:22Z"
}{- "message": "Promotions fetched successfully",
- "response": {
- "promotions": [
- {
- "id": 901,
- "brand_id": 42,
- "name": "Free regular coffee",
- "description": "One regular coffee on us",
- "discount_type": "free_item",
- "applies_to": "in_store",
- "channel": "pos",
- "location_id": null,
- "valid_from": "2026-01-01T00:00:00.000Z",
- "valid_until": "2026-12-31T23:59:59.000Z",
- "created_at": "2025-12-01T00:00:00.000Z",
- "updated_at": "2026-06-01T00:00:00.000Z",
- "availability": [
- "connection_pages",
- "lsk",
- "lso",
- "meandu",
- "meandu_connect",
- "shopify"
], - "requirements": {
- "products": [ ],
- "categories": [
- {
- "id": 10,
- "name": "Coffee",
- "extended": [ ]
}
]
}, - "targets": {
- "products": [
- {
- "id": 501,
- "name": "Flat white",
- "extended": [
- {
- "source": "lightspeed-k",
- "external_id": "456",
- "json_data": { },
- "updated_at": null
}
]
}
], - "categories": [ ]
}
}
], - "total": 1,
- "limit": 20,
- "offset": 0,
- "page": 1
}
}Step 1 in the Promotions section (Check what fits). This call only returns which rewards fit. It does not lock a reward. Read that life cycle first; this page lists the fields for this call.
We need: who the customer is (customer_id, or an external_id you already synced under your External source), the open-order lines (each with the Order line pos_id for this Surface), and where you are selling (lsk for register, lso for ordering). This is an open-order call: send surface for the app. Do not send integration_type (that field is only for listing promotions without a basket). Send your order id (basket_id and MYNE reference) so later calls use the same order. Send a venue or location id when the offer depends on place.
What happens: myne returns which rewards fit and how much to take off. Each reward includes image_url (the offer image, or null) and a nested promotion. Unavailable rewards include non_redeemable_cause. Join-page claim uses CLAIM_REQUIRED. An exhausted first-X redemption cap uses TOTAL_REDEMPTION_LIMIT_REACHED (POS surfaces keep MAXIMUM_USES_REACHED for the same limit). Next: take the money off in your POS (step 2), Apply a promotion (step 3), then record the redemption when the customer has paid (steps 4–5).
See glossary: Surface, Order line pos_id, basket_id and MYNE reference, External source, external_id and external_data.
| brand_id required | integer |
| customer_id | integer myne customer id (use this or external_id). |
| external_id | string Your partner/PMS/CRM id previously synced under your credential External source (for example a CRM id). Resolved when customer_id is omitted (or via customer_source when set). Not email/phone matching. Not used for order-line product matching. Do not pass source on this endpoint. |
| customer_source | string Optional External source for customer external_id lookup only. Defaults to your credential source. Never used for order-line pos_id matching. |
| basket_id | string Your order id (POS order id or a unique id you generate). myne sends it back unchanged. This does not lock a reward. Reuse the same value on Apply a promotion. |
required | object |
| venue_id | string Venue id for location-scoped rules on ordering surfaces. Do not use this instead of business_location_id for register (surface=lsk). |
| business_location_id | string POS business location id (location.external_location_id in myne). Prefer for surface=lsk (register). |
| surface | string Enum: "connection_pages" "meandu" "lso" "lsk" "shopify" Channel for this open order. Send lsk (register) or lso (ordering). Do not send integration_type on this basket call. lsk/meandu/lso are forwarded to the rewards engine as source. |
| order_type | string Enum: "pickup" "delivery" "dine_in" Optional fulfillment type for this open order (pickup, delivery, or dine_in). Omit it and unrestricted promotions still evaluate. A promotion restricted to order types is not eligible when this is omitted or does not match (ORDER_TYPE_REQUIRED or ORDER_TYPE_MISMATCH on that reward, not a failed call). Any other value returns 400. Bopple maps their types to these three: ASAP / Scheduled / Catering Pickup → pickup; ASAP / Scheduled / Catering Delivery → delivery; Dine in / Room Service / Order to seat → dine_in. Web, App, and Kiosk stay as surface, not order_type. |
{- "customer_id": 104823,
- "surface": "lsk",
- "basket_id": "pos-order-7f3a2c",
- "business_location_id": "loc-sydney-01",
- "cart": {
- "items": [
- {
- "metadata": {
- "pos_id": "456"
}, - "amount_in_cents": 550,
- "quantity": 1
}
]
}
}{- "message": "Promotions evaluated successfully",
- "response": {
- "status": "ok",
- "basket_id": "pos-order-7f3a2c",
- "membership": {
- "id": "104823",
- "points_balance": 120,
- "rewards": [
- {
- "id": "901",
- "type": "Offer",
- "name": "Free regular coffee",
- "description": "One regular coffee on us",
- "status": "AVAILABLE_TO_REDEEM",
- "visibility_state": "unlocked",
- "application_scope": "line",
- "cart_application_method": "MANUAL_APPLY",
- "discount_amount_in_cents": 550,
- "discount_application": {
- "method": "negative_product_line",
- "instructions": "To take this discount off at the register: add a promotion product line priced at minus discount_amount_in_cents. Name the line after the reward. To lock the reward, send Apply a promotion with this promotion_id. Do not use the promotion id as a point of sale product id. After the customer has paid: if paid sales already go to myne, write MYNE and the order id on the order; otherwise send Complete promotions with that order id."
}, - "promotion": {
- "id": 901,
- "brand_id": 42,
- "name": "Free regular coffee",
- "description": "One regular coffee on us",
- "discount_type": "free_item",
- "applies_to": "in_store",
- "channel": "pos",
- "location_id": null,
- "valid_from": "2026-01-01T00:00:00.000Z",
- "valid_until": "2026-12-31T23:59:59.000Z",
- "created_at": "2025-12-01T00:00:00.000Z",
- "updated_at": "2026-06-01T00:00:00.000Z",
- "availability": [
- "connection_pages",
- "lsk",
- "lso",
- "meandu",
- "meandu_connect",
- "shopify"
], - "requirements": {
- "products": [ ],
- "categories": [
- {
- "id": 10,
- "name": "Coffee",
- "extended": [ ]
}
]
}, - "targets": {
- "products": [
- {
- "id": 501,
- "name": "Flat white",
- "extended": [
- {
- "source": "lightspeed-k",
- "external_id": "456",
- "json_data": { },
- "updated_at": null
}
]
}
], - "categories": [ ]
}
}
}
]
}, - "rewards": [
- {
- "id": "901",
- "type": "Offer",
- "name": "Free regular coffee",
- "description": "One regular coffee on us",
- "status": "AVAILABLE_TO_REDEEM",
- "visibility_state": "unlocked",
- "application_scope": "line",
- "cart_application_method": "MANUAL_APPLY",
- "discount_amount_in_cents": 550,
- "discount_application": {
- "method": "negative_product_line",
- "instructions": "To take this discount off at the register: add a promotion product line priced at minus discount_amount_in_cents. Name the line after the reward. To lock the reward, send Apply a promotion with this promotion_id. Do not use the promotion id as a point of sale product id. After the customer has paid: if paid sales already go to myne, write MYNE and the order id on the order; otherwise send Complete promotions with that order id."
}, - "promotion": {
- "id": 901,
- "brand_id": 42,
- "name": "Free regular coffee",
- "description": "One regular coffee on us",
- "discount_type": "free_item",
- "applies_to": "in_store",
- "channel": "pos",
- "location_id": null,
- "valid_from": "2026-01-01T00:00:00.000Z",
- "valid_until": "2026-12-31T23:59:59.000Z",
- "created_at": "2025-12-01T00:00:00.000Z",
- "updated_at": "2026-06-01T00:00:00.000Z",
- "availability": [
- "connection_pages",
- "lsk",
- "lso",
- "meandu",
- "meandu_connect",
- "shopify"
], - "requirements": {
- "products": [ ],
- "categories": [
- {
- "id": 10,
- "name": "Coffee",
- "extended": [ ]
}
]
}, - "targets": {
- "products": [
- {
- "id": 501,
- "name": "Flat white",
- "extended": [
- {
- "source": "lightspeed-k",
- "external_id": "456",
- "json_data": { },
- "updated_at": null
}
]
}
], - "categories": [ ]
}
}
}
], - "surface": "lsk"
}
}This is Apply a promotion — step 3 in the Promotions section (Lock the reward). This call applies the chosen promotion to the open order (locks it). It does not take money off the ticket (step 2) and it does not record the redemption (step 5). Read that life cycle first; this page lists the fields for this call.
We need: the Surface (lsk for register, lso for ordering), who the customer is (customer_id, or an external_id you already synced under your External source), and which Promotion they are using (promotion_id). This is an open-order call: send surface for the app. Do not send integration_type. Send the same order id (basket_id and MYNE reference) from step 1 when you have one. Send a venue or location id when the offer depends on place.
What happens: myne returns the order id to keep using (basket_id; if you omitted it, this is myne’s selection_id). The promotion is applied (locked), not redeemed. If the offer must be claimed on the join page first, apply rejects with code CLAIM_REQUIRED. If the first-X redemption cap is gone, apply rejects with TOTAL_REDEMPTION_LIMIT_REACHED. Then either mark the order so myne records the redemption when the paid sale arrives (steps 4–5), or send Complete promotions when the customer has paid (skip step 4).
Promotion stacking is covered in the Promotions life cycle (step 3).
See glossary: basket_id and MYNE reference, Promotion stacking, Surface, Promotion, External source.
| brand_id required | integer |
| customer_id | integer myne customer id (use this or external_id). |
| external_id | string Your partner/PMS/CRM id under your credential External source (for example a CRM id). Resolved when customer_id is omitted (or via customer_source when set). |
| customer_source | string Optional External source for customer external_id lookup only. Defaults to your credential source. |
| promotion_id required | integer Promotion to apply (lock) on the open order (from evaluate or catalog). |
| basket_id | string Your order id (POS order id or a unique id you generate). Field name remains basket_id. Reuse the same value on every apply for this open order, then on Complete promotions or on the MYNE order mark. |
| surface required | string Enum: "lsk" "lso" Where this open order is sold. Required. Send lsk (register) or lso (ordering) — the same value as evaluate. Apply can lock a reward only on those two surfaces, not join page, meandu, or shopify. When surface=lsk, the response includes MYNE mark details if paid sales already go to myne. |
| location_id | integer myne location id when the offer depends on place. |
| venue_id | string Venue id for location-scoped rules on ordering. Do not use this instead of business_location_id for register (surface=lsk). |
| business_location_id | string POS business location id (location.external_location_id in myne). Prefer for surface=lsk (register). |
| order_type | string Enum: "pickup" "delivery" "dine_in" Optional fulfillment type for this open order (pickup, delivery, or dine_in). Omit it and unrestricted promotions still evaluate. A promotion restricted to order types is not eligible when this is omitted or does not match (ORDER_TYPE_REQUIRED or ORDER_TYPE_MISMATCH on that reward, not a failed call). Any other value returns 400. Bopple maps their types to these three: ASAP / Scheduled / Catering Pickup → pickup; ASAP / Scheduled / Catering Delivery → delivery; Dine in / Room Service / Order to seat → dine_in. Web, App, and Kiosk stay as surface, not order_type. |
{- "customer_id": 104823,
- "promotion_id": 901,
- "surface": "lsk",
- "basket_id": "pos-order-7f3a2c"
}{- "message": "Promotion applied to open order successfully",
- "response": {
- "status": "ok",
- "basket_id": 9001,
- "selection_id": 9001,
- "promotion_id": 901,
- "surface": "lsk",
- "external_reference_prefix": "MYNE"
}
}Step 5 in the Promotions section (Record the redemption when the customer has paid), when paid sales from this POS do not already arrive in myne. Skip the order mark (step 4). Read that life cycle first; this page lists the fields for this call.
We need: the same basket_id from apply, who the customer is, the Surface (lsk or lso), and the paid product lines (each with the Order line pos_id — not the discount line). This is an open-order call: send surface for the app. Do not send integration_type. Optional discounts[] amounts are for reporting only. They do not cause the redemption. One call records every locked reward on that order.
What happens: myne records the redemptions and clears the lock. A later POS or ordering sale for the same cart does not redeem again. If nothing is still locked, redeemed_count is 0. Complete does not create Claimed-group membership. If a locked offer still requires join-page claim, complete rejects with CLAIM_REQUIRED instead of recording. Recording the redemption does not create the paid purchase in myne. That purchase is created when the POS or ordering system sends the sale.
See glossary: basket_id and MYNE reference, Surface, Transaction.
| brand_id required | integer |
| customer_id | integer myne customer id (use this or external_id). |
| external_id | string Your partner/PMS/CRM id under your credential External source. Resolved when customer_id is omitted (or via customer_source when set). |
| customer_source | string Optional External source for customer external_id lookup only. Defaults to your credential source. |
| basket_id required | string Required. Same open-order id returned from apply (or the partner id you sent on apply). |
| surface required | string Enum: "lsk" "lso" Where you sold. Required. Must be the same lsk (register) or lso (ordering) value used on evaluate and apply. |
required | object Paid product lines sold on this order (not the discount line). Same line shape as evaluate. We need these lines to record the redemption. |
| location_id | integer myne location id when the offer depends on place. |
| venue_id | string Optional venue id for location-scoped rules. |
| business_location_id | string POS business location id (location.external_location_id in myne). |
| order_type | string Enum: "pickup" "delivery" "dine_in" Optional fulfillment type for this open order (pickup, delivery, or dine_in). Omit it and unrestricted promotions still evaluate. A promotion restricted to order types is not eligible when this is omitted or does not match (ORDER_TYPE_REQUIRED or ORDER_TYPE_MISMATCH on that reward, not a failed call). Any other value returns 400. Bopple maps their types to these three: ASAP / Scheduled / Catering Pickup → pickup; ASAP / Scheduled / Catering Delivery → delivery; Dine in / Room Service / Order to seat → dine_in. Web, App, and Kiosk stay as surface, not order_type. |
Array of objects Optional amounts already taken off the paid order, for reporting only. Omit if you do not have amounts. Does not cause the redemption. |
{- "customer_id": 104823,
- "basket_id": "pos-order-7f3a2c",
- "surface": "lsk",
- "location_id": 12,
- "cart": {
- "items": [
- {
- "metadata": {
- "pos_id": "ITEM-1"
}, - "amount_in_cents": 550,
- "quantity": 1
}
]
}, - "discounts": [
- {
- "promotion_id": 901,
- "amount_in_cents": 550
}
]
}{- "message": "Promotions completed for this order successfully",
- "response": {
- "status": "ok",
- "redeemed_count": 1,
- "promotion_ids": [
- 901
], - "basket_id": "pos-order-7f3a2c",
- "surface": "lsk"
}
}Paid purchase history and line-item detail for customers. An open sale is an order. After the customer pays, myne can store a transaction. Purchases are created when the POS or ordering system sends the sale. Recording a promotion redemption does not create a purchase.
Page through paid purchase history for one customer. An open sale is an order; once paid it becomes a Transaction. Returns POS and payment-linked transactions with line items.
Use limit and offset (or page) for pagination. Set payments=true to include payment detail on each row.
Customer events and CRM activities are separate endpoints—see Activities and POST …/events. Returns 404 when the customer is not in this brand.
See glossary: Customer, Transaction, Activity.
| brand_id required | integer |
| customer_id required | integer |
| limit | integer [ 1 .. 100 ] Default: 25 |
| offset | integer [ 0 .. 10000 ] Default: 0 |
| page | integer >= 1 Default: 1 |
| payments | boolean When true, include payment detail on each transaction. |
| updated_since | string <date-time> ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400. |
{- "message": "Transactions fetched successfully",
- "response": {
- "transactions": [
- {
- "id": 550012,
- "transaction_id": 550012,
- "transacted_at": "2026-06-21T03:15:00.000Z",
- "location": "Sydney CBD",
- "location_id": 3,
- "source": "lightspeed",
- "external_transaction_id": "POS-88421",
- "notes": null,
- "order_type": "dine_in",
- "total_paid": 24.5,
- "line_items": [
- {
- "id": 88001,
- "product_id": 1201,
- "product_name": "Flat White",
- "quantity": 1,
- "price_inc_tax": 5.5,
- "total_price": 5.5,
- "children": [ ],
- "modifiers": [ ]
}
], - "payments": [
- {
- "id": 77001,
- "method": "Card",
- "paid_amount": 24.5,
- "tip_amount": 0,
- "last_4": "4242"
}
]
}
], - "total": 1,
- "limit": 25,
- "offset": 0,
- "page": 1
}
}Load one paid Transaction for the brand, including line items and payment rows.
Use after listing customer transactions or searching brand-wide. Returns 404 when the transaction is not in this brand.
See glossary: Transaction.
| brand_id required | integer |
| transaction_id required | integer |
| payments | boolean Default: true When false, omit payment rows from the response. |
{- "message": "Transaction fetched successfully",
- "response": {
- "transaction": {
- "id": 550012,
- "transaction_id": 550012,
- "transacted_at": "2026-06-21T03:15:00.000Z",
- "location": "Sydney CBD",
- "location_id": 3,
- "source": "lightspeed",
- "external_transaction_id": "POS-88421",
- "notes": null,
- "order_type": "dine_in",
- "total_paid": 24.5,
- "line_items": [
- {
- "id": 88001,
- "product_id": 1201,
- "product_name": "Flat White",
- "quantity": 1,
- "price_inc_tax": 5.5,
- "total_price": 5.5,
- "children": [ ],
- "modifiers": [ ]
}
], - "payments": [
- {
- "id": 77001,
- "method": "Card",
- "paid_amount": 24.5,
- "tip_amount": 0,
- "last_4": "4242"
}
]
}
}
}Search brand transactions (paid orders) with a JSON body: optional customer_id, location_id, date range (transacted_from / transacted_to), min_amount and max_amount (total paid), plus page, limit, and payments=true for payment detail.
Prefer the customer-scoped list when you already have customer_id.
See glossary: Transaction.
| brand_id required | integer |
| customer_id | integer |
| location_id | integer |
| transacted_from | string <date-time> |
| transacted_to | string <date-time> |
| date_from | string <date-time> Alias for transacted_from. |
| date_to | string <date-time> Alias for transacted_to. |
| min_amount | number |
| max_amount | number |
| payments | boolean |
| page | integer >= 1 Default: 1 |
| limit | integer [ 1 .. 100 ] Default: 25 |
| offset | integer >= 0 Default: 0 |
| updated_since | string <date-time> ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (UTC, inclusive). |
{- "customer_id": 0,
- "location_id": 0,
- "transacted_from": "2019-08-24T14:15:22Z",
- "transacted_to": "2019-08-24T14:15:22Z",
- "date_from": "2019-08-24T14:15:22Z",
- "date_to": "2019-08-24T14:15:22Z",
- "min_amount": 0,
- "max_amount": 0,
- "payments": true,
- "page": 1,
- "limit": 25,
- "offset": 0,
- "updated_since": "2019-08-24T14:15:22Z"
}{- "message": "Transactions fetched successfully",
- "response": {
- "transactions": [
- {
- "id": 550012,
- "transaction_id": 550012,
- "transacted_at": "2026-06-21T03:15:00.000Z",
- "location": "Sydney CBD",
- "location_id": 3,
- "source": "lightspeed",
- "external_transaction_id": "POS-88421",
- "notes": null,
- "order_type": "dine_in",
- "total_paid": 24.5,
- "line_items": [
- {
- "id": 88001,
- "product_id": 1201,
- "product_name": "Flat White",
- "quantity": 1,
- "price_inc_tax": 5.5,
- "total_price": 5.5,
- "children": [ ],
- "modifiers": [ ]
}
], - "payments": [
- {
- "id": 77001,
- "method": "Card",
- "paid_amount": 24.5,
- "tip_amount": 0,
- "last_4": "4242"
}
], - "customer_id": 104823
}
], - "total": 1,
- "limit": 25,
- "offset": 0,
- "page": 1
}
}Return activity type labels for CRM dropdowns: the standard library (Call, Email, Meeting, Note, Site visit, Demo, Proposal, Contract, Follow-up, Support) merged with any custom labels already used on this brand (staff or Connect).
Use before POST …/activities/manual.
Customer events (POST …/events) are integration timeline writes. Activities are the CRM feed, including manual notes.
See glossary: Activity.
| brand_id required | integer |
{- "message": "Manual activity types fetched successfully",
- "response": {
- "types": [
- "Call",
- "Contract",
- "Demo",
- "Email",
- "Follow-up",
- "Meeting",
- "Note",
- "Proposal",
- "Site visit",
- "Support"
]
}
}Page through the CRM activity feed for one customer—manual notes, reservations, task-linked entries, and integration-sourced rows.
Optional query filters: activity_type (or type) and source. For date ranges or brand-wide search, use POST …/activities/search.
Not the same as POST …/events, which appends integration timeline events. Paginate with limit and offset (or page). Returns 404 when the customer is not in this brand.
See glossary: Activity.
| brand_id required | integer |
| customer_id required | integer |
| limit | integer [ 1 .. 100 ] Default: 25 |
| offset | integer [ 0 .. 10000 ] Default: 0 |
| page | integer >= 1 Default: 1 |
| activity_type | string Match event_name or manual activity_type label (e.g. Call). |
| type | string Alias for activity_type. |
| source | string Filter by event source (e.g. business_manual or your integration source). |
| updated_since | string <date-time> ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400. |
{- "message": "Activities fetched successfully",
- "response": {
- "activities": [
- {
- "id": 99001,
- "event_time": "2026-06-20T11:00:00.000Z",
- "event_name": "Manual activity",
- "source": "integration_api",
- "location": "Sydney CBD",
- "location_id": 3,
- "business_id": 801,
- "business_name": "Acme Hospitality Group",
- "activity_performer_category": "staff",
- "title": "Follow-up call",
- "activity_type": "Call",
- "notes": "Confirmed catering order for Friday.",
- "created_by_display_name": "Partner CRM",
- "created_by_integration_source": "integration_api"
}
], - "total": 1,
- "limit": 25,
- "offset": 0,
- "page": 1
}
}Add a manual note or call log to the customer CRM feed without staff login.
Requires title, or both activity_type and notes. Prefer activity_type values from GET …/activities/manual-types.
Optional business_id when the contact is linked to a business account; event_time defaults to now. The activity is attributed to your API credential source and label. Do not send source in the body.
See glossary: Activity, Business, External source.
| brand_id required | integer |
| customer_id required | integer |
| title | string |
| activity_type | string |
| notes | string |
| event_time | string <date-time> |
| business_id | integer |
{- "title": "string",
- "activity_type": "string",
- "notes": "string",
- "event_time": "2019-08-24T14:15:22Z",
- "business_id": 0
}{- "message": "Manual activity recorded successfully",
- "response": {
- "id": 99001,
- "event_time": "2026-06-20T11:00:00.000Z",
- "event_name": "Manual activity",
- "source": "integration_api",
- "title": "Follow-up call",
- "activity_type": "Call",
- "notes": "Confirmed catering order for Friday."
}
}Search the CRM activity feed with a JSON body: optional customer_id, business_id, activity_type (or type), source, date range (event_from / event_to), plus page and limit.
Customer events (POST …/events) are integration timeline writes; this endpoint reads the broader CRM feed.
See glossary: Activity.
| brand_id required | integer |
| customer_id | integer |
| business_id | integer |
| activity_type | string |
| type | string Alias for activity_type. |
| source | string |
| event_from | string <date-time> |
| event_to | string <date-time> |
| date_from | string <date-time> Alias for event_from. |
| date_to | string <date-time> Alias for event_to. |
| page | integer >= 1 Default: 1 |
| limit | integer [ 1 .. 100 ] Default: 25 |
| offset | integer >= 0 Default: 0 |
| updated_since | string <date-time> ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (UTC, inclusive). |
{- "customer_id": 0,
- "business_id": 0,
- "activity_type": "string",
- "type": "string",
- "source": "string",
- "event_from": "2019-08-24T14:15:22Z",
- "event_to": "2019-08-24T14:15:22Z",
- "date_from": "2019-08-24T14:15:22Z",
- "date_to": "2019-08-24T14:15:22Z",
- "page": 1,
- "limit": 25,
- "offset": 0,
- "updated_since": "2019-08-24T14:15:22Z"
}{- "message": "Activities fetched successfully",
- "response": {
- "activities": [
- {
- "id": 99001,
- "event_time": "2026-06-20T11:00:00.000Z",
- "event_name": "Manual activity",
- "source": "integration_api",
- "location": "Sydney CBD",
- "location_id": 3,
- "business_id": 801,
- "business_name": "Acme Hospitality Group",
- "activity_performer_category": "staff",
- "title": "Follow-up call",
- "activity_type": "Call",
- "notes": "Confirmed catering order for Friday.",
- "created_by_display_name": "Partner CRM",
- "created_by_integration_source": "integration_api",
- "customer_id": 104823
}
], - "total": 1,
- "limit": 25,
- "offset": 0,
- "page": 1
}
}Staff follow-up tasks: browse, get, and search by assignee, due date, status, business, or customer.
Page through staff tasks for the brand. Defaults to open work (todo and in_progress) sorted by most recently updated.
Filter with query parameters: status or statuses, exclude_statuses, assignee_user_ids, assignee_filter (e.g. unassigned), due_date_filter, business_id, customer_id, free-text query, sort_by, and sort_dir.
Partner-safe fields only—assignee and customer emails are omitted.
See glossary: Task.
| brand_id required | integer |
| page | integer >= 1 Default: 1 |
| limit | integer [ 1 .. 100 ] Default: 20 |
| query | string Free-text search on title and linked record names. |
| status | string Enum: "todo" "in_progress" "complete" "parked" |
| statuses | string Comma-separated status values. |
| exclude_statuses | string Comma-separated statuses to omit. |
| assignee_user_ids | string Comma-separated myne user IDs. |
| assignee_filter | string Value: "unassigned" Special assignee filter. Use unassigned for tasks with no assignee. |
| due_date_filter | string Enum: "today" "tomorrow" "this_week" "past" "upcoming" "no_due_date" |
| business_id | integer |
| customer_id | integer |
| sort_by | string Default: "updated_at" Enum: "title" "status" "due_date" "updated_at" |
| sort_dir | string Default: "desc" Enum: "asc" "desc" |
| updated_since | string <date-time> ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400. |
{- "message": "Tasks fetched successfully",
- "response": {
- "tasks": [
- {
- "id": 501,
- "brand_id": 42,
- "title": "Call venue about catering enquiry",
- "description": "Follow up on Monday intake form.",
- "due_date": "2026-07-15",
- "due_time": "09:00:00",
- "status": "todo",
- "created_at": "2026-07-01T02:30:00.000Z",
- "updated_at": "2026-07-10T04:15:00.000Z",
- "status_changed_at": null,
- "comment_count": 1,
- "latest_comment": {
- "created_at": "2026-07-10T04:15:00.000Z",
- "preview": "Left voicemail",
- "author_first_name": "Alex",
- "author_last_name": "Nguyen"
}, - "assignees": [
- {
- "user_id": 12,
- "first_name": "Alex",
- "last_name": "Nguyen"
}
], - "businesses": [
- {
- "business_id": 801,
- "name": "Harbour Catering Co"
}
], - "customers": [
- {
- "customer_id": 104823,
- "first_name": "Jordan",
- "last_name": "Lee"
}
], - "locations": [
- {
- "location_id": 3,
- "name": "Main"
}
], - "attachments": [ ]
}
], - "pagination": {
- "page": 1,
- "limit": 20,
- "total_tasks": 1,
- "total_pages": 1
}
}
}Load one task with linked customers, businesses, locations, assignees, and optional status/comment updates.
Set include_updates=false to omit the updates array. Returns 404 when the task is not in this brand.
See glossary: Task.
| brand_id required | integer |
| task_id required | integer |
| include_updates | boolean Default: true When false, omit the updates array from the response. |
{- "message": "Task fetched successfully",
- "response": {
- "id": 501,
- "brand_id": 42,
- "title": "Call venue about catering enquiry",
- "description": "Follow up on Monday intake form.",
- "due_date": "2026-07-15",
- "due_time": "09:00:00",
- "status": "todo",
- "created_at": "2026-07-01T02:30:00.000Z",
- "updated_at": "2026-07-10T04:15:00.000Z",
- "status_changed_at": null,
- "comment_count": 1,
- "latest_comment": {
- "created_at": "2026-07-10T04:15:00.000Z",
- "preview": "Left voicemail",
- "author_first_name": "Alex",
- "author_last_name": "Nguyen"
}, - "assignees": [
- {
- "user_id": 12,
- "first_name": "Alex",
- "last_name": "Nguyen"
}
], - "businesses": [
- {
- "business_id": 801,
- "name": "Harbour Catering Co"
}
], - "customers": [
- {
- "customer_id": 104823,
- "first_name": "Jordan",
- "last_name": "Lee"
}
], - "locations": [
- {
- "location_id": 3,
- "name": "Main"
}
], - "attachments": [ ],
- "updates": [
- {
- "id": 9001,
- "task_id": 501,
- "brand_id": 42,
- "update_kind": "comment",
- "status_from": null,
- "status_to": null,
- "comment_text": "Left voicemail",
- "created_at": "2026-07-10T04:15:00.000Z",
- "created_by_first_name": "Alex",
- "created_by_last_name": "Nguyen"
}
]
}
}Page through tasks linked to one customer (contact). Uses the same pagination fields as brand task browse.
Useful when syncing follow-ups from your CRM back to myne or displaying open work on a contact profile.
See glossary: Task, Customer.
| brand_id required | integer |
| customer_id required | integer |
| page | integer >= 1 Default: 1 |
| limit | integer [ 1 .. 100 ] Default: 20 |
| updated_since | string <date-time> ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400. |
{- "message": "Customer tasks fetched successfully",
- "response": {
- "tasks": [
- {
- "id": 501,
- "brand_id": 42,
- "title": "Call venue about catering enquiry",
- "description": "Follow up on Monday intake form.",
- "due_date": "2026-07-15",
- "due_time": "09:00:00",
- "status": "todo",
- "created_at": "2026-07-01T02:30:00.000Z",
- "updated_at": "2026-07-10T04:15:00.000Z",
- "status_changed_at": null,
- "comment_count": 1,
- "latest_comment": {
- "created_at": "2026-07-10T04:15:00.000Z",
- "preview": "Left voicemail",
- "author_first_name": "Alex",
- "author_last_name": "Nguyen"
}, - "assignees": [
- {
- "user_id": 12,
- "first_name": "Alex",
- "last_name": "Nguyen"
}
], - "businesses": [
- {
- "business_id": 801,
- "name": "Harbour Catering Co"
}
], - "customers": [
- {
- "customer_id": 104823,
- "first_name": "Jordan",
- "last_name": "Lee"
}
], - "locations": [
- {
- "location_id": 3,
- "name": "Main"
}
], - "attachments": [ ]
}
], - "pagination": {
- "page": 1,
- "limit": 20,
- "total_tasks": 1,
- "total_pages": 1
}
}
}Search tasks with a JSON body: assignee_user_ids, assignee_filter, status or statuses, exclude_statuses, due_date_filter, business_id, customer_id, free-text query, sort_by, sort_dir, page, and limit.
Prefer GET …/tasks for the default open/recent browse; use this when you need richer filters in one request.
Creating tasks via Connect is not available yet.
See glossary: Task.
| brand_id required | integer |
| query | string |
| search | string Alias for query. |
| status | string Enum: "todo" "in_progress" "complete" "parked" |
| statuses | Array of strings |
| exclude_statuses | Array of strings |
| assignee_user_ids | Array of integers |
| assignee_filter | string Value: "unassigned" Special assignee filter. Use unassigned for tasks with no assignee. |
| due_date_filter | string Enum: "today" "tomorrow" "this_week" "past" "upcoming" "no_due_date" |
| business_id | integer |
| customer_id | integer |
| sort_by | string Enum: "title" "status" "due_date" "updated_at" |
| sort_dir | string Enum: "asc" "desc" |
| page | integer >= 1 Default: 1 |
| limit | integer [ 1 .. 100 ] Default: 20 |
| updated_since | string <date-time> ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (UTC, inclusive). |
{- "query": "string",
- "search": "string",
- "status": "todo",
- "statuses": [
- "string"
], - "exclude_statuses": [
- "string"
], - "assignee_user_ids": [
- 0
], - "assignee_filter": "unassigned",
- "due_date_filter": "today",
- "business_id": 0,
- "customer_id": 0,
- "sort_by": "title",
- "sort_dir": "asc",
- "page": 1,
- "limit": 20,
- "updated_since": "2019-08-24T14:15:22Z"
}{- "message": "Tasks fetched successfully",
- "response": {
- "tasks": [
- {
- "id": 501,
- "brand_id": 42,
- "title": "Call venue about catering enquiry",
- "description": "Follow up on Monday intake form.",
- "due_date": "2026-07-15",
- "due_time": "09:00:00",
- "status": "todo",
- "created_at": "2026-07-01T02:30:00.000Z",
- "updated_at": "2026-07-10T04:15:00.000Z",
- "status_changed_at": null,
- "comment_count": 1,
- "latest_comment": {
- "created_at": "2026-07-10T04:15:00.000Z",
- "preview": "Left voicemail",
- "author_first_name": "Alex",
- "author_last_name": "Nguyen"
}, - "assignees": [
- {
- "user_id": 12,
- "first_name": "Alex",
- "last_name": "Nguyen"
}
], - "businesses": [
- {
- "business_id": 801,
- "name": "Harbour Catering Co"
}
], - "customers": [
- {
- "customer_id": 104823,
- "first_name": "Jordan",
- "last_name": "Lee"
}
], - "locations": [
- {
- "location_id": 3,
- "name": "Main"
}
], - "attachments": [ ]
}
], - "pagination": {
- "page": 1,
- "limit": 20,
- "total_tasks": 1,
- "total_pages": 1
}
}
}Resolve a myne business account from your external_id without paging browse results.
When source is omitted, lookup uses your Custom API app name (Integrations label). Pass source explicitly to read IDs synced under another integration.
Returns 404 when no match exists for this brand.
See glossary: Business, external_id and external_data, External source.
| brand_id required | integer |
| external_id required | string Your integration's identifier for the business. |
| source | string Origin system for the external_id. When omitted, defaults to your Custom API app name (Integrations label). Read-only filter for cross-integration lookups. |
{- "message": "Business fetched successfully",
- "response": {
- "id": 801,
- "brand_id": 42,
- "location_id": 3,
- "name": "Acme Hospitality Group",
- "external_id": "biz-001",
- "source": "integration_api",
- "address_line_1": "100 George St",
- "address_line_2": null,
- "city": "Sydney",
- "region": "NSW",
- "postal_code": "2000",
- "country": "AU",
- "phone": "+61298765432",
- "email": "accounts@acme.example.com",
- "logo_url": null,
- "description": "Corporate catering partner",
- "created_at": "2025-03-01T10:00:00.000Z",
- "updated_at": "2026-06-15T08:30:00.000Z",
- "archived_at": null,
- "contact_count": 5,
- "primary_contact_customer_id": 104823,
- "primary_contact_first_name": "Jordan",
- "primary_contact_last_name": "Lee",
- "last_contacted_at": "2026-06-18T09:00:00.000Z",
- "last_active_at": "2026-06-20T14:22:00.000Z"
}
}Page through business (B2B) accounts for a brand. Use page and limit for pagination and query for free-text search.
Pass updated_since (ISO-8601, compared in UTC) to poll only businesses changed on or after that timestamp.
Business IDs from here or sync batch can be passed to the single-business and extended endpoints.
See glossary: Business.
| brand_id required | integer |
| page | integer >= 1 Default: 1 |
| limit | integer [ 1 .. 100 ] Default: 10 |
| query | string Free-text search across business fields. |
| updated_since | string <date-time> ISO-8601 timestamp. When set, only businesses with updated_at on or after this instant are returned (compared in UTC). |
{- "message": "Business records fetched successfully",
- "response": {
- "businesses": [
- {
- "id": 801,
- "brand_id": 42,
- "location_id": 3,
- "name": "Acme Hospitality Group",
- "external_id": "biz-001",
- "source": "integration_api",
- "address_line_1": "100 George St",
- "address_line_2": null,
- "city": "Sydney",
- "region": "NSW",
- "postal_code": "2000",
- "country": "AU",
- "phone": "+61298765432",
- "email": "accounts@acme.example.com",
- "logo_url": null,
- "description": "Corporate catering partner",
- "created_at": "2025-03-01T10:00:00.000Z",
- "updated_at": "2026-06-15T08:30:00.000Z",
- "archived_at": null,
- "contact_count": 5,
- "primary_contact_customer_id": 104823,
- "primary_contact_first_name": "Jordan",
- "primary_contact_last_name": "Lee",
- "last_contacted_at": "2026-06-18T09:00:00.000Z",
- "last_active_at": "2026-06-20T14:22:00.000Z"
}
], - "total_businesses": 24,
- "total_pages": 3,
- "current_page": 1
}
}Load one business account by ID.
Use after browse or sync when you need core fields such as name, email, and phone.
See glossary: Business.
| brand_id required | integer |
| business_id required | integer |
{- "message": "Business fetched successfully",
- "response": {
- "id": 801,
- "brand_id": 42,
- "location_id": 3,
- "name": "Acme Hospitality Group",
- "external_id": "biz-001",
- "source": "integration_api",
- "address_line_1": "100 George St",
- "address_line_2": null,
- "city": "Sydney",
- "region": "NSW",
- "postal_code": "2000",
- "country": "AU",
- "phone": "+61298765432",
- "email": "accounts@acme.example.com",
- "logo_url": null,
- "description": "Corporate catering partner",
- "created_at": "2025-03-01T10:00:00.000Z",
- "updated_at": "2026-06-15T08:30:00.000Z",
- "archived_at": null,
- "contact_count": 5,
- "primary_contact_customer_id": 104823,
- "primary_contact_first_name": "Jordan",
- "primary_contact_last_name": "Lee",
- "last_contacted_at": "2026-06-18T09:00:00.000Z",
- "last_active_at": "2026-06-20T14:22:00.000Z"
}
}Fetch extended business data: custom properties, linked contacts, and other metadata beyond the core business row.
Use when your integration needs the full CRM picture for a company account.
See glossary: Business, Extended profile.
| brand_id required | integer |
| business_id required | integer |
{- "message": "Business extended data fetched successfully",
- "response": [
- {
- "source": "integration_api",
- "external_id": "biz-001",
- "json_data": {
- "industry": "hospitality",
- "account_tier": "gold",
- "contract_renewal": "2027-01-01"
}, - "updated_at": "2026-06-15T08:30:00.000Z"
}
]
}Push up to 50 contacts, businesses, business–contact links, extended fields, or health topics in one request.
This is the primary write path for CRM, PMS, and POS integrations syncing data into myne (for example guests from MEWS).
Send top-level contact fields plus your external_id, and optionally contact_extended bodies for extra partner keys/metadata used later for lookup and enrichment.
Records are always stamped with your Custom API app name—do not send source (or contact_source / business_source) in item bodies.
Each item has a type and body; see the request schema for supported types.
See glossary: Sync batch, External source, external_id and external_data.
| brand_id required | integer |
required | Array of objects <= 50 items |
{- "items": [
- {
- "type": "contact",
- "body": { }
}
]
}{- "message": "Batch upsert complete",
- "response": {
- "count": 2,
- "results": [
- {
- "type": "contact",
- "result": {
- "customer_id": 104823,
- "external_id": "crm-001",
- "created": true
}
}, - {
- "type": "business",
- "result": {
- "business_id": 801,
- "external_id": "biz-001",
- "is_new": false
}
}
]
}
}Create a short-lived Bearer token (15 minutes) to load myne CRM embed widgets for one customer or business.
Call from your server after Basic auth. Pass entity_type (customer or business) and entity_id in the body, then use embed_token in the widget bootstrap.
See glossary: CRM embed, Customer, Business.
| brand_id required | integer |
| entity_type required | string Enum: "customer" "business" |
| entity_id required | integer |
{- "entity_type": "customer",
- "entity_id": 0
}{- "response": {
- "embed_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJicmFuZF9pZCI6NDJ9.example",
- "expires_in": 900,
- "token_type": "Bearer"
}
}Subscribe HTTPS destinations to brand events. Signing secret is returned only on create and rotate. Verify X-Myne-Signature with HMAC-SHA256 over {unix_ts}.{rawBody} using crypto.timingSafeEqual. Use X-Myne-Delivery-Id for idempotency. Poll collection GETs with updated_since when you prefer pull.
List HTTPS destinations this Custom API app created for brand events.
Signing secrets are never returned on list or get. Copy the secret when you create or rotate.
Staff can also manage the same rows in Settings → Webhooks → Outgoing.
See glossary: Outbound webhook, API Credentials.
| brand_id required | integer |
Subscribe an HTTPS URL to one or more brand event topics.
The signing secret is returned once. Store it and verify X-Myne-Signature with HMAC-SHA256 over {unix_ts}.{rawBody} using crypto.timingSafeEqual. Also send X-Myne-Topic and X-Myne-Delivery-Id (use delivery_id for idempotency).
URL must be HTTPS and must not resolve to private, loopback, link-local, or metadata addresses. Default ignore_own_writes is true so events this app wrote are not echoed back.
See glossary: Outbound webhook, Signature.
| brand_id required | integer |
| label | string |
| url required | string <uri> |
| topics required | Array of strings Items Enum: "customer.created" "customer.updated" "transaction.completed" "customer.added_to_group" "customer.removed_from_group" "customer.connected" "customer.form_submitted" "promotion.redeemed" "customer_activity.created" "customer_activity.updated" |
| ignore_own_writes | boolean Default: true |
{- "label": "string",
- "topics": [
- "customer.created"
], - "ignore_own_writes": true
}Return the topic catalog you can subscribe to: customer.created, customer.updated, transaction.completed, customer.added_to_group, customer.removed_from_group, customer.connected, customer.form_submitted, promotion.redeemed, customer_activity.created, customer_activity.updated.
The customer.created and customer.updated payloads include connection_type — connected when the customer has joined loyalty, known when myne has the customer but they have not joined, and unknown for an anonymous website visitor. It matches the connection_type on the customer list and get-customer endpoints.
Unknown topic names on create or update return 400.
See glossary: Outbound webhook.
| brand_id required | integer |
Change label, URL, topics, status (enabled or disabled), or ignore_own_writes.
Does not return a new signing secret. Use rotate for that.
See glossary: Outbound webhook.
| brand_id required | integer |
| webhook_id required | integer |
POST a sample Connect-shaped envelope to the destination URL with the same signature headers as live events, and write a delivery row.
See glossary: Outbound webhook, Signature.
| brand_id required | integer |
| webhook_id required | integer |
Recent delivery attempts for this subscription: status, HTTP code, topic, delivery_id, and a truncated error. Payloads and signing secrets are not stored.
See glossary: Outbound webhook.
| brand_id required | integer |
| webhook_id required | integer |