{"openapi":"3.1.0","info":{"title":"Loyeo API","version":"1.0.0","description":"The Loyeo public API. One key acts for the merchants its owner can reach, and the API requires a paid plan on each of them.\n\nEvery refusal carries a `code` in the body: switch on the code, never on the HTTP status alone.\n\nAuthentication is `Authorization: Bearer lk_live_...`. A key is shown once, at creation."},"servers":[{"url":"https://loyeo.fr/api/v1"}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"An API key, created in Réglages > API or in the network console. Prefix `lk_live_`."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}},"security":[{"apiKey":[]}],"x-scopes":[{"resource":"merchants","scopes":["merchants:read","merchants:write"]},{"resource":"program","scopes":["program:read","program:write"]},{"resource":"locations","scopes":["locations:read","locations:write"]},{"resource":"clients","scopes":["clients:read","clients:write","stamps:write"]},{"resource":"campaigns","scopes":["campaigns:read","campaigns:write"]},{"resource":"stats","scopes":["stats:read"]},{"resource":"autopilot","scopes":["autopilot:read","autopilot:write"]}],"paths":{"/merchants":{"get":{"summary":"The merchants this key may act for. `api_enabled` says whether the public API is usable on each one.","description":"Requires the `merchants:read` scope.","operationId":"getMerchants","security":[{"apiKey":["merchants:read"]}],"parameters":[],"responses":{"200":{"description":"Success."},"400":{"description":"invalid_request: the body or a parameter is malformed.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"missing_key, invalid_key, revoked_key or expired_key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"missing_scope (the key lacks the scope), merchant_forbidden (not this key’s merchant) or plan_required (the merchant has no paid plan).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"rate_limited. 120 reads and 20 writes per minute per key. Carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"lookup_failed or a transient dependency. Safe to retry.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}}}}},"/merchants/{merchantId}":{"get":{"summary":"One merchant.","description":"Requires the `merchants:read` scope.","operationId":"getMerchantsByMerchantId","security":[{"apiKey":["merchants:read"]}],"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success."},"400":{"description":"invalid_request: the body or a parameter is malformed.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"missing_key, invalid_key, revoked_key or expired_key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"missing_scope (the key lacks the scope), merchant_forbidden (not this key’s merchant) or plan_required (the merchant has no paid plan).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"rate_limited. 120 reads and 20 writes per minute per key. Carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"lookup_failed or a transient dependency. Safe to retry.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}}}}},"/merchants/{merchantId}/clients":{"get":{"summary":"The customer file. Phone numbers are masked.","description":"Requires the `clients:read` scope.","operationId":"getMerchantsByMerchantIdClients","security":[{"apiKey":["clients:read"]}],"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"page","in":"query","required":false,"description":"1-based page number. 20 per page.","schema":{"type":"string"}},{"name":"search","in":"query","required":false,"description":"Free-text match on name or phone.","schema":{"type":"string"}},{"name":"sort","in":"query","required":false,"description":"created_at | last_visit | total_visits | stamps","schema":{"type":"string"}},{"name":"order","in":"query","required":false,"description":"asc | desc","schema":{"type":"string"}}],"responses":{"200":{"description":"Success."},"400":{"description":"invalid_request: the body or a parameter is malformed.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"missing_key, invalid_key, revoked_key or expired_key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"missing_scope (the key lacks the scope), merchant_forbidden (not this key’s merchant) or plan_required (the merchant has no paid plan).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"rate_limited. 120 reads and 20 writes per minute per key. Carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"lookup_failed or a transient dependency. Safe to retry.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}}}}},"/merchants/{merchantId}/stamps":{"post":{"summary":"Credit a customer. Send exactly one of `phone` (create-or-credit) or `enrollment_id`. Supports Idempotency-Key.","description":"Requires the `stamps:write` scope.\n\nRules the schema cannot express:\n- Send exactly one of `phone` or `enrollment_id`, never both and never neither.\n- With `phone`, `consent: true` is required: the merchant must have collected the customer consent.\n- `quantity` applies in visit or quantity mode, `amount_cents` in amount mode. The server reads the mode from the programme and ignores the other field.\n\nAccepts `Idempotency-Key`. A replay returns the original response without redoing the work; the same key with a different body returns 422.","operationId":"postMerchantsByMerchantIdStamps","security":[{"apiKey":["stamps:write"]}],"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"phone":{"type":"string","minLength":1,"maxLength":40},"first_name":{"type":"string","maxLength":60},"consent":{"type":"boolean"},"enrollment_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$"},"quantity":{"type":"integer","minimum":1,"maximum":1000},"amount_cents":{"type":"integer","minimum":1,"maximum":1000000}}}}}},"responses":{"200":{"description":"Success."},"400":{"description":"invalid_request: the body or a parameter is malformed.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"missing_key, invalid_key, revoked_key or expired_key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"missing_scope (the key lacks the scope), merchant_forbidden (not this key’s merchant) or plan_required (the merchant has no paid plan).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"422":{"description":"The request is well formed but refused on the merits.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"rate_limited. 120 reads and 20 writes per minute per key. Carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"lookup_failed or a transient dependency. Safe to retry.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}}}}},"/merchants/{merchantId}/clients/import":{"post":{"summary":"Bulk-import customers, up to 5000 rows. Enrolls at zero stamps and sends NO welcome message.","description":"Requires the `clients:write` scope.\n\nRules the schema cannot express:\n- `attestation: true` is required: the merchant confirms the imported customers agreed to join the programme.\n- A row with a name but no usable phone is stored as pending, not enrolled, and is reported in `pending`.\n- A malformed `email` is dropped and the row is still imported. The count comes back in `dropped_emails`.\n- This path deliberately sends no welcome message: importing 5000 rows must not blast 5000 messages.\n\nAccepts `Idempotency-Key`. A replay returns the original response without redoing the work; the same key with a different body returns 422.","operationId":"postMerchantsByMerchantIdClientsImport","security":[{"apiKey":["clients:write"]}],"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"rows":{"minItems":1,"maxItems":5000,"type":"array","items":{"type":"object","properties":{"phone":{"type":"string","maxLength":40},"first_name":{"type":"string","maxLength":60},"last_name":{"type":"string","maxLength":60},"email":{"type":"string","maxLength":255},"row_number":{"type":"integer","minimum":1,"maximum":9007199254740991}},"required":["phone","first_name","row_number"]}},"attestation":{"type":"boolean"},"location_id":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$"},{"type":"null"}]}},"required":["rows","attestation"]}}}},"responses":{"200":{"description":"Success."},"400":{"description":"invalid_request: the body or a parameter is malformed.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"missing_key, invalid_key, revoked_key or expired_key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"missing_scope (the key lacks the scope), merchant_forbidden (not this key’s merchant) or plan_required (the merchant has no paid plan).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"422":{"description":"The request is well formed but refused on the merits.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"rate_limited. 120 reads and 20 writes per minute per key. Carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"lookup_failed or a transient dependency. Safe to retry.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}}}}},"/merchants/{merchantId}/stats":{"get":{"summary":"Dashboard counters. `stamps_issued_*` counts stamps, not customers.","description":"Requires the `stats:read` scope.","operationId":"getMerchantsByMerchantIdStats","security":[{"apiKey":["stats:read"]}],"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success."},"400":{"description":"invalid_request: the body or a parameter is malformed.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"missing_key, invalid_key, revoked_key or expired_key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"missing_scope (the key lacks the scope), merchant_forbidden (not this key’s merchant) or plan_required (the merchant has no paid plan).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"rate_limited. 120 reads and 20 writes per minute per key. Carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"lookup_failed or a transient dependency. Safe to retry.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}}}}},"/merchants/{merchantId}/program":{"get":{"summary":"The loyalty programme and its reward ladder. A reward with a null id is the legacy single reward, not yet a milestone row.","description":"Requires the `program:read` scope.","operationId":"getMerchantsByMerchantIdProgram","security":[{"apiKey":["program:read"]}],"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success."},"400":{"description":"invalid_request: the body or a parameter is malformed.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"missing_key, invalid_key, revoked_key or expired_key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"missing_scope (the key lacks the scope), merchant_forbidden (not this key’s merchant) or plan_required (the merchant has no paid plan).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"rate_limited. 120 reads and 20 writes per minute per key. Carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"lookup_failed or a transient dependency. Safe to retry.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}}}},"patch":{"summary":"Replace the programme settings and the WHOLE reward ladder. Partial ladders are not accepted.","description":"Requires the `program:write` scope.\n\nAccepts `Idempotency-Key`. A replay returns the original response without redoing the work; the same key with a different body returns 422.","operationId":"patchMerchantsByMerchantIdProgram","security":[{"apiKey":["program:write"]}],"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"programId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$"},"program_name":{"anyOf":[{"type":"string","maxLength":100},{"type":"string","const":""}]},"cooldown_hours":{"type":"integer","minimum":1,"maximum":48},"stamp_mode":{"type":"string","enum":["visit","quantity","amount"]},"amount_per_stamp":{"type":"number","exclusiveMinimum":0,"maximum":1000},"reward_mode":{"type":"string","enum":["card","catalogue"]},"milestones":{"minItems":1,"maxItems":10,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$"},"stamps_required":{"type":"integer","minimum":1,"maximum":1000},"reward_description":{"type":"string","minLength":3,"maxLength":200}},"required":["stamps_required","reward_description"]}}},"required":["programId","cooldown_hours","milestones"]}}}},"responses":{"200":{"description":"Success."},"400":{"description":"invalid_request: the body or a parameter is malformed.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"missing_key, invalid_key, revoked_key or expired_key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"missing_scope (the key lacks the scope), merchant_forbidden (not this key’s merchant) or plan_required (the merchant has no paid plan).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"422":{"description":"The request is well formed but refused on the merits.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"rate_limited. 120 reads and 20 writes per minute per key. Carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"lookup_failed or a transient dependency. Safe to retry.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}}}}},"/merchants/{merchantId}/locations":{"get":{"summary":"Points of sale. A location tags a visit for attribution; it never scopes stamps or the card.","description":"Requires the `locations:read` scope.","operationId":"getMerchantsByMerchantIdLocations","security":[{"apiKey":["locations:read"]}],"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success."},"400":{"description":"invalid_request: the body or a parameter is malformed.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"missing_key, invalid_key, revoked_key or expired_key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"missing_scope (the key lacks the scope), merchant_forbidden (not this key’s merchant) or plan_required (the merchant has no paid plan).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"rate_limited. 120 reads and 20 writes per minute per key. Carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"lookup_failed or a transient dependency. Safe to retry.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}}}},"post":{"summary":"Declare a point of sale. Past the plan cap this returns 409 with cap_reached.","description":"Requires the `locations:write` scope.\n\nAccepts `Idempotency-Key`. A replay returns the original response without redoing the work; the same key with a different body returns 422.","operationId":"postMerchantsByMerchantIdLocations","security":[{"apiKey":["locations:write"]}],"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":2,"maxLength":100},"address":{"type":"string","maxLength":200},"city":{"type":"string","maxLength":100},"postal_code":{"type":"string","pattern":"^(\\d{5})?$"}},"required":["name"]}}}},"responses":{"200":{"description":"Success."},"400":{"description":"invalid_request: the body or a parameter is malformed.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"missing_key, invalid_key, revoked_key or expired_key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"missing_scope (the key lacks the scope), merchant_forbidden (not this key’s merchant) or plan_required (the merchant has no paid plan).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"422":{"description":"The request is well formed but refused on the merits.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"rate_limited. 120 reads and 20 writes per minute per key. Carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"lookup_failed or a transient dependency. Safe to retry.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}}}}},"/merchants/{merchantId}/locations/{locationId}":{"patch":{"summary":"Rename a point of sale, or activate it. Renaming works on any plan; activating counts against the paid cap.","description":"Requires the `locations:write` scope.\n\nRules the schema cannot express:\n- Send at least one field to change.\n\nAccepts `Idempotency-Key`. A replay returns the original response without redoing the work; the same key with a different body returns 422.","operationId":"patchMerchantsByMerchantIdLocationsByLocationId","security":[{"apiKey":["locations:write"]}],"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"locationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":2,"maxLength":100},"address":{"type":"string","maxLength":200},"city":{"type":"string","maxLength":100},"postal_code":{"type":"string","pattern":"^(\\d{5})?$"},"is_active":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Success."},"400":{"description":"invalid_request: the body or a parameter is malformed.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"missing_key, invalid_key, revoked_key or expired_key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"missing_scope (the key lacks the scope), merchant_forbidden (not this key’s merchant) or plan_required (the merchant has no paid plan).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"422":{"description":"The request is well formed but refused on the merits.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"rate_limited. 120 reads and 20 writes per minute per key. Carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"lookup_failed or a transient dependency. Safe to retry.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}}}}},"/merchants/{merchantId}/campaigns":{"get":{"summary":"Campaign history. accepted_count is what the provider took, not what was delivered.","description":"Requires the `campaigns:read` scope.","operationId":"getMerchantsByMerchantIdCampaigns","security":[{"apiKey":["campaigns:read"]}],"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success."},"400":{"description":"invalid_request: the body or a parameter is malformed.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"missing_key, invalid_key, revoked_key or expired_key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"missing_scope (the key lacks the scope), merchant_forbidden (not this key’s merchant) or plan_required (the merchant has no paid plan).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"rate_limited. 120 reads and 20 writes per minute per key. Carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"lookup_failed or a transient dependency. Safe to retry.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}}}},"post":{"summary":"Create a campaign. `intent` is required and has no default. A saved campaign is inert until /send.","description":"Requires the `campaigns:write` scope.\n\nAccepts `Idempotency-Key`. A replay returns the original response without redoing the work; the same key with a different body returns 422.","operationId":"postMerchantsByMerchantIdCampaigns","security":[{"apiKey":["campaigns:write"]}],"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"channel":{"type":"string","enum":["sms","whatsapp","wallet"]},"audience_type":{"type":"string","enum":["all","segment","manual","single"]},"audience_ref":{"anyOf":[{"type":"string","maxLength":20000},{"type":"null"}]},"template_key":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}]},"template_values":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string","maxLength":1000}},"message":{"type":"string","maxLength":1600},"scheduled_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]},"intent":{"type":"string","enum":["send","save"]},"edit_id":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$"},{"type":"null"}]}},"required":["channel","audience_type","intent"]}}}},"responses":{"200":{"description":"Success."},"400":{"description":"invalid_request: the body or a parameter is malformed.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"missing_key, invalid_key, revoked_key or expired_key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"missing_scope (the key lacks the scope), merchant_forbidden (not this key’s merchant) or plan_required (the merchant has no paid plan).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"422":{"description":"The request is well formed but refused on the merits.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"rate_limited. 120 reads and 20 writes per minute per key. Carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"lookup_failed or a transient dependency. Safe to retry.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}}}}},"/merchants/{merchantId}/campaigns/{campaignId}/send":{"post":{"summary":"Send a saved campaign. Requires confirm:true. 202 means it was deferred to the next allowed slot, not refused.","description":"Requires the `campaigns:write` scope.\n\nAccepts `Idempotency-Key`. A replay returns the original response without redoing the work; the same key with a different body returns 422.","operationId":"postMerchantsByMerchantIdCampaignsByCampaignIdSend","security":[{"apiKey":["campaigns:write"]}],"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"campaignId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"confirm":{"type":"boolean","const":true}},"required":["confirm"]}}}},"responses":{"200":{"description":"Success."},"400":{"description":"invalid_request: the body or a parameter is malformed.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"missing_key, invalid_key, revoked_key or expired_key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"missing_scope (the key lacks the scope), merchant_forbidden (not this key’s merchant) or plan_required (the merchant has no paid plan).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"422":{"description":"The request is well formed but refused on the merits.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"rate_limited. 120 reads and 20 writes per minute per key. Carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"lookup_failed or a transient dependency. Safe to retry.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable cause. Switch on this, not on the status."},"message":{"type":"string"},"detail":{"type":"object","additionalProperties":true}},"required":["code","message"]}},"required":["error"]}}}}}}}}}