openapi: 3.0.3 info: title: Pixalink description: '' version: 1.0.0 servers: - url: '{{ config("app.url") }}' tags: - name: Categories description: "\nCategories provide a hierarchical classification system for organising spaces within the platform.
\nThe system uses a two-level structure where main categories (e.g., Food & Beverage, Retail)\ncontain subcategories (e.g., Restaurant, Cafe, Hotel) that spaces can be assigned to.
\nThis helps in filtering and discovering spaces based on their business type or industry." - name: Credits description: '' - name: 'Customer Paid Memberships' description: '' - name: 'Customer Rewards' description: "\nAPIs for managing customer rewards" - name: Customers description: '' - name: Organisations description: '' - name: 'POS Credits' description: '' - name: 'POS Transaction' description: '' - name: 'Paid Membership Types' description: '' - name: Payments description: '' - name: Plans description: '' - name: 'Reward Validation' description: "\nAPIs for validating reward redemption codes" - name: Rewards description: '' - name: Roles description: '' - name: Spaces description: '' - name: Transactions description: '' - name: Users description: '' - name: Calendars description: '' - name: Reservations description: '' - name: Endpoints description: '' - name: 'Location Management' description: '' - name: SSO description: '' components: securitySchemes: default: type: http scheme: bearer description: 'To use our API, please contact our support team to request API access. Once approved, you can generate your access token through your admin panel by visiting Personal Access Tokens page.' security: - default: [] paths: /api/categories: get: summary: 'List categories' operationId: listCategories description: "Returns a paginated list of categories. By default, only returns second level categories\n(categories that have a parent)." parameters: - in: query name: page description: 'Page number for pagination.' example: 1 required: false schema: type: integer description: 'Page number for pagination.' example: 1 - in: query name: per_page description: 'Number of items per page.' example: 15 required: false schema: type: integer description: 'Number of items per page.' example: 15 - in: query name: include description: "Comma-separated list of relations to include. Allowed values:\n* parent" example: parent required: false schema: type: string description: "Comma-separated list of relations to include. Allowed values:\n* parent" example: parent - in: query name: sort description: "Field to sort by. Prefix with - for descending order. Allowed values:\n* name\n* created_at\n* updated_at" example: '-created_at' required: false schema: type: string description: "Field to sort by. Prefix with - for descending order. Allowed values:\n* name\n* created_at\n* updated_at" example: '-created_at' - in: query name: 'filter[name]' description: 'Filter by name (partial match).' example: Restaurant required: false schema: type: string description: 'Filter by name (partial match).' example: Restaurant - in: query name: 'filter[parent_id]' description: 'Filter by parent ID. If set to null or 0, returns main categories.' example: 1 required: false schema: type: integer description: 'Filter by parent ID. If set to null or 0, returns main categories.' example: 1 - in: query name: 'filter[id]' description: 'Filter by ID.' example: 1 required: false schema: type: integer description: 'Filter by ID.' example: 1 - in: query name: 'filter[slug]' description: 'Filter by exact slug match.' example: restaurant required: false schema: type: string description: 'Filter by exact slug match.' example: restaurant responses: 200: description: '' content: application/json: schema: oneOf: - description: 'Default (Second Level Categories)' type: object example: data: - id: 5 name: Restaurant parent_id: 1 - id: 6 name: Cafe parent_id: 1 - id: 7 name: Hotel parent_id: 2 links: first: '@{{$baseUrl}}/api/categories?page=1' last: '@{{$baseUrl}}/api/categories?page=1' prev: null next: null meta: current_page: 1 from: 1 last_page: 1 path: '@{{$baseUrl}}/api/categories' per_page: 15 to: 3 total: 3 properties: data: type: array example: - id: 5 name: Restaurant parent_id: 1 - id: 6 name: Cafe parent_id: 1 - id: 7 name: Hotel parent_id: 2 items: type: object properties: id: type: integer example: 5 name: type: string example: Restaurant parent_id: type: integer example: 1 links: type: object properties: first: type: string example: '@{{$baseUrl}}/api/categories?page=1' last: type: string example: '@{{$baseUrl}}/api/categories?page=1' prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 1 path: type: string example: '@{{$baseUrl}}/api/categories' per_page: type: integer example: 15 to: type: integer example: 3 total: type: integer example: 3 - description: 'With Parent Included' type: object example: data: - id: 5 name: Restaurant parent_id: 1 parent: id: 1 name: 'Food & Beverage' - id: 6 name: Cafe parent_id: 1 parent: id: 1 name: 'Food & Beverage' links: first: '@{{$baseUrl}}/api/categories?include=parent&page=1' last: '@{{$baseUrl}}/api/categories?include=parent&page=1' prev: null next: null meta: current_page: 1 from: 1 last_page: 1 path: '@{{$baseUrl}}/api/categories' per_page: 15 to: 2 total: 2 properties: data: type: array example: - id: 5 name: Restaurant parent_id: 1 parent: id: 1 name: 'Food & Beverage' - id: 6 name: Cafe parent_id: 1 parent: id: 1 name: 'Food & Beverage' items: type: object properties: id: type: integer example: 5 name: type: string example: Restaurant parent_id: type: integer example: 1 parent: type: object properties: id: type: integer example: 1 name: type: string example: 'Food & Beverage' links: type: object properties: first: type: string example: '@{{$baseUrl}}/api/categories?include=parent&page=1' last: type: string example: '@{{$baseUrl}}/api/categories?include=parent&page=1' prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 1 path: type: string example: '@{{$baseUrl}}/api/categories' per_page: type: integer example: 15 to: type: integer example: 2 total: type: integer example: 2 tags: - Categories /api/credits: get: summary: 'List Credits' operationId: listCredits description: 'Get a paginated list of credits for the organisation.' parameters: - in: query name: 'filter[customer_id]' description: 'Filter by customer ID.' example: 1 required: false schema: type: integer description: 'Filter by customer ID.' example: 1 - in: query name: 'filter[type]' description: 'Filter by credit type.' example: 'Top Up' required: false schema: type: string description: 'Filter by credit type.' example: 'Top Up' - in: query name: 'filter[space_id]' description: 'Filter by space ID.' example: 1 required: false schema: type: integer description: 'Filter by space ID.' example: 1 - in: query name: 'filter[amount]' description: 'Filter by amount with operators.' example: '>100' required: false schema: type: string description: 'Filter by amount with operators.' example: '>100' - in: query name: sort description: 'Sort field (created_at, amount).' example: '-created_at' required: false schema: type: string description: 'Sort field (created_at, amount).' example: '-created_at' - in: query name: include description: 'Include relationships (customer, space, reward).' example: customer required: false schema: type: string description: 'Include relationships (customer, space, reward).' example: customer - in: query name: per_page description: 'Number of records per page.' example: 15 required: false schema: type: integer description: 'Number of records per page.' example: 15 responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 customer_id: 42 space_id: 1 amount: '50.00' operation: + type: 'Top Up' remarks: 'Birthday top-up' created_at: '2025-05-06T14:30:01.000000Z' updated_at: '2025-05-06T14:30:01.000000Z' links: first: ... last: ... prev: null next: null meta: current_page: 1 from: 1 last_page: 1 per_page: 15 to: 1 total: 1 properties: data: type: array example: - id: 1 customer_id: 42 space_id: 1 amount: '50.00' operation: + type: 'Top Up' remarks: 'Birthday top-up' created_at: '2025-05-06T14:30:01.000000Z' updated_at: '2025-05-06T14:30:01.000000Z' items: type: object properties: id: type: integer example: 1 customer_id: type: integer example: 42 space_id: type: integer example: 1 amount: type: string example: '50.00' operation: type: string example: + type: type: string example: 'Top Up' remarks: type: string example: 'Birthday top-up' created_at: type: string example: '2025-05-06T14:30:01.000000Z' updated_at: type: string example: '2025-05-06T14:30:01.000000Z' links: type: object properties: first: type: string example: ... last: type: string example: ... prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 1 per_page: type: integer example: 15 to: type: integer example: 1 total: type: integer example: 1 401: description: Unauthenticated content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. 403: description: 'Missing scope' content: application/json: schema: type: object example: message: 'This action is unauthorized.' properties: message: type: string example: 'This action is unauthorized.' tags: - Credits post: summary: 'Create Credit' operationId: createCredit description: 'Creates a new credit entry for a customer (top up, deduct, revert, etc.).' parameters: [] responses: 201: description: 'Top Up created' content: application/json: schema: type: object example: data: id: 1 customer_id: 42 space_id: 1 amount: '50.00' operation: + type: 'Top Up' remarks: 'Birthday top-up' created_at: '2025-05-06T14:30:01.000000Z' updated_at: '2025-05-06T14:30:01.000000Z' properties: data: type: object properties: id: type: integer example: 1 customer_id: type: integer example: 42 space_id: type: integer example: 1 amount: type: string example: '50.00' operation: type: string example: + type: type: string example: 'Top Up' remarks: type: string example: 'Birthday top-up' created_at: type: string example: '2025-05-06T14:30:01.000000Z' updated_at: type: string example: '2025-05-06T14:30:01.000000Z' 403: description: 'Missing scope' content: application/json: schema: type: object example: message: 'This action is unauthorized.' properties: message: type: string example: 'This action is unauthorized.' 422: description: 'Validation failed' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: type: - 'The selected type is invalid.' amount: - 'The amount field is required.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: type: type: array example: - 'The selected type is invalid.' items: type: string amount: type: array example: - 'The amount field is required.' items: type: string tags: - Credits requestBody: required: true content: application/json: schema: type: object properties: customer_id: type: integer description: 'Customer ID. Required if phone_number not provided.' example: 42 phone_number: type: string description: 'Customer phone number. Required if customer_id not provided.' example: '+60123456789' amount: type: numeric description: 'Credit amount in dollars.' example: '50.00' type: type: string description: 'Credit type. One of: `Top Up`, `Deduct`, `Bonus`, `Refund`, `Referral`, `Revert`.' example: 'Top Up' space_id: type: integer description: 'Space ID.' example: 1 remarks: type: string description: 'Additional remarks. Max 500 characters.' example: 'Birthday top-up' custom_properties: type: object description: 'Custom properties for this credit entry.' example: [] properties: { } source_credit_id: type: integer description: 'Required when type is `Revert`. The original TopUp credit to revert.' example: 10 required: - amount - type '/api/credits/{id}': get: summary: 'Get Credit Details' operationId: getCreditDetails description: 'Retrieve detailed information about a specific credit.' parameters: - in: query name: include description: 'Relationships to include (customer, space, reward).' example: customer required: false schema: type: string description: 'Relationships to include (customer, space, reward).' example: customer responses: 200: description: Success content: application/json: schema: type: object example: data: id: 1 customer_id: 42 space_id: 1 amount: '50.00' operation: + type: 'Top Up' remarks: 'Birthday top-up' created_at: '2025-05-06T14:30:01.000000Z' updated_at: '2025-05-06T14:30:01.000000Z' properties: data: type: object properties: id: type: integer example: 1 customer_id: type: integer example: 42 space_id: type: integer example: 1 amount: type: string example: '50.00' operation: type: string example: + type: type: string example: 'Top Up' remarks: type: string example: 'Birthday top-up' created_at: type: string example: '2025-05-06T14:30:01.000000Z' updated_at: type: string example: '2025-05-06T14:30:01.000000Z' 403: description: 'Missing scope' content: application/json: schema: type: object example: message: 'This action is unauthorized.' properties: message: type: string example: 'This action is unauthorized.' 404: description: 'Not found' content: application/json: schema: type: object example: message: 'Record not found.' properties: message: type: string example: 'Record not found.' tags: - Credits parameters: - in: path name: id description: 'The ID of the credit.' example: 3 required: true schema: type: integer - in: path name: credit description: 'The ID of the credit.' example: 1 required: true schema: type: integer /api/customer_paid_memberships: get: summary: 'List Customer Paid Memberships' operationId: listCustomerPaidMemberships description: "Returns a paginated list of customer paid membership instances. Records are scoped to the\nauthenticated user's organisation via the model's global scope." parameters: - in: query name: include description: 'Comma-separated relationships to include. Available: `customer`, `paidMembershipType`, `transactions`, `payments`, `activePayment`. Use `payments` to retrieve all payment records for reconciliation, or `activePayment` to retrieve only the currently linked payment.' example: 'paidMembershipType,customer' required: false schema: type: string description: 'Comma-separated relationships to include. Available: `customer`, `paidMembershipType`, `transactions`, `payments`, `activePayment`. Use `payments` to retrieve all payment records for reconciliation, or `activePayment` to retrieve only the currently linked payment.' example: 'paidMembershipType,customer' - in: query name: 'filter[customer_id]' description: 'Filter by customer ID.' example: 1 required: false schema: type: integer description: 'Filter by customer ID.' example: 1 - in: query name: 'filter[paid_membership_type_id]' description: 'Filter by membership type ID.' example: 1 required: false schema: type: integer description: 'Filter by membership type ID.' example: 1 - in: query name: 'filter[is_active]' description: 'Filter by active flag.' example: true required: false schema: type: boolean description: 'Filter by active flag.' example: true - in: query name: 'filter[billing_cycle]' description: 'Filter by billing cycle (`monthly` or `yearly`).' example: yearly required: false schema: type: string description: 'Filter by billing cycle (`monthly` or `yearly`).' example: yearly - in: query name: 'filter[expires_at]' description: 'Filter by expiry date using comparison operators (`>`, `<`, `=`). ISO 8601 date.' example: '>2025-04-01' required: false schema: type: string description: 'Filter by expiry date using comparison operators (`>`, `<`, `=`). ISO 8601 date.' example: '>2025-04-01' - in: query name: sort description: 'Sort field and direction. Prefix with `-` for descending. Available: `created_at`, `starts_at`, `expires_at`. Defaults to `-created_at`.' example: '-expires_at' required: false schema: type: string description: 'Sort field and direction. Prefix with `-` for descending. Available: `created_at`, `starts_at`, `expires_at`. Defaults to `-created_at`.' example: '-expires_at' - in: query name: per_page description: 'Number of records per page. Defaults to 15.' example: 15 required: false schema: type: integer description: 'Number of records per page. Defaults to 15.' example: 15 responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - 'Customer Paid Memberships' post: summary: 'Assign a Paid Membership' operationId: assignAPaidMembership description: "Assigns a paid membership of the given type and billing cycle to a customer. Any existing\nactive membership for that customer is automatically deactivated. Delegates lifecycle logic\nto `PaidMembershipService::assignMembership`, which records a `ManualAssign` transaction and\nissues any rewards configured on the membership type.\n\nIf the integrator has already collected payment in their own system, an optional `payment`\nobject may be supplied. When present, a `Payment` row is recorded against the membership\nand a `ManualPayment` transaction is logged. The accepted `payment.method` values (`cash`,\n`card`, `e_wallet`) are deliberately limited to manually-recorded instruments — gateway\nmethods are not accepted via this endpoint. Omit `payment` entirely for free/comp\nassignments." parameters: [] responses: 201: description: Created content: application/json: schema: type: object example: data: id: 1 membership_status: active membership_type_name: 'VIP Gold' billing_cycle: monthly starts_at: '2025-04-01' expires_at: '2025-05-01' is_active: true message: 'Paid membership assigned successfully' properties: data: type: object properties: id: type: integer example: 1 membership_status: type: string example: active membership_type_name: type: string example: 'VIP Gold' billing_cycle: type: string example: monthly starts_at: type: string example: '2025-04-01' expires_at: type: string example: '2025-05-01' is_active: type: boolean example: true message: type: string example: 'Paid membership assigned successfully' 422: description: 'Validation Error' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: billing_cycle: - 'The selected billing cycle is not available for this membership type.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: billing_cycle: type: array example: - 'The selected billing cycle is not available for this membership type.' items: type: string tags: - 'Customer Paid Memberships' requestBody: required: true content: application/json: schema: type: object properties: customer_id: type: string description: 'The ID of the customer to assign the membership to. Must belong to your organisation. The id of an existing record in the customers table.' example: 1 paid_membership_type_id: type: string description: 'The ID of the paid membership type catalog entry. Must belong to your organisation. The id of an existing record in the paid_membership_types table.' example: 1 billing_cycle: type: string description: 'The billing cycle for this membership. Must be one of the cycles configured in the membership type pricing options.' example: monthly enum: - monthly - yearly starts_at: type: string description: 'Optional start date. Defaults to now if omitted. ISO 8601 (YYYY-MM-DD). Must be a valid date.' example: '2025-04-01' nullable: true remarks: type: string description: 'Optional internal note explaining the assignment (e.g. promotion code, manual signup reason). Must not be greater than 500 characters.' example: 'Manually assigned by support after refund.' nullable: true payment: type: object description: 'Optional payment record. Use when the integrator has already collected payment in their own system and wants Pixalink to record it alongside the membership. Omit this object entirely for a free/comp assignment.' example: null properties: method: type: string description: 'The payment instrument used. Must be one of: `cash`, `card`, `e_wallet`. Payment gateway methods are not accepted here. This field is required when payment is present.' example: cash enum: - cash - card - e_wallet amount: type: number description: 'The amount collected, in major currency units (e.g. `99.00` for RM 99.00). Must be greater than zero — omit the `payment` block entirely for free/comp assignments. This field is required when payment is present. Must be at least 0.01.' example: 99.0 currency: type: string description: 'ISO 4217 currency code. Defaults to `MYR` if omitted. Must be 3 characters.' example: MYR nullable: true reference_id: type: string description: "The integrator's own transaction identifier, stored on the payment row for later reconciliation. Must not be greater than 255 characters." example: POS-12345 nullable: true remarks: type: string description: 'Optional note attached to the payment record. Must not be greater than 500 characters.' example: 'Paid at counter' nullable: true required: - customer_id - paid_membership_type_id - billing_cycle '/api/customer_paid_memberships/{id}': get: summary: 'Show Customer Paid Membership' operationId: showCustomerPaidMembership description: 'Retrieves a single customer paid membership by ID. Cross-organisation access returns 404.' parameters: - in: query name: include description: 'Comma-separated relationships to include. Available: `customer`, `paidMembershipType`, `transactions`, `payments`, `activePayment`. Use `payments` to retrieve all payment records for reconciliation, or `activePayment` to retrieve only the currently linked payment.' example: payments required: false schema: type: string description: 'Comma-separated relationships to include. Available: `customer`, `paidMembershipType`, `transactions`, `payments`, `activePayment`. Use `payments` to retrieve all payment records for reconciliation, or `activePayment` to retrieve only the currently linked payment.' example: payments responses: 200: description: Success content: application/json: schema: type: object example: data: id: 1 membership_status: active membership_type_name: 'VIP Gold' billing_cycle: monthly starts_at: '2025-04-01' expires_at: '2025-05-01' auto_renew: false is_active: true properties: data: type: object properties: id: type: integer example: 1 membership_status: type: string example: active membership_type_name: type: string example: 'VIP Gold' billing_cycle: type: string example: monthly starts_at: type: string example: '2025-04-01' expires_at: type: string example: '2025-05-01' auto_renew: type: boolean example: false is_active: type: boolean example: true 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'Record not found' properties: message: type: string example: 'Record not found' tags: - 'Customer Paid Memberships' put: summary: 'Update Customer Paid Membership' operationId: updateCustomerPaidMembership description: "Either extends the membership (via `extend_days` + `remarks`) or toggles `auto_renew`.\nThe two operations are mutually exclusive — sending both at once returns a 422.\n\nExtending delegates to `PaidMembershipService::extendMembership`, which records a\n`ManualExtend` transaction and sends the customer the configured notification." parameters: [] responses: 200: description: Success content: application/json: schema: type: object example: data: id: 1 membership_status: active membership_type_name: 'VIP Gold' billing_cycle: monthly starts_at: '2025-04-01' expires_at: '2025-05-15' auto_renew: true is_active: true message: 'Paid membership updated successfully' properties: data: type: object properties: id: type: integer example: 1 membership_status: type: string example: active membership_type_name: type: string example: 'VIP Gold' billing_cycle: type: string example: monthly starts_at: type: string example: '2025-04-01' expires_at: type: string example: '2025-05-15' auto_renew: type: boolean example: true is_active: type: boolean example: true message: type: string example: 'Paid membership updated successfully' 422: description: 'Mutually Exclusive Fields' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: extend_days: - 'The extend days field prohibits auto_renew from being present.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: extend_days: type: array example: - 'The extend days field prohibits auto_renew from being present.' items: type: string tags: - 'Customer Paid Memberships' requestBody: required: false content: application/json: schema: type: object properties: extend_days: type: integer description: 'Number of days to extend the membership by. Mutually exclusive with `auto_renew`. Requires `remarks`. This field is required when auto_renew is not present. This field is required when remarks is present. Must be at least 1. Must not be greater than 3650.' example: 14 nullable: true remarks: type: string description: 'Internal note for the extension. Required when `extend_days` is set. This field is required when extend_days is present. Must not be greater than 500 characters.' example: 'Goodwill extension after service outage.' nullable: true auto_renew: type: boolean description: 'Toggle the auto-renew flag for this membership. Mutually exclusive with `extend_days` / `remarks`. This field is required when extend_days is not present.' example: true nullable: true delete: summary: 'Cancel Customer Paid Membership' operationId: cancelCustomerPaidMembership description: "Cancels (deactivates) the customer's paid membership. Sets `is_active` to `false` and\nrecords a `Cancelled` transaction for the audit trail. The row is not hard-deleted —\nhistorical lookups remain intact." parameters: [] responses: 204: description: Cancelled content: text/plain: schema: type: string example: '' tags: - 'Customer Paid Memberships' parameters: - in: path name: id description: 'The ID of the customer paid membership.' example: 1 required: true schema: type: integer - in: path name: customer_paid_membership description: 'The ID of the membership instance.' example: 1 required: true schema: type: integer /api/customer_rewards: get: summary: 'List Customer Rewards' operationId: listCustomerRewards description: "Returns a paginated list of customer rewards belonging to the authenticated user's organisation.\nResults can be filtered, sorted and include related data through query parameters." parameters: - in: query name: include description: "Comma-separated list of relationships to include:\n- customer\n- reward\n- validatedAtSpace\n- validatedByUser" example: 'customer,reward,validatedAtSpace,validatedByUser' required: false schema: type: string description: "Comma-separated list of relationships to include:\n- customer\n- reward\n- validatedAtSpace\n- validatedByUser" example: 'customer,reward,validatedAtSpace,validatedByUser' - in: query name: 'filter[status]' description: 'Filter by reward status (Pending, Used, Expired).' example: Pending required: false schema: type: string description: 'Filter by reward status (Pending, Used, Expired).' example: Pending - in: query name: 'filter[code]' description: 'Filter by exact reward code match.' example: ABC123 required: false schema: type: string description: 'Filter by exact reward code match.' example: ABC123 - in: query name: 'filter[expired_at]' description: 'Filter by expiry date with operators (>, <, =).' example: '>2024-01-01' required: false schema: type: string description: 'Filter by expiry date with operators (>, <, =).' example: '>2024-01-01' - in: query name: 'filter[used_at]' description: 'Filter by redemption date with operators (>, <, =).' example: '<2024-12-31' required: false schema: type: string description: 'Filter by redemption date with operators (>, <, =).' example: '<2024-12-31' - in: query name: 'filter[customer_id]' description: 'Filter by customer ID.' example: 1 required: false schema: type: integer description: 'Filter by customer ID.' example: 1 - in: query name: 'filter[reward_id]' description: 'Filter by reward ID.' example: 1 required: false schema: type: integer description: 'Filter by reward ID.' example: 1 - in: query name: 'filter[validated_at_space_id]' description: 'Filter by validation space ID.' example: 1 required: false schema: type: integer description: 'Filter by validation space ID.' example: 1 - in: query name: 'filter[validated_by_user_id]' description: 'Filter by validator user ID.' example: 1 required: false schema: type: integer description: 'Filter by validator user ID.' example: 1 - in: query name: sort description: 'Sort field and direction. Allowed fields: expired_at, used_at, created_at.' example: '-created_at' required: false schema: type: string description: 'Sort field and direction. Allowed fields: expired_at, used_at, created_at.' example: '-created_at' - in: query name: per_page description: 'Number of records per page.' example: 15 required: false schema: type: integer description: 'Number of records per page.' example: 15 responses: 200: description: '' content: text/plain: schema: type: string example: "{\n \"data\":[\n {\n \"id\": 1,\n \"customer_id\": 1,\n \"reward_id\": 1,\n \"status\": \"Pending\",\n \"code\": \"REWARD123\",\n \"expired_at\": \"2024-12-31T23:59:59.000000Z\",\n \"used_at\": null,\n \"created_at\": \"2024-02-11T00:00:00.000000Z\",\n \"updated_at\": \"2024-02-11T00:00:00.000000Z\",\n \"customer\" : {\n \"id\": 1,\n \"name\": \"John Smith\",\n \"email\": \"john.smith@example.com\",\n \"phone_number\": \"+60123456789\",\n \"gender\": 1,\n \"date_of_birth\": \"1990-05-15\",\n \"source\": \"WhatsApp\",\n \"status\": \"Converted\",\n \"current_point\": 2500,\n \"created_at\": \"2024-01-15T08:30:00.000000Z\",\n \"updated_at\": \"2024-02-01T14:22:33.000000Z\",\n \"notes\": \"Prefers to be contacted via WhatsApp\",\n \"is_manually_assign_tier\": 0,\n \"current_credits\": 150,\n \"birthday_month\": 5,\n \"tags\": [\"VIP\", \"Regular Customer\"],\n \"space_id\": 1,\n \"tier_id\": 1,\n \"organisation_id\": 1,\n }\n },\n ],\n \"links\": {\n \"first\": \"@{{$baseUrl}}/customer_rewards?page=1\",\n \"last\": \"@{{$baseUrl}}/customer_rewards?page=5\",\n \"prev\": null,\n \"next\": \"@{{$baseUrl}}/customer_rewards?page=2\"\n },\n \"meta\": {\n \"current_page\": 1,\n \"from\": 1,\n \"last_page\": 5,\n \"links\": [\n {\n \"url\": null,\n \"label\": \"« Previous\",\n \"active\": false\n },\n {\n \"url\": \"@{{$baseUrl}}/customer_rewards?page=1\",\n \"label\": \"1\",\n \"active\": true\n },\n {\n \"url\": \"@{{$baseUrl}}/customer_rewards?page=2\",\n \"label\": \"2\",\n \"active\": false\n },\n {\n \"url\": \"@{{$baseUrl}}/customer_rewards?page=3\",\n \"label\": \"3\",\n \"active\": false\n },\n {\n \"url\": \"@{{$baseUrl}}/customer_rewards?page=2\",\n \"label\": \"Next »\",\n \"active\": false\n }\n ],\n \"path\": \"@{{$baseUrl}}/customer_rewards\",\n \"per_page\": 15,\n \"to\": 15,\n \"total\": 68\n }\n }" tags: - 'Customer Rewards' post: summary: 'Create Customer Reward' operationId: createCustomerReward description: "Assigns a reward to a customer and processes the point redemption. This endpoint performs several validation checks:\n\n1. Verifies that the customer has sufficient points for the reward\n2. Checks if the reward has reached its total availability limit (if configured)\n3. For one-time rewards, ensures the customer hasn't already redeemed it\n4. Validates that both customer and reward belong to the authenticated organisation\n\nUpon successful validation, the system will:\n- Deduct points from the customer's balance\n- Create a customer reward record\n- Generate a unique redemption code\n- If mark_as_redeemed is true, immediately mark the reward as used\n\nNote: The reward's points cost is determined by its configured amount. Ensure the customer\nhas sufficient points before making this request to avoid validation errors." parameters: [] responses: 201: description: 'Created successfully' content: application/json: schema: type: object example: data: id: 1 customer_id: 1 reward_id: 1 status: Pending code: REWARD123 expired_at: '2024-12-31T23:59:59.000000Z' used_at: null created_at: '2024-02-11T00:00:00.000000Z' updated_at: '2024-02-11T00:00:00.000000Z' message: 'Customer reward created successfully' properties: data: type: object properties: id: type: integer example: 1 customer_id: type: integer example: 1 reward_id: type: integer example: 1 status: type: string example: Pending code: type: string example: REWARD123 expired_at: type: string example: '2024-12-31T23:59:59.000000Z' used_at: type: string example: null nullable: true created_at: type: string example: '2024-02-11T00:00:00.000000Z' updated_at: type: string example: '2024-02-11T00:00:00.000000Z' message: type: string example: 'Customer reward created successfully' 422: description: '' content: application/json: schema: oneOf: - description: 'Total availability exceeded' type: object example: message: 'The given data was invalid.' errors: reward_id: - 'This reward cannot be redeemed as it has reached its maximum redemption limit.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: reward_id: type: array example: - 'This reward cannot be redeemed as it has reached its maximum redemption limit.' items: type: string - description: 'One-time reward already redeemed' type: object example: message: 'The given data was invalid.' errors: reward_id: - 'This reward has already been used/redeemed. (Single redemption reward cannot be redeemed twice)' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: reward_id: type: array example: - 'This reward has already been used/redeemed. (Single redemption reward cannot be redeemed twice)' items: type: string - description: 'Insufficient points' type: object example: message: 'The given data was invalid.' errors: reward_id: - 'Customer does not have enough point to redeem' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: reward_id: type: array example: - 'Customer does not have enough point to redeem' items: type: string - description: 'Invalid customer or reward ID' type: object example: message: 'The given data was invalid.' errors: customer_id: - 'The selected customer id is invalid.' reward_id: - 'The selected reward id is invalid.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: customer_id: type: array example: - 'The selected customer id is invalid.' items: type: string reward_id: type: array example: - 'The selected reward id is invalid.' items: type: string tags: - 'Customer Rewards' requestBody: required: true content: application/json: schema: type: object properties: customer_id: type: integer description: 'The ID of the customer who will receive the reward. Must belong to the authenticated organisation.' example: 1 reward_id: type: integer description: "The ID of the reward to be redeemed. Must exist in one of the organisation's spaces." example: 1 mark_as_redeemed: type: boolean description: 'optional If true, the reward will be marked as used immediately upon creation. Default: false.' example: false currency_type: type: string description: 'optional The currency type to use for redemption. Must be "point" or "credit". Defaults to "point" if not specified.' example: point nullable: true validated_at_space_id: type: integer description: 'optional ID of the space where the reward was validated. Required when mark_as_redeemed is true.' example: 1 nullable: true validated_by_user_id: type: integer description: 'optional ID of the user who validated the reward. Will default to current user if not specified when mark_as_redeemed is true.' example: 1 nullable: true required: - customer_id - reward_id '/api/customer_rewards/{id}': get: summary: 'Get Customer Reward Details' operationId: getCustomerRewardDetails description: 'Retrieves detailed information about a specific customer reward.' parameters: - in: query name: include description: 'Comma-separated list of relationships to include (customer, reward, validatedAtSpace, validatedByUser).' example: 'customer,reward,validatedAtSpace,validatedByUser' required: false schema: type: string description: 'Comma-separated list of relationships to include (customer, reward, validatedAtSpace, validatedByUser).' example: 'customer,reward,validatedAtSpace,validatedByUser' responses: 200: description: Success content: application/json: schema: type: object example: data: id: 1 customer_id: 1 reward_id: 1 status: Pending code: REWARD123 expired_at: '2024-12-31T23:59:59.000000Z' used_at: null created_at: '2024-02-11T00:00:00.000000Z' updated_at: '2024-02-11T00:00:00.000000Z' properties: data: type: object properties: id: type: integer example: 1 customer_id: type: integer example: 1 reward_id: type: integer example: 1 status: type: string example: Pending code: type: string example: REWARD123 expired_at: type: string example: '2024-12-31T23:59:59.000000Z' used_at: type: string example: null nullable: true created_at: type: string example: '2024-02-11T00:00:00.000000Z' updated_at: type: string example: '2024-02-11T00:00:00.000000Z' tags: - 'Customer Rewards' put: summary: 'Update Customer Reward' operationId: updateCustomerReward description: "Updates a customer reward's details.
\nCan update status, expiry date, and usage date.
\nCannot modify rewards that are Expired or Used for more than 1 hour.
\nUsed rewards can be updated within 1 hour of being marked as used." parameters: [] responses: 200: description: '' content: application/json: schema: oneOf: - description: 'Success - Status Update' type: object example: data: id: 1 status: Used expired_at: '2024-12-31T23:59:59.000000Z' used_at: '2024-02-11T00:00:00.000000Z' updated_at: '2024-02-11T00:00:00.000000Z' message: 'Customer reward updated successfully' properties: data: type: object properties: id: type: integer example: 1 status: type: string example: Used expired_at: type: string example: '2024-12-31T23:59:59.000000Z' used_at: type: string example: '2024-02-11T00:00:00.000000Z' updated_at: type: string example: '2024-02-11T00:00:00.000000Z' message: type: string example: 'Customer reward updated successfully' - description: 'Success - Update Expiry' type: object example: data: id: 1 status: Pending expired_at: '2024-12-31T23:59:59.000000Z' used_at: null updated_at: '2024-02-11T00:00:00.000000Z' message: 'Customer reward updated successfully' properties: data: type: object properties: id: type: integer example: 1 status: type: string example: Pending expired_at: type: string example: '2024-12-31T23:59:59.000000Z' used_at: type: string example: null nullable: true updated_at: type: string example: '2024-02-11T00:00:00.000000Z' message: type: string example: 'Customer reward updated successfully' 422: description: '' content: application/json: schema: oneOf: - description: 'Invalid Status Value' type: object example: message: 'The selected status is invalid.' errors: status: - 'The selected status is invalid.' properties: message: type: string example: 'The selected status is invalid.' errors: type: object properties: status: type: array example: - 'The selected status is invalid.' items: type: string - description: 'Invalid Status Change' type: object example: message: 'Cannot update expired rewards or rewards used more than 1 hour ago' errors: status: - 'Cannot update expired rewards or rewards used more than 1 hour ago.' properties: message: type: string example: 'Cannot update expired rewards or rewards used more than 1 hour ago' errors: type: object properties: status: type: array example: - 'Cannot update expired rewards or rewards used more than 1 hour ago.' items: type: string - description: 'Invalid Used At Without Status' type: object example: message: 'The given data was invalid.' errors: used_at: - 'used_at can only be set when status is Used.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: used_at: type: array example: - 'used_at can only be set when status is Used.' items: type: string - description: 'Future Used At Date' type: object example: message: 'The given data was invalid.' errors: used_at: - 'The used at must be a date before or equal to now.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: used_at: type: array example: - 'The used at must be a date before or equal to now.' items: type: string - description: 'Past Expiry Date' type: object example: message: 'The given data was invalid.' errors: expired_at: - 'The expired at must be a date after now.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: expired_at: type: array example: - 'The expired at must be a date after now.' items: type: string - description: 'Invalid Date Format' type: object example: message: 'The given data was invalid.' errors: expired_at: - 'The expired at is not a valid date.' used_at: - 'The used at is not a valid date.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: expired_at: type: array example: - 'The expired at is not a valid date.' items: type: string used_at: type: array example: - 'The used at is not a valid date.' items: type: string tags: - 'Customer Rewards' requestBody: required: false content: application/json: schema: type: object properties: status: type: string description: 'optional The reward status. Cannot update Expired rewards or Used rewards older than 1 hour. Used rewards can be updated within 1 hour.' example: Used expired_at: type: datetime description: 'optional New expiry date for the reward. Must be a future date.' example: '2024-12-31T23:59:59Z' used_at: type: datetime description: 'optional Date when the reward was used. Can only be set when status is Used. Must not be in the future.' example: '2024-02-10T15:30:00Z' nullable: true validated_at_space_id: type: integer description: 'optional ID of the space where the reward was validated. Auto-set when marking as Used. Can be explicitly set to null when reverting to Pending.' example: 1 nullable: true validated_by_user_id: type: integer description: 'optional ID of the user who validated the reward. Auto-set to current user when marking as Used. Can be explicitly set to null when reverting to Pending.' example: 1 nullable: true delete: summary: 'Delete Customer Reward' operationId: deleteCustomerReward description: 'Revokes/deletes a pending customer reward. Cannot revoke Used or Expired rewards.' parameters: [] responses: 200: description: Success content: application/json: schema: type: object example: message: 'Customer reward revoked successfully' properties: message: type: string example: 'Customer reward revoked successfully' 422: description: 'Cannot Revoke' content: application/json: schema: type: object example: message: 'Cannot revoke used or expired rewards' properties: message: type: string example: 'Cannot revoke used or expired rewards' tags: - 'Customer Rewards' parameters: - in: path name: id description: 'The ID of the customer reward.' example: 1 required: true schema: type: integer /api/customers: get: summary: 'List Customers' operationId: listCustomers description: "Returns a paginated list of customers belonging to the authenticated user's organisation.
\nResults can be filtered, sorted and include related data through query parameters." parameters: - in: query name: include description: "Comma-separated list of relationships to include:\n- tier\n- space\n- tags\n- address\n- activePaidMembership.paidMembershipType (includes active paid membership with type details)" example: 'tier,activePaidMembership.paidMembershipType' required: false schema: type: string description: "Comma-separated list of relationships to include:\n- tier\n- space\n- tags\n- address\n- activePaidMembership.paidMembershipType (includes active paid membership with type details)" example: 'tier,activePaidMembership.paidMembershipType' - in: query name: 'filter[phone_number]' description: 'Filter by phone number (partial match). Searches for phone numbers containing the provided value.' example: '88888888 (matches +60188888888)' required: false schema: type: string description: 'Filter by phone number (partial match). Searches for phone numbers containing the provided value.' example: '88888888 (matches +60188888888)' - in: query name: 'filter[email]' description: 'Filter by email match.' example: customer@example.com required: false schema: type: string description: 'Filter by email match.' example: customer@example.com - in: query name: 'filter[gender]' description: "Filter by gender. Must be one of:\n- 0 (Female)\n- 1 (Male)" example: 1 required: false schema: type: integer description: "Filter by gender. Must be one of:\n- 0 (Female)\n- 1 (Male)" example: 1 - in: query name: 'filter[source]' description: "Filter by customer source. Must be one of:\n- Email\n- Phone\n- Direct\n- Reservation\n- WhatsApp\n- StoreHub\n- Loyverse\n- Softinn\n- Loyalty" example: WhatsApp required: false schema: type: string description: "Filter by customer source. Must be one of:\n- Email\n- Phone\n- Direct\n- Reservation\n- WhatsApp\n- StoreHub\n- Loyverse\n- Softinn\n- Loyalty" example: WhatsApp - in: query name: 'filter[status]' description: "Filter by customer status. Must be one of:\n- Lead\n- Open\n- Replied\n- Opportunity\n- Quotation\n- Lost Quotation\n- Interested\n- Converted\n- Do Not Contact\n- Blocked" example: Lead required: false schema: type: string description: "Filter by customer status. Must be one of:\n- Lead\n- Open\n- Replied\n- Opportunity\n- Quotation\n- Lost Quotation\n- Interested\n- Converted\n- Do Not Contact\n- Blocked" example: Lead - in: query name: 'filter[current_point]' description: 'Filter by points with operators (>, <, =).' example: '>100' required: false schema: type: string description: 'Filter by points with operators (>, <, =).' example: '>100' - in: query name: 'filter[current_credits]' description: 'Filter by credits with operators (>, <, =).' example: '>50' required: false schema: type: string description: 'Filter by credits with operators (>, <, =).' example: '>50' - in: query name: 'filter[created_at]' description: "Filter by creation date with operators (>, <, =). You can:\n- Find customers created on a specific date: 2024-01-01\n- Find customers created after a date: >2024-01-01\n- Find customers created before a date: <2024-01-01\n- Find customers created between dates: >2024-01-01,<2024-01-31\nUse ISO 8601 format (YYYY-MM-DD)." example: '2024-01-01' required: false schema: type: string description: "Filter by creation date with operators (>, <, =). You can:\n- Find customers created on a specific date: 2024-01-01\n- Find customers created after a date: >2024-01-01\n- Find customers created before a date: <2024-01-01\n- Find customers created between dates: >2024-01-01,<2024-01-31\nUse ISO 8601 format (YYYY-MM-DD)." example: '2024-01-01' - in: query name: 'filter[updated_at]' description: 'Filter by last update date with operators (>, <, =). Use ISO 8601 format (YYYY-MM-DD).' example: '<2024-12-31' required: false schema: type: string description: 'Filter by last update date with operators (>, <, =). Use ISO 8601 format (YYYY-MM-DD).' example: '<2024-12-31' - in: query name: 'filter[tier]' description: 'Filter by tier ID.' example: '1' required: false schema: type: string description: 'Filter by tier ID.' example: '1' - in: query name: 'filter[space]' description: 'Filter by space ID.' example: '1' required: false schema: type: string description: 'Filter by space ID.' example: '1' - in: query name: 'filter[has_tags]' description: 'Filter by tag name.' example: VIP required: false schema: type: string description: 'Filter by tag name.' example: VIP - in: query name: 'filter[name]' description: 'Filter by customer name (partial match).' example: John required: false schema: type: string description: 'Filter by customer name (partial match).' example: John - in: query name: 'filter[birthday_month]' description: 'Filter by birth month (1-12).' example: 10 required: false schema: type: integer description: 'Filter by birth month (1-12).' example: 10 - in: query name: sort description: 'Sort field and direction. Allowed fields: name, created_at, current_point, current_credits.' example: '-created_at' required: false schema: type: string description: 'Sort field and direction. Allowed fields: name, created_at, current_point, current_credits.' example: '-created_at' - in: query name: per_page description: 'Number of records per page.' example: 15 required: false schema: type: integer description: 'Number of records per page.' example: 15 responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 name: 'John Smith' email: john.smith@example.com phone_number: '+60123456789' gender: 1 date_of_birth: '1990-05-15' source: WhatsApp status: Converted current_point: 2500 created_at: '2024-01-15T08:30:00.000000Z' updated_at: '2024-02-01T14:22:33.000000Z' notes: 'Prefers to be contacted via WhatsApp' is_manually_assign_tier: 0 current_credits: 150 birthday_month: 5 tags: - VIP - 'Regular Customer' space_id: 1 tier_id: 1 organisation_id: 1 tier: id: 1 name: 'Gold Member' sort: 2 min_point: 2000 color: '#FFD700' is_manual_assign: 1 point_multiplier: 1.5 perks: birthday_multiplier: 2 tier_configuration_id: 1 space: id: 1 uuid: f2d75bf6-3cb8-305c-8f70-f71fe697cba3 name: 'Main Branch' slug: main-branch matterport_model_id: null description: 'Our flagship store in the city centre' visibility: public email: main@example.com phone_number: '+60312345678' website: '@{{$baseUrl}}' social_media: facebook: example instagram: example organisation_id: 1 category_id: 1 visits: 1250 created_at: '2023-01-01T00:00:00.000000Z' updated_at: '2024-01-15T08:30:00.000000Z' deleted_at: null links: public_url: '@{{$baseUrl}}/spaces/main-branch' address: address_line_1: '123 Customer Street' address_line_2: 'Unit 4B' city: 'Kuala Lumpur' state: 'Federal Territory of Kuala Lumpur' country: Malaysia postal_code: '50000' full_address: '123 Customer Street, Unit 4B, Kuala Lumpur, Federal Territory of Kuala Lumpur, 50000, Malaysia' paidMembership: id: 123 membership_status: active membership_type_name: 'VIP Gold' billing_cycle: monthly starts_at: '2024-01-01' expires_at: '2024-02-01' is_active: true links: first: '@{{$baseUrl}}/customers?page=1' last: '@{{$baseUrl}}/customers?page=5' prev: null next: '@{{$baseUrl}}/customers?page=2' meta: current_page: 1 from: 1 last_page: 5 links: - url: null label: '« Previous' active: false - url: '@{{$baseUrl}}/customers?page=1' label: '1' active: true - url: '@{{$baseUrl}}/customers?page=2' label: '2' active: false - url: '@{{$baseUrl}}/customers?page=3' label: '3' active: false - url: '@{{$baseUrl}}/customers?page=2' label: 'Next »' active: false path: '@{{$baseUrl}}/customers' per_page: 15 to: 15 total: 68 properties: data: type: array example: - id: 1 name: 'John Smith' email: john.smith@example.com phone_number: '+60123456789' gender: 1 date_of_birth: '1990-05-15' source: WhatsApp status: Converted current_point: 2500 created_at: '2024-01-15T08:30:00.000000Z' updated_at: '2024-02-01T14:22:33.000000Z' notes: 'Prefers to be contacted via WhatsApp' is_manually_assign_tier: 0 current_credits: 150 birthday_month: 5 tags: - VIP - 'Regular Customer' space_id: 1 tier_id: 1 organisation_id: 1 tier: id: 1 name: 'Gold Member' sort: 2 min_point: 2000 color: '#FFD700' is_manual_assign: 1 point_multiplier: 1.5 perks: birthday_multiplier: 2 tier_configuration_id: 1 space: id: 1 uuid: f2d75bf6-3cb8-305c-8f70-f71fe697cba3 name: 'Main Branch' slug: main-branch matterport_model_id: null description: 'Our flagship store in the city centre' visibility: public email: main@example.com phone_number: '+60312345678' website: '@{{$baseUrl}}' social_media: facebook: example instagram: example organisation_id: 1 category_id: 1 visits: 1250 created_at: '2023-01-01T00:00:00.000000Z' updated_at: '2024-01-15T08:30:00.000000Z' deleted_at: null links: public_url: '@{{$baseUrl}}/spaces/main-branch' address: address_line_1: '123 Customer Street' address_line_2: 'Unit 4B' city: 'Kuala Lumpur' state: 'Federal Territory of Kuala Lumpur' country: Malaysia postal_code: '50000' full_address: '123 Customer Street, Unit 4B, Kuala Lumpur, Federal Territory of Kuala Lumpur, 50000, Malaysia' paidMembership: id: 123 membership_status: active membership_type_name: 'VIP Gold' billing_cycle: monthly starts_at: '2024-01-01' expires_at: '2024-02-01' is_active: true items: type: object properties: id: type: integer example: 1 name: type: string example: 'John Smith' email: type: string example: john.smith@example.com phone_number: type: string example: '+60123456789' gender: type: integer example: 1 date_of_birth: type: string example: '1990-05-15' source: type: string example: WhatsApp status: type: string example: Converted current_point: type: integer example: 2500 created_at: type: string example: '2024-01-15T08:30:00.000000Z' updated_at: type: string example: '2024-02-01T14:22:33.000000Z' notes: type: string example: 'Prefers to be contacted via WhatsApp' is_manually_assign_tier: type: integer example: 0 current_credits: type: integer example: 150 birthday_month: type: integer example: 5 tags: type: array example: - VIP - 'Regular Customer' items: type: string space_id: type: integer example: 1 tier_id: type: integer example: 1 organisation_id: type: integer example: 1 tier: type: object properties: id: type: integer example: 1 name: type: string example: 'Gold Member' sort: type: integer example: 2 min_point: type: integer example: 2000 color: type: string example: '#FFD700' is_manual_assign: type: integer example: 1 point_multiplier: type: number example: 1.5 perks: type: object properties: birthday_multiplier: type: integer example: 2 tier_configuration_id: type: integer example: 1 space: type: object properties: id: type: integer example: 1 uuid: type: string example: f2d75bf6-3cb8-305c-8f70-f71fe697cba3 name: type: string example: 'Main Branch' slug: type: string example: main-branch matterport_model_id: type: string example: null nullable: true description: type: string example: 'Our flagship store in the city centre' visibility: type: string example: public email: type: string example: main@example.com phone_number: type: string example: '+60312345678' website: type: string example: '@{{$baseUrl}}' social_media: type: object properties: facebook: type: string example: example instagram: type: string example: example organisation_id: type: integer example: 1 category_id: type: integer example: 1 visits: type: integer example: 1250 created_at: type: string example: '2023-01-01T00:00:00.000000Z' updated_at: type: string example: '2024-01-15T08:30:00.000000Z' deleted_at: type: string example: null nullable: true links: type: object properties: public_url: type: string example: '@{{$baseUrl}}/spaces/main-branch' address: type: object properties: address_line_1: type: string example: '123 Customer Street' address_line_2: type: string example: 'Unit 4B' city: type: string example: 'Kuala Lumpur' state: type: string example: 'Federal Territory of Kuala Lumpur' country: type: string example: Malaysia postal_code: type: string example: '50000' full_address: type: string example: '123 Customer Street, Unit 4B, Kuala Lumpur, Federal Territory of Kuala Lumpur, 50000, Malaysia' paidMembership: type: object properties: id: type: integer example: 123 membership_status: type: string example: active membership_type_name: type: string example: 'VIP Gold' billing_cycle: type: string example: monthly starts_at: type: string example: '2024-01-01' expires_at: type: string example: '2024-02-01' is_active: type: boolean example: true links: type: object properties: first: type: string example: '@{{$baseUrl}}/customers?page=1' last: type: string example: '@{{$baseUrl}}/customers?page=5' prev: type: string example: null nullable: true next: type: string example: '@{{$baseUrl}}/customers?page=2' meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 5 links: type: array example: - url: null label: '« Previous' active: false - url: '@{{$baseUrl}}/customers?page=1' label: '1' active: true - url: '@{{$baseUrl}}/customers?page=2' label: '2' active: false - url: '@{{$baseUrl}}/customers?page=3' label: '3' active: false - url: '@{{$baseUrl}}/customers?page=2' label: 'Next »' active: false items: type: object properties: url: type: string example: null nullable: true label: type: string example: '« Previous' active: type: boolean example: false path: type: string example: '@{{$baseUrl}}/customers' per_page: type: integer example: 15 to: type: integer example: 15 total: type: integer example: 68 tags: - Customers post: summary: 'Create Customer' operationId: createCustomer description: "Creates a new customer in the system.
\nThe customer will be associated with the authenticated user's organisation.
\nSupports tag assignment." parameters: [] responses: 201: description: 'Created successfully' content: text/plain: schema: type: string example: "{\n \"data\": {\n \"id\": 1,\n \"name\": \"John Doe\",\n \"email\": \"john@example.com\",\n \"phone_number\": \"+60123456789\",\n \"gender\": 1,\n \"date_of_birth\": \"1990-01-01\",\n \"source\": \"WhatsApp\",\n \"status\": \"Lead\",\n \"current_point\": 0,\n \"notes\": \"Prefers evening appointments\",\n \"custom_properties\": null,\n \"is_manually_assign_tier\": 0,\n \"current_credits\": 0,\n \"birthday_month\": 1,\n \"space_id\": 1,\n \"organisation_id\": 1,\n \"created_at\": \"2025-02-01T00:00:00.000000Z\",\n \"updated_at\": \"2025-02-01T00:00:00.000000Z\",\n \"address\": {\n \"address_line_1\": \"No 39, Jalan Desa 1/1\",\n \"address_line_2\": \"Taman Desa\",\n \"city\": \"Petaling Jaya\",\n \"state\": \"Selangor\",\n \"country\": \"Malaysia\",\n \"postal_code\": \"58100\",\n \"full_address\": \"No 39, Jalan Desa 1/1, Taman Desa, Petaling Jaya, Selangor, 58100, Malaysia\",\n }\n },\n \"message\": \"Customer created successfully\"\n}" 422: description: 'Validation failed' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: email: - 'The email address is already taken.' phone_number: - 'The phone number is already taken.' source: - 'The selected source is invalid.' status: - 'The selected status is invalid.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: email: type: array example: - 'The email address is already taken.' items: type: string phone_number: type: array example: - 'The phone number is already taken.' items: type: string source: type: array example: - 'The selected source is invalid.' items: type: string status: type: array example: - 'The selected status is invalid.' items: type: string tags: - Customers requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'The full name of the customer. Must not be greater than 255 characters.' example: 'John Smith' email: type: string description: 'The email address of the customer. Must be a valid RFC compliant email. Must be a valid email address.' example: john.smith@example.com nullable: true phone_number: type: string description: 'The phone number of the customer in E.164 format.' example: '+60123456789' gender: type: string description: 'The gender of the customer.' example: 1 enum: - 0 - 1 nullable: true date_of_birth: type: string description: 'The date of birth of the customer. Must be a valid date. Must be a date before 2008-05-21. Must be a date after 1914-05-21.' example: '1990-01-01' nullable: true space_id: type: string description: 'The ID of the space this customer belongs to. Must belong to your organisation. The id of an existing record in the spaces table.' example: 1 nullable: true source: type: string description: 'The source where the customer was acquired from.' example: WhatsApp enum: - Email - Phone - Direct - Reservation - WhatsApp - StoreHub - Loyverse - Softinn - GeniusPos - WooCommerce - Shopify - Loyalty - Zeoniq - Bukku - IvendPos - LemonPos - PosPal - QnE status: type: string description: 'The status of the customer.' example: Lead enum: - Lead - Open - Replied - Opportunity - Quotation - 'Lost Quotation' - Interested - Converted - 'Do Not Contact' - Blocked tier_id: type: string description: 'The ID of the tier assigned to this customer. Must be a valid tier from your organisation.' example: 1 nullable: true is_manually_assign_tier: type: boolean description: 'Whether the tier was manually assigned to the customer.' example: true tags: type: array description: '' example: - aliquid items: type: string notes: type: string description: 'Additional notes about the customer. Must not be greater than 65535 characters.' example: 'Prefers to be contacted via WhatsApp' nullable: true address: type: object description: '' example: [] properties: address_line_1: type: string description: "The first line of the customer's address. Must not be greater than 255 characters." example: '123 Main Street' nullable: true address_line_2: type: string description: "The second line of the customer's address (optional). Must not be greater than 255 characters." example: 'Unit 4B' nullable: true country: type: string description: 'The country of residence. This field is required when address.address_line_1 is present.' example: Malaysia enum: - Malaysia - Singapore state: type: string description: 'The state of residence. Must exist in our database. This field is required when address.address_line_1 is present. The name of an existing record in the states table.' example: Selangor city: type: string description: 'The city of residence. Must exist in our database. This field is required when address.address_line_1 is present. The name of an existing record in the cities table.' example: 'Petaling Jaya' postal_code: type: string description: 'The postal code. Must be exactly 5 digits. This field is required when address.address_line_1 is present. Must be 5 digits.' example: '46150' required: - name - phone_number - source - status '/api/customers/{id}': get: summary: 'Show Customer Details' operationId: showCustomerDetails description: 'Retrieves detailed information about a specific customer, including any requested relationships.' parameters: - in: query name: include description: "Comma-separated list of relationships to include. Available relationships:\n- tier\n- space\n- address\n- tags\n- activePaidMembership.paidMembershipType (includes active paid membership with type details)" example: 'tier,activePaidMembership.paidMembershipType' required: false schema: type: string description: "Comma-separated list of relationships to include. Available relationships:\n- tier\n- space\n- address\n- tags\n- activePaidMembership.paidMembershipType (includes active paid membership with type details)" example: 'tier,activePaidMembership.paidMembershipType' responses: 200: description: Success content: application/json: schema: type: object example: data: id: 1 name: 'John Doe' email: john@example.com phone_number: '+60123456789' gender: 1 date_of_birth: '1990-01-01' source: WhatsApp status: Lead current_point: 0 notes: 'Prefers evening appointments' custom_properties: null is_manually_assign_tier: 0 current_credits: 0 birthday_month: 1 space_id: 1 organisation_id: 1 created_at: '2025-02-01T00:00:00.000000Z' updated_at: '2025-02-01T00:00:00.000000Z' tier_id: 1 tier: id: 1 name: Bronze points_required: 0 paidMembership: id: 123 membership_status: active membership_type_name: 'VIP Gold' billing_cycle: monthly starts_at: '2024-01-01' expires_at: '2024-02-01' is_active: true properties: data: type: object properties: id: type: integer example: 1 name: type: string example: 'John Doe' email: type: string example: john@example.com phone_number: type: string example: '+60123456789' gender: type: integer example: 1 date_of_birth: type: string example: '1990-01-01' source: type: string example: WhatsApp status: type: string example: Lead current_point: type: integer example: 0 notes: type: string example: 'Prefers evening appointments' custom_properties: type: string example: null nullable: true is_manually_assign_tier: type: integer example: 0 current_credits: type: integer example: 0 birthday_month: type: integer example: 1 space_id: type: integer example: 1 organisation_id: type: integer example: 1 created_at: type: string example: '2025-02-01T00:00:00.000000Z' updated_at: type: string example: '2025-02-01T00:00:00.000000Z' tier_id: type: integer example: 1 tier: type: object properties: id: type: integer example: 1 name: type: string example: Bronze points_required: type: integer example: 0 paidMembership: type: object properties: id: type: integer example: 123 membership_status: type: string example: active membership_type_name: type: string example: 'VIP Gold' billing_cycle: type: string example: monthly starts_at: type: string example: '2024-01-01' expires_at: type: string example: '2024-02-01' is_active: type: boolean example: true 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'Record not found' properties: message: type: string example: 'Record not found' tags: - Customers put: summary: 'Update Customer' operationId: updateCustomer description: "Updates an existing customer's information.
All fields are optional.
\nExisting tags will be synced with the provided tags array if included." parameters: [] responses: 200: description: Success content: text/plain: schema: type: string example: "{\n \"data\": {\n \"id\": 1,\n \"name\": \"John Doe\",\n \"email\": \"john@example.com\",\n \"phone_number\": \"+60123456789\",\n \"gender\": 1,\n \"date_of_birth\": \"1990-01-01\",\n \"source\": \"WhatsApp\",\n \"status\": \"Lead\",\n \"current_point\": 0,\n \"notes\": \"Prefers evening appointments\",\n \"custom_properties\": null,\n \"is_manually_assign_tier\": 0,\n \"current_credits\": 0,\n \"birthday_month\": 1,\n \"space_id\": 1,\n \"organisation_id\": 1,\n \"created_at\": \"2025-02-01T00:00:00.000000Z\",\n \"updated_at\": \"2025-02-01T00:00:00.000000Z\",\n \"address\": {\n \"address_line_1\": \"No 39, Jalan Desa 1/1\",\n \"address_line_2\": \"Taman Desa\",\n \"city\": \"Petaling Jaya\",\n \"state\": \"Selangor\",\n \"country\": \"Malaysia\",\n \"postal_code\": \"58100\",\n \"full_address\": \"No 39, Jalan Desa 1/1, Taman Desa, Petaling Jaya, Selangor, 58100, Malaysia\",\n }\n },\n \"message\": \"Customer updated successfully\"\n}" 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'Customer not found' properties: message: type: string example: 'Customer not found' 422: description: 'Validation Error' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: email: - 'The email address is already taken.' phone_number: - 'The phone number is already taken.' source: - 'The selected source is invalid.' status: - 'The selected status is invalid.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: email: type: array example: - 'The email address is already taken.' items: type: string phone_number: type: array example: - 'The phone number is already taken.' items: type: string source: type: array example: - 'The selected source is invalid.' items: type: string status: type: array example: - 'The selected status is invalid.' items: type: string tags: - Customers requestBody: required: false content: application/json: schema: type: object properties: name: type: string description: "optional The customer's full name." example: 'John Doe' email: type: string description: 'optional A valid email address.' example: john@example.com nullable: true phone_number: type: string description: 'optional Phone number with country code.' example: '+60123456789' gender: type: integer|null description: "optional Gender (0: Female, 1: Male). If present, must be one of:\n- 0 (Female)\n- 1 (Male)" example: '1' nullable: true date_of_birth: type: date description: "optional The customer's birthdate (Y-m-d format)." example: '1990-01-01' nullable: true space_id: type: integer description: 'optional The ID of the space this customer belongs to.' example: 1 nullable: true source: type: string description: "optional Source of the customer. Must be one of:\n- Email\n- Phone\n- Direct\n- Reservation\n- WhatsApp\n- StoreHub\n- Loyverse\n- Softinn\n- Loyalty" example: WhatsApp status: type: string description: "optional Customer's status. Must be one of:\n- Lead\n- Open\n- Replied\n- Opportunity\n- Quotation\n- Lost Quotation\n- Interested\n- Converted\n- Do Not Contact\n- Blocked" example: Lead tier_id: type: string description: 'The ID of the tier assigned to this customer. Must be a valid tier from your organisation.' example: 1 nullable: true is_manually_assign_tier: type: boolean description: 'Whether the tier was manually assigned to the customer.' example: true tags: type: array description: 'optional Array of tag IDs to sync with the customer. Will remove any existing tags not in the array.' example: - VIP - 'Frequent Visitor' items: type: string notes: type: string description: 'optional Additional notes about the customer.' example: 'Prefers evening appointments' nullable: true address: type: object description: '' example: [] properties: address_line_1: type: string description: 'optional The first line of the address.' example: 'No 39, Jalan Desa 1/1' nullable: true address_line_2: type: string description: 'optional The second line of the address.' example: 'Taman Desa' nullable: true country: type: string description: 'optional|required_with:address.address_line_1 The country (Malaysia or Singapore).' example: Malaysia state: type: string description: 'optional|required_with:address.address_line_1 The state name.' example: Selangor city: type: string description: 'optional|required_with:address.address_line_1 The city name.' example: 'Petaling Jaya' postal_code: type: string description: 'optional|required_with:address.address_line_1 5-digit postal code.' example: '58100' delete: summary: 'Delete Customer' operationId: deleteCustomer description: "Queues a customer for deletion.
\nThe deletion process runs asynchronously.
\nAll related data will be deleted including rewards, transactions, and media (if any)." parameters: [] responses: 202: description: Accepted content: application/json: schema: type: object example: message: 'Customer deletion has been queued and will be processed shortly' properties: message: type: string example: 'Customer deletion has been queued and will be processed shortly' 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'Record not found' properties: message: type: string example: 'Record not found' tags: - Customers parameters: - in: path name: id description: 'The ID of the customer to delete.' example: 1 required: true schema: type: integer /api/customers/bulk-import: post: summary: 'Bulk Import Customers' operationId: bulkImportCustomers description: "Import multiple customers asynchronously.
\nThe operation is queued and processed in chunks of 100 records.
\nLimited to 1,000 customers per request and rate limited to 10 requests per minute." parameters: [] responses: 202: description: '' content: application/json: schema: type: object example: message: 'Bulk customer import has been queued and will be processed shortly' properties: message: type: string example: 'Bulk customer import has been queued and will be processed shortly' 422: description: '' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: customers: - 'The customers must not have more than 1000 items.' 'customers.*.email': - 'The customers.0.email has already been taken.' 'customers.*.phone_number': - 'The customers.0.phone_number must be a valid phone number.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: customers: type: array example: - 'The customers must not have more than 1000 items.' items: type: string 'customers.*.email': type: array example: - 'The customers.0.email has already been taken.' items: type: string 'customers.*.phone_number': type: array example: - 'The customers.0.phone_number must be a valid phone number.' items: type: string 429: description: 'Too Many Requests' content: application/json: schema: type: object example: message: 'Too Many Attempts.' properties: message: type: string example: 'Too Many Attempts.' tags: - Customers requestBody: required: true content: application/json: schema: type: object properties: customers: type: array description: 'Array of customers to import (max 1,000). Must not have more than 1000 items.' example: - name: 'John Doe' email: john@example.com phone_number: '+60123456789' gender: 1 date_of_birth: '1990-01-01' source: WhatsApp status: Lead notes: 'VIP customer' tags: - VIP - WhatsApp items: type: object properties: name: type: string description: 'Full name of the customer. Must not be greater than 255 characters.' example: 'John Doe' email: type: string description: 'Email address (must be unique per organisation). Must be a valid email address.' example: john@example.com nullable: true phone_number: type: string description: 'Phone number with country code (must be unique per organisation).' example: '+60123456789' gender: type: string description: 'Gender (0: Female, 1: Male).' example: '1' enum: - 0 - 1 nullable: true date_of_birth: type: string description: 'Date of birth in Y-m-d format. Must be a valid date.' example: '1990-01-01' nullable: true source: type: string description: 'Customer source. Must be one of: Email, Phone, Direct, Reservation, WhatsApp, StoreHub, Loyverse, Softinn, WooCommerce, Shopify, Loyalty, Zeoniq, Bukku, IvendPos, LemonPos, PosPal.' example: WhatsApp enum: - Email - Phone - Direct - Reservation - WhatsApp - StoreHub - Loyverse - Softinn - GeniusPos - WooCommerce - Shopify - Loyalty - Zeoniq - Bukku - IvendPos - LemonPos - PosPal - QnE nullable: true status: type: string description: 'Customer status. Must be one of: Lead, Open, Replied, Opportunity, Quotation, Lost Quotation, Interested, Converted, Do Not Contact, Blocked.' example: Lead enum: - Lead - Open - Replied - Opportunity - Quotation - 'Lost Quotation' - Interested - Converted - 'Do Not Contact' - Blocked nullable: true notes: type: string description: 'Additional notes about the customer.' example: 'VIP customer' nullable: true tags: type: array description: '' example: - animi items: type: string required: - name - phone_number - tags space_id: type: string description: 'The ID of the space to assign all customers to. The id of an existing record in the spaces table.' example: 1 nullable: true required: - customers /api/organisations: get: summary: 'List Organisations' operationId: listOrganisations description: "Returns a paginated list of organisations. Vendors see only their own organisation.\nSuper admins with \"View Any Organisation\" permission see all organisations." parameters: - in: query name: 'filter[name]' description: 'Filter by organisation name (partial match).' example: Acme required: false schema: type: string description: 'Filter by organisation name (partial match).' example: Acme - in: query name: sort description: 'Sort field. Allowed: id, name, created_at.' example: '-id' required: false schema: type: string description: 'Sort field. Allowed: id, name, created_at.' example: '-id' - in: query name: per_page description: 'Records per page.' example: 15 required: false schema: type: integer description: 'Records per page.' example: 15 responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 name: 'Acme Corp' friendly_name: Acme email: hello@acme.com phone_number: '+60123456789' pos_provider: null ecommerce_provider: null features: loyalty: true credits: false tiering: false ecommerce_integration: false created_at: '2024-01-01T00:00:00.000000Z' updated_at: '2024-01-01T00:00:00.000000Z' links: first: ... last: ... prev: null next: null meta: current_page: 1 from: 1 last_page: 1 per_page: 15 to: 1 total: 1 properties: data: type: array example: - id: 1 name: 'Acme Corp' friendly_name: Acme email: hello@acme.com phone_number: '+60123456789' pos_provider: null ecommerce_provider: null features: loyalty: true credits: false tiering: false ecommerce_integration: false created_at: '2024-01-01T00:00:00.000000Z' updated_at: '2024-01-01T00:00:00.000000Z' items: type: object properties: id: type: integer example: 1 name: type: string example: 'Acme Corp' friendly_name: type: string example: Acme email: type: string example: hello@acme.com phone_number: type: string example: '+60123456789' pos_provider: type: string example: null nullable: true ecommerce_provider: type: string example: null nullable: true features: type: object properties: loyalty: type: boolean example: true credits: type: boolean example: false tiering: type: boolean example: false ecommerce_integration: type: boolean example: false created_at: type: string example: '2024-01-01T00:00:00.000000Z' updated_at: type: string example: '2024-01-01T00:00:00.000000Z' links: type: object properties: first: type: string example: ... last: type: string example: ... prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 1 per_page: type: integer example: 15 to: type: integer example: 1 total: type: integer example: 1 tags: - Organisations '/api/organisations/{id}': get: summary: 'Show Organisation' operationId: showOrganisation description: '' parameters: [] responses: 200: description: Success content: application/json: schema: type: object example: data: id: 1 name: 'Acme Corp' features: loyalty: true credits: false tiering: false ecommerce_integration: false properties: data: type: object properties: id: type: integer example: 1 name: type: string example: 'Acme Corp' features: type: object properties: loyalty: type: boolean example: true credits: type: boolean example: false tiering: type: boolean example: false ecommerce_integration: type: boolean example: false 403: description: Forbidden content: application/json: schema: type: object example: message: 'This action is unauthorized.' properties: message: type: string example: 'This action is unauthorized.' tags: - Organisations put: summary: 'Update Organisation' operationId: updateOrganisation description: '' parameters: [] responses: 200: description: Success content: application/json: schema: type: object example: data: id: 1 name: 'Updated Name' message: 'Organisation updated successfully' properties: data: type: object properties: id: type: integer example: 1 name: type: string example: 'Updated Name' message: type: string example: 'Organisation updated successfully' 422: description: 'Validation Error' content: application/json: schema: type: object example: message: ... errors: { } properties: message: type: string example: ... errors: type: object properties: { } tags: - Organisations requestBody: required: false content: application/json: schema: type: object properties: name: type: string description: 'The name of the organisation. Must not be greater than 255 characters.' example: 'Acme Corp' friendly_name: type: string description: 'A short display name for the organisation. Must not be greater than 255 characters.' example: Acme nullable: true email: type: string description: 'Contact email address for the organisation. Must be a valid email address.' example: hello@acme.com phone_number: type: string description: 'Contact phone number for the organisation. Must not be greater than 50 characters.' example: '+60123456789' parameters: - in: path name: id description: 'The organisation ID.' example: 1 required: true schema: type: integer '/api/organisations/{organisation_id}/subscriptions': get: summary: 'List Organisation Subscriptions' operationId: listOrganisationSubscriptions description: "Returns a paginated list of Stripe subscriptions for the specified organisation.\nOnly the organisation's own members can access this endpoint." parameters: - in: query name: per_page description: 'Records per page.' example: 15 required: false schema: type: integer description: 'Records per page.' example: 15 responses: 200: description: '' content: application/json: schema: oneOf: - description: Success type: object example: data: - id: 1 type: default stripe_id: sub_1ABC stripe_status: active stripe_price: price_1ABC quantity: 1 trial_ends_at: null ends_at: null created_at: '2024-01-01T00:00:00.000000Z' updated_at: '2024-01-01T00:00:00.000000Z' links: first: ... last: ... prev: null next: null meta: current_page: 1 total: 1 properties: data: type: array example: - id: 1 type: default stripe_id: sub_1ABC stripe_status: active stripe_price: price_1ABC quantity: 1 trial_ends_at: null ends_at: null created_at: '2024-01-01T00:00:00.000000Z' updated_at: '2024-01-01T00:00:00.000000Z' items: type: object properties: id: type: integer example: 1 type: type: string example: default stripe_id: type: string example: sub_1ABC stripe_status: type: string example: active stripe_price: type: string example: price_1ABC quantity: type: integer example: 1 trial_ends_at: type: string example: null nullable: true ends_at: type: string example: null nullable: true created_at: type: string example: '2024-01-01T00:00:00.000000Z' updated_at: type: string example: '2024-01-01T00:00:00.000000Z' links: type: object properties: first: type: string example: ... last: type: string example: ... prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: current_page: type: integer example: 1 total: type: integer example: 1 - description: '' type: object example: data: - id: 1 type: default stripe_id: sub_1ABC stripe_status: active stripe_price: price_1ABC quantity: 1 trial_ends_at: null ends_at: null created_at: '2024-01-01T00:00:00.000000Z' updated_at: '2024-01-01T00:00:00.000000Z' links: first: ... last: ... prev: null next: null meta: current_page: 1 from: 1 last_page: 1 per_page: 15 to: 1 total: 1 properties: data: type: array example: - id: 1 type: default stripe_id: sub_1ABC stripe_status: active stripe_price: price_1ABC quantity: 1 trial_ends_at: null ends_at: null created_at: '2024-01-01T00:00:00.000000Z' updated_at: '2024-01-01T00:00:00.000000Z' items: type: object properties: id: type: integer example: 1 type: type: string example: default stripe_id: type: string example: sub_1ABC stripe_status: type: string example: active stripe_price: type: string example: price_1ABC quantity: type: integer example: 1 trial_ends_at: type: string example: null nullable: true ends_at: type: string example: null nullable: true created_at: type: string example: '2024-01-01T00:00:00.000000Z' updated_at: type: string example: '2024-01-01T00:00:00.000000Z' links: type: object properties: first: type: string example: ... last: type: string example: ... prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 1 per_page: type: integer example: 15 to: type: integer example: 1 total: type: integer example: 1 403: description: Forbidden content: application/json: schema: type: object example: message: 'This action is unauthorized.' properties: message: type: string example: 'This action is unauthorized.' tags: - Organisations parameters: - in: path name: organisation_id description: 'The organisation ID.' example: 1 required: true schema: type: integer /api/posCredit: get: summary: 'List POS credits' operationId: listPOSCredits description: '' parameters: - in: query name: 'filter[status]' description: 'Filter by status (`pending`, `adopted`).' example: adopted required: false schema: type: string description: 'Filter by status (`pending`, `adopted`).' example: adopted - in: query name: 'filter[space_id]' description: 'Filter by space ID.' example: 1 required: false schema: type: integer description: 'Filter by space ID.' example: 1 - in: query name: 'filter[ref_id]' description: 'Filter by ref_id.' example: REF-123 required: false schema: type: string description: 'Filter by ref_id.' example: REF-123 - in: query name: 'filter[transaction_timestamp]' description: 'Filter by date (YYYY-MM-DD).' example: '2025-05-06' required: false schema: type: string description: 'Filter by date (YYYY-MM-DD).' example: '2025-05-06' - in: query name: 'filter[amount]' description: 'Filter by amount with operators.' example: '>50' required: false schema: type: string description: 'Filter by amount with operators.' example: '>50' - in: query name: sort description: 'Sort field. Allowed: transaction_timestamp, amount, created_at.' example: '-created_at' required: false schema: type: string description: 'Sort field. Allowed: transaction_timestamp, amount, created_at.' example: '-created_at' - in: query name: include description: 'Relationships to include (space).' example: space required: false schema: type: string description: 'Relationships to include (space).' example: space - in: query name: per_page description: 'Records per page.' example: 15 required: false schema: type: integer description: 'Records per page.' example: 15 responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 ref_id: CREDIT-12345 transaction_timestamp: '2025-05-06T14:30:00.000000Z' amount: '50.00' status: adopted pos_provider: eats365 organisation_id: 1 space_id: 1 credit_id: 101 void_credit_id: null payload: terminal: T001 created_at: '2025-05-06T14:30:01.000000Z' updated_at: '2025-05-06T14:30:01.000000Z' links: first: ... last: ... prev: null next: null meta: current_page: 1 from: 1 last_page: 1 per_page: 15 to: 1 total: 1 properties: data: type: array example: - id: 1 ref_id: CREDIT-12345 transaction_timestamp: '2025-05-06T14:30:00.000000Z' amount: '50.00' status: adopted pos_provider: eats365 organisation_id: 1 space_id: 1 credit_id: 101 void_credit_id: null payload: terminal: T001 created_at: '2025-05-06T14:30:01.000000Z' updated_at: '2025-05-06T14:30:01.000000Z' items: type: object properties: id: type: integer example: 1 ref_id: type: string example: CREDIT-12345 transaction_timestamp: type: string example: '2025-05-06T14:30:00.000000Z' amount: type: string example: '50.00' status: type: string example: adopted pos_provider: type: string example: eats365 organisation_id: type: integer example: 1 space_id: type: integer example: 1 credit_id: type: integer example: 101 void_credit_id: type: string example: null nullable: true payload: type: object properties: terminal: type: string example: T001 created_at: type: string example: '2025-05-06T14:30:01.000000Z' updated_at: type: string example: '2025-05-06T14:30:01.000000Z' links: type: object properties: first: type: string example: ... last: type: string example: ... prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 1 per_page: type: integer example: 15 to: type: integer example: 1 total: type: integer example: 1 403: description: 'POS integration not enabled' content: application/json: schema: type: object example: message: 'This action is unauthorized.' properties: message: type: string example: 'This action is unauthorized.' tags: - 'POS Credits' post: summary: 'Create POS Credit' operationId: createPOSCredit description: "Ingests a POS credit record. If a customer is identified (via customer_id or phone_number),\na credit wallet entry is created and the POS credit status is set to **adopted**.\nWithout customer identification the record is stored as **pending** for later adoption.\n\n- Positive amounts create a `Top Up` credit.\n- Negative amounts create a `Refund` credit." parameters: [] responses: 201: description: '' content: application/json: schema: oneOf: - description: 'Adopted (with customer)' type: object example: data: id: 1 ref_id: CREDIT-12345 transaction_timestamp: '2025-05-06T14:30:00.000000Z' amount: '50.00' status: adopted pos_provider: eats365 organisation_id: 1 space_id: 1 credit_id: 101 void_credit_id: null payload: terminal: T001 created_at: '2025-05-06T14:30:01.000000Z' updated_at: '2025-05-06T14:30:01.000000Z' properties: data: type: object properties: id: type: integer example: 1 ref_id: type: string example: CREDIT-12345 transaction_timestamp: type: string example: '2025-05-06T14:30:00.000000Z' amount: type: string example: '50.00' status: type: string example: adopted pos_provider: type: string example: eats365 organisation_id: type: integer example: 1 space_id: type: integer example: 1 credit_id: type: integer example: 101 void_credit_id: type: string example: null nullable: true payload: type: object properties: terminal: type: string example: T001 created_at: type: string example: '2025-05-06T14:30:01.000000Z' updated_at: type: string example: '2025-05-06T14:30:01.000000Z' - description: 'Pending (no customer)' type: object example: data: id: 2 ref_id: CREDIT-99999 status: pending credit_id: null void_credit_id: null properties: data: type: object properties: id: type: integer example: 2 ref_id: type: string example: CREDIT-99999 status: type: string example: pending credit_id: type: string example: null nullable: true void_credit_id: type: string example: null nullable: true 403: description: 'POS integration not enabled' content: application/json: schema: type: object example: message: 'This action is unauthorized.' properties: message: type: string example: 'This action is unauthorized.' 422: description: 'Duplicate ref_id' content: application/json: schema: type: object example: message: 'The ref id has already been taken.' errors: ref_id: - 'The ref id has already been taken.' properties: message: type: string example: 'The ref id has already been taken.' errors: type: object properties: ref_id: type: array example: - 'The ref id has already been taken.' items: type: string tags: - 'POS Credits' requestBody: required: true content: application/json: schema: type: object properties: ref_id: type: string description: 'A unique reference ID for this POS credit transaction. Must be unique within the organisation.' example: REF-20250506-001 transaction_timestamp: type: string description: 'The date and time when the transaction occurred at the POS terminal. Must be a valid date.' example: '2025-05-06T14:30:00Z' amount: type: integer description: 'Transaction amount in cents. Positive for top-ups, negative for refunds.' example: 5000 payload: type: string description: 'Raw payload from the POS system. Can be a JSON string or object.' example: receipt_no: RCP-001 terminal_id: T01 space_id: type: integer description: 'The ID of the space (outlet) where this credit transaction occurred. Must belong to the authenticated organisation. The id of an existing record in the spaces table.' example: 1 customer_id: type: integer description: 'The ID of the customer to link this credit to. Optional — credit will be auto-adopted if provided. The id of an existing record in the customers table.' example: 42 phone_number: type: string description: 'Customer phone number as fallback for identification. Will be normalized to E.164 format.' example: '+60123456789' remarks: type: string description: 'Optional remarks for this POS credit transaction. Max 500 characters. Must not be greater than 500 characters.' example: 'Top-up via Eats365 POS' required: - ref_id - transaction_timestamp - amount - payload - space_id '/api/posCredit/{posCredit_id}/void': post: summary: 'Void POS Credit' operationId: voidPOSCredit description: "Voids an adopted POS credit record by creating a deduct entry in the customer's credit wallet.\n\n**Idempotency:** Voiding an already-voided POS credit returns success without creating a duplicate entry." parameters: [] responses: 200: description: '' content: application/json: schema: oneOf: - description: 'Voided successfully' type: object example: message: 'POS credit voided successfully.' data: id: 202 customer_id: 42 amount: 50 operation: '-' type: Deduct remarks: 'Voided POS credit #1' created_at: '2025-05-06T15:00:00.000000Z' properties: message: type: string example: 'POS credit voided successfully.' data: type: object properties: id: type: integer example: 202 customer_id: type: integer example: 42 amount: type: integer example: 50 operation: type: string example: '-' type: type: string example: Deduct remarks: type: string example: 'Voided POS credit #1' created_at: type: string example: '2025-05-06T15:00:00.000000Z' - description: 'Already voided (idempotent)' type: object example: message: 'This transaction has already been voided.' data: null properties: message: type: string example: 'This transaction has already been voided.' data: type: string example: null nullable: true - description: 'Not adopted, nothing to void' type: object example: message: 'Transaction not adopted, nothing to void.' data: null properties: message: type: string example: 'Transaction not adopted, nothing to void.' data: type: string example: null nullable: true 403: description: Unauthorized content: application/json: schema: type: object example: message: 'This action is unauthorized.' properties: message: type: string example: 'This action is unauthorized.' 404: description: 'POS credit not found' content: application/json: schema: type: object example: message: 'Record not found.' properties: message: type: string example: 'Record not found.' 422: description: 'customer_id required' content: application/json: schema: type: object example: message: 'A customer ID is required to void this POS credit.' properties: message: type: string example: 'A customer ID is required to void this POS credit.' tags: - 'POS Credits' requestBody: required: false content: application/json: schema: type: object properties: customer_id: type: integer description: 'The ID of the customer. Required only when the POS credit has no linked customer. Must belong to the authenticated organisation. The id of an existing record in the customers table.' example: 42 remarks: type: string description: 'Optional remarks for this void operation. Max 500 characters. Must not be greater than 500 characters.' example: 'Voided per customer request' parameters: - in: path name: posCredit_id description: 'The ID of the posCredit.' example: 1 required: true schema: type: integer - in: path name: posCredit description: 'The ID of the POS credit to void.' example: 1 required: true schema: type: integer /api/posTransaction: get: summary: 'List POS transactions' operationId: listPOSTransactions description: "Returns a paginated list of POS transactions for the authenticated organisation.\nResults can be filtered, sorted and paginated." parameters: - in: query name: 'filter[status]' description: 'Filter by transaction status.' example: adopted required: false schema: type: string description: 'Filter by transaction status.' example: adopted - in: query name: 'filter[space_id]' description: 'Filter by space UUID.' example: '1' required: false schema: type: string description: 'Filter by space UUID.' example: '1' - in: query name: 'filter[transaction_time]' description: 'Filter by transaction date (Y-m-d format).' example: '2025-05-06' required: false schema: type: string description: 'Filter by transaction date (Y-m-d format).' example: '2025-05-06' - in: query name: 'filter[amount_min]' description: 'Filter by minimum amount.' example: 50.0 required: false schema: type: number description: 'Filter by minimum amount.' example: 50.0 - in: query name: 'filter[amount_max]' description: 'Filter by maximum amount.' example: 500.0 required: false schema: type: number description: 'Filter by maximum amount.' example: 500.0 - in: query name: 'filter[ref_id]' description: 'Filter by reference ID.' example: TRX-123 required: false schema: type: string description: 'Filter by reference ID.' example: TRX-123 - in: query name: sort description: 'Sort by column (prefixed with - for descending). Available options: transaction_time, amount, created_at.' example: '-transaction_time' required: false schema: type: string description: 'Sort by column (prefixed with - for descending). Available options: transaction_time, amount, created_at.' example: '-transaction_time' - in: query name: include description: 'Include related resources. Available options: space.' example: space required: false schema: type: string description: 'Include related resources. Available options: space.' example: space - in: query name: page description: 'Page number.' example: 1 required: false schema: type: integer description: 'Page number.' example: 1 - in: query name: per_page description: 'Items per page (max 100).' example: 15 required: false schema: type: integer description: 'Items per page (max 100).' example: 15 responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 ref_id: TRX-12345678 transaction_time: '2025-05-06T14:30:00Z' amount: 150.0 status: adopted pos_provider: provider_name organisation_id: 1 created_at: '2025-05-06T14:35:00Z' updated_at: '2025-05-06T14:35:00Z' space: id: '1' name: 'Main Store' slug: main-store links: first: '@{{$baseUrl}}/pos-transactions?page=1' last: '@{{$baseUrl}}/pos-transactions?page=5' prev: null next: '@{{$baseUrl}}/pos-transactions?page=2' meta: current_page: 1 from: 1 last_page: 5 path: '@{{$baseUrl}}/pos-transactions' per_page: 15 to: 15 total: 75 properties: data: type: array example: - id: 1 ref_id: TRX-12345678 transaction_time: '2025-05-06T14:30:00Z' amount: 150 status: adopted pos_provider: provider_name organisation_id: 1 created_at: '2025-05-06T14:35:00Z' updated_at: '2025-05-06T14:35:00Z' space: id: '1' name: 'Main Store' slug: main-store items: type: object properties: id: type: integer example: 1 ref_id: type: string example: TRX-12345678 transaction_time: type: string example: '2025-05-06T14:30:00Z' amount: type: number example: 150.0 status: type: string example: adopted pos_provider: type: string example: provider_name organisation_id: type: integer example: 1 created_at: type: string example: '2025-05-06T14:35:00Z' updated_at: type: string example: '2025-05-06T14:35:00Z' space: type: object properties: id: type: string example: '1' name: type: string example: 'Main Store' slug: type: string example: main-store links: type: object properties: first: type: string example: '@{{$baseUrl}}/pos-transactions?page=1' last: type: string example: '@{{$baseUrl}}/pos-transactions?page=5' prev: type: string example: null nullable: true next: type: string example: '@{{$baseUrl}}/pos-transactions?page=2' meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 5 path: type: string example: '@{{$baseUrl}}/pos-transactions' per_page: type: integer example: 15 to: type: integer example: 15 total: type: integer example: 75 tags: - 'POS Transaction' post: summary: 'Create a new POS transaction' operationId: createANewPOSTransaction description: "This endpoint creates a new POS transaction in the system. If a customer identifier (customer_id or phone_number)\nis provided, the transaction will be automatically adopted and a corresponding transaction record will be created for the customer.\n\n**Partial Refunds:** To process a partial refund, send a negative amount. Optionally provide the original_ref_id\nof the transaction being refunded. The refund will deduct proportional points from the customer's balance.\n\n**Blind Refunds:** If no original_ref_id is provided with a negative amount (blind refund), points will be\ndeducted using the formula: refund_amount × tier_multiplier (or 1:1 if customer has no tier)." parameters: [] responses: 201: description: '' content: application/json: schema: oneOf: - description: Success type: object example: data: id: 1 ref_id: TRX-12345678 transaction_time: '2025-05-06T14:30:00Z' amount: 150.0 status: pending pos_provider: provider_name space_id: '1' organisation_id: 1 created_at: '2025-05-06T14:35:00Z' updated_at: '2025-05-06T14:35:00Z' properties: data: type: object properties: id: type: integer example: 1 ref_id: type: string example: TRX-12345678 transaction_time: type: string example: '2025-05-06T14:30:00Z' amount: type: number example: 150.0 status: type: string example: pending pos_provider: type: string example: provider_name space_id: type: string example: '1' organisation_id: type: integer example: 1 created_at: type: string example: '2025-05-06T14:35:00Z' updated_at: type: string example: '2025-05-06T14:35:00Z' - description: 'With customer adoption' type: object example: data: id: 1 ref_id: TRX-12345678 transaction_time: '2025-05-06T14:30:00Z' amount: 150.0 status: adopted pos_provider: provider_name space_id: '1' organisation_id: 1 created_at: '2025-05-06T14:35:00Z' updated_at: '2025-05-06T14:35:00Z' properties: data: type: object properties: id: type: integer example: 1 ref_id: type: string example: TRX-12345678 transaction_time: type: string example: '2025-05-06T14:30:00Z' amount: type: number example: 150.0 status: type: string example: adopted pos_provider: type: string example: provider_name space_id: type: string example: '1' organisation_id: type: integer example: 1 created_at: type: string example: '2025-05-06T14:35:00Z' updated_at: type: string example: '2025-05-06T14:35:00Z' - description: 'Partial refund' type: object example: data: id: 2 ref_id: TRX-REFUND-001 transaction_time: '2025-05-06T15:00:00Z' amount: -50.0 status: adopted pos_provider: provider_name space_id: '1' organisation_id: 1 is_refund: true original_ref_id: TRX-12345678 refund_details: original_amount: 150.0 refund_amount: 50.0 points_deducted: 50 created_at: '2025-05-06T15:00:00Z' updated_at: '2025-05-06T15:00:00Z' properties: data: type: object properties: id: type: integer example: 2 ref_id: type: string example: TRX-REFUND-001 transaction_time: type: string example: '2025-05-06T15:00:00Z' amount: type: number example: -50.0 status: type: string example: adopted pos_provider: type: string example: provider_name space_id: type: string example: '1' organisation_id: type: integer example: 1 is_refund: type: boolean example: true original_ref_id: type: string example: TRX-12345678 refund_details: type: object properties: original_amount: type: number example: 150.0 refund_amount: type: number example: 50.0 points_deducted: type: integer example: 50 created_at: type: string example: '2025-05-06T15:00:00Z' updated_at: type: string example: '2025-05-06T15:00:00Z' - description: 'Blind refund (no original_ref_id)' type: object example: data: id: 3 ref_id: TRX-BLIND-REFUND-001 transaction_time: '2025-05-06T15:00:00Z' amount: -50.0 status: adopted pos_provider: provider_name space_id: '1' organisation_id: 1 is_refund: true original_ref_id: null refund_details: original_amount: null refund_amount: 50.0 points_deducted: 75 created_at: '2025-05-06T15:00:00Z' updated_at: '2025-05-06T15:00:00Z' properties: data: type: object properties: id: type: integer example: 3 ref_id: type: string example: TRX-BLIND-REFUND-001 transaction_time: type: string example: '2025-05-06T15:00:00Z' amount: type: number example: -50.0 status: type: string example: adopted pos_provider: type: string example: provider_name space_id: type: string example: '1' organisation_id: type: integer example: 1 is_refund: type: boolean example: true original_ref_id: type: string example: null nullable: true refund_details: type: object properties: original_amount: type: string example: null nullable: true refund_amount: type: number example: 50.0 points_deducted: type: integer example: 75 created_at: type: string example: '2025-05-06T15:00:00Z' updated_at: type: string example: '2025-05-06T15:00:00Z' 403: description: 'POS integration not enabled' content: application/json: schema: type: object example: message: 'POS integration not enabled for this organisation' properties: message: type: string example: 'POS integration not enabled for this organisation' 422: description: 'Validation error' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: ref_id: - 'The ref id has already been taken.' amount: - 'The refund amount exceeds the refundable balance.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: ref_id: type: array example: - 'The ref id has already been taken.' items: type: string amount: type: array example: - 'The refund amount exceeds the refundable balance.' items: type: string tags: - 'POS Transaction' requestBody: required: true content: application/json: schema: type: object properties: space_id: type: string description: 'The UUID of the space where the transaction occurred. Must belong to the authenticated organisation.' example: '1' nullable: true ref_id: type: string description: 'A unique reference ID for the transaction. Must be unique across all POS transactions.' example: TRX-12345678 transaction_time: type: datetime description: 'The date and time when the transaction occurred. Must be a valid date format.' example: '2025-05-06T14:30:00Z' amount: type: integer description: 'The transaction amount in cents. Positive for purchases, negative for partial refunds.' example: 15000 payload: type: json description: 'Additional transaction details in JSON format.' example: '{"items": [{"name": "Product A", "quantity": 2, "price": 7500}], "payment_method": "credit_card", "terminal_id": "T001"}' customer_id: type: integer description: 'The ID of the customer associated with this transaction. Must exist in the customers table.' example: 42 nullable: true phone_number: type: string description: "The customer's phone number in E.164 format." example: '+60123456789' nullable: true original_ref_id: type: string description: 'The ref_id of the original transaction being refunded. Optional for refunds - if provided, points are deducted proportionally; if not provided (blind refund), points = refund_amount × tier_multiplier.' example: TRX-ORIGINAL-001 nullable: true remarks: type: string description: 'Free-form text describing the purchase items or refund reason. Max 500 characters.' example: '2x Coffee, 1x Sandwich' nullable: true required: - ref_id - transaction_time - amount - payload '/api/posTransaction/{posTransaction_id}/void': post: summary: 'Void POS Transaction' operationId: voidPOSTransaction description: "Voids an adopted POS transaction and deducts the exact points that were earned.\nThis generic endpoint works for all POS providers.\n\nThe void operation uses the linked transaction_id to deduct the exact amount of points\nthat were earned, accounting for tier multipliers. For example:\n- Customer with 2x Gold tier earns 200 points on $100 purchase\n- Voiding deducts exactly 200 points (not 100)\n\n**Idempotency:** Attempting to void the same transaction multiple times will return success\nbut only create one void transaction." parameters: [] responses: 200: description: '' content: application/json: schema: oneOf: - description: '' type: object example: message: 'POS transaction voided successfully.' data: id: 789 customer_id: 456 amount: 200 operation: '-' remarks: 'Transaction voided from POS System (Ref: REF-12345)' created_at: '2025-10-25T14:30:00Z' properties: message: type: string example: 'POS transaction voided successfully.' data: type: object properties: id: type: integer example: 789 customer_id: type: integer example: 456 amount: type: integer example: 200 operation: type: string example: '-' remarks: type: string example: 'Transaction voided from POS System (Ref: REF-12345)' created_at: type: string example: '2025-10-25T14:30:00Z' - description: '' type: object example: message: 'Transaction not adopted, nothing to void.' data: null properties: message: type: string example: 'Transaction not adopted, nothing to void.' data: type: string example: null nullable: true - description: '' type: object example: message: 'This transaction has already been voided.' data: null properties: message: type: string example: 'This transaction has already been voided.' data: type: string example: null nullable: true 403: description: '' content: application/json: schema: type: object example: message: 'This action is unauthorized.' properties: message: type: string example: 'This action is unauthorized.' 404: description: '' content: application/json: schema: type: object example: message: 'POS Transaction not found.' properties: message: type: string example: 'POS Transaction not found.' 422: description: '' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: customer_id: - 'The selected customer does not exist in your organisation.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: customer_id: type: array example: - 'The selected customer does not exist in your organisation.' items: type: string tags: - 'POS Transaction' requestBody: required: true content: application/json: schema: type: object properties: customer_id: type: integer description: 'The ID of the customer who earned the points.' example: 456 remarks: type: string description: 'Optional custom remarks for the void transaction.' example: '"Transaction voided - customer request"' nullable: true required: - customer_id parameters: - in: path name: posTransaction_id description: 'The ID of the posTransaction.' example: 1140884 required: true schema: type: integer - in: path name: posTransaction description: 'The ID of the POS transaction to void.' example: 123 required: true schema: type: integer /api/paid_membership_types: get: summary: 'List Paid Membership Types' operationId: listPaidMembershipTypes description: "Returns a paginated list of paid membership type catalog entries belonging to the\nauthenticated user's organisation. Supports filtering and sorting via query parameters." parameters: - in: query name: 'filter[is_active]' description: 'Filter by active status.' example: true required: false schema: type: boolean description: 'Filter by active status.' example: true - in: query name: 'filter[name]' description: 'Filter by membership type name (partial match).' example: Gold required: false schema: type: string description: 'Filter by membership type name (partial match).' example: Gold - in: query name: sort description: 'Sort field and direction. Prefix with `-` for descending. Available fields: `sort`, `name`, `created_at`. Defaults to `sort`.' example: '-created_at' required: false schema: type: string description: 'Sort field and direction. Prefix with `-` for descending. Available fields: `sort`, `name`, `created_at`. Defaults to `sort`.' example: '-created_at' - in: query name: per_page description: 'Number of records per page. Defaults to 15.' example: 15 required: false schema: type: integer description: 'Number of records per page. Defaults to 15.' example: 15 responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 organisation_id: 1 name: 'VIP Gold' description: 'Priority support and birthday rewards.' pricing_options: - cycle: monthly price: 3500 - cycle: yearly price: 35000 benefits: - icon: heroicon-o-star heading: 'Priority Support' description: 'Get priority customer support' sort: 1 is_active: true created_at: '2025-01-31T21:16:49.000000Z' updated_at: '2025-01-31T21:16:49.000000Z' links: first: ... last: ... prev: null next: null meta: current_page: 1 from: 1 last_page: 1 path: ... per_page: 15 to: 1 total: 1 properties: data: type: array example: - id: 1 organisation_id: 1 name: 'VIP Gold' description: 'Priority support and birthday rewards.' pricing_options: - cycle: monthly price: 3500 - cycle: yearly price: 35000 benefits: - icon: heroicon-o-star heading: 'Priority Support' description: 'Get priority customer support' sort: 1 is_active: true created_at: '2025-01-31T21:16:49.000000Z' updated_at: '2025-01-31T21:16:49.000000Z' items: type: object properties: id: type: integer example: 1 organisation_id: type: integer example: 1 name: type: string example: 'VIP Gold' description: type: string example: 'Priority support and birthday rewards.' pricing_options: type: array example: - cycle: monthly price: 3500 - cycle: yearly price: 35000 items: type: object properties: cycle: type: string example: monthly price: type: integer example: 3500 benefits: type: array example: - icon: heroicon-o-star heading: 'Priority Support' description: 'Get priority customer support' items: type: object properties: icon: type: string example: heroicon-o-star heading: type: string example: 'Priority Support' description: type: string example: 'Get priority customer support' sort: type: integer example: 1 is_active: type: boolean example: true created_at: type: string example: '2025-01-31T21:16:49.000000Z' updated_at: type: string example: '2025-01-31T21:16:49.000000Z' links: type: object properties: first: type: string example: ... last: type: string example: ... prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 1 path: type: string example: ... per_page: type: integer example: 15 to: type: integer example: 1 total: type: integer example: 1 tags: - 'Paid Membership Types' '/api/paid_membership_types/{id}': get: summary: 'Show Paid Membership Type' operationId: showPaidMembershipType description: 'Retrieves a single paid membership type by ID. Cross-organisation access returns 404.' parameters: [] responses: 200: description: Success content: application/json: schema: type: object example: data: id: 1 organisation_id: 1 name: 'VIP Gold' description: 'Priority support and birthday rewards.' pricing_options: - cycle: monthly price: 3500 benefits: [] sort: 1 is_active: true created_at: '2025-01-31T21:16:49.000000Z' updated_at: '2025-01-31T21:16:49.000000Z' properties: data: type: object properties: id: type: integer example: 1 organisation_id: type: integer example: 1 name: type: string example: 'VIP Gold' description: type: string example: 'Priority support and birthday rewards.' pricing_options: type: array example: - cycle: monthly price: 3500 items: type: object properties: cycle: type: string example: monthly price: type: integer example: 3500 benefits: type: array example: [] sort: type: integer example: 1 is_active: type: boolean example: true created_at: type: string example: '2025-01-31T21:16:49.000000Z' updated_at: type: string example: '2025-01-31T21:16:49.000000Z' 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'Record not found' properties: message: type: string example: 'Record not found' tags: - 'Paid Membership Types' parameters: - in: path name: id description: 'The ID of the paid membership type.' example: 1 required: true schema: type: integer - in: path name: paid_membership_type description: 'The ID of the paid membership type.' example: 1 required: true schema: type: integer /api/payments: get: summary: 'List Payments' operationId: listPayments description: "Get a paginated list of payments and refunds belonging to the authenticated\nuser's organisation. Results can be filtered, sorted and expanded with related\ndata through query parameters." parameters: - in: query name: 'filter[type]' description: 'Filter by record type. Available values: `payment`, `refund`.' example: payment required: false schema: type: string description: 'Filter by record type. Available values: `payment`, `refund`.' example: payment - in: query name: 'filter[status]' description: 'Filter by payment status code (e.g. 0 = pending, 1 = success, 2 = failed).' example: 1 required: false schema: type: integer description: 'Filter by payment status code (e.g. 0 = pending, 1 = success, 2 = failed).' example: 1 - in: query name: 'filter[method]' description: 'Filter by payment method (e.g. `commerce_pay`, `manual`, `credit`).' example: commerce_pay required: false schema: type: string description: 'Filter by payment method (e.g. `commerce_pay`, `manual`, `credit`).' example: commerce_pay - in: query name: 'filter[payable_type]' description: 'Filter by the fully-qualified class of what was paid for.' example: App\Models\Reservation required: false schema: type: string description: 'Filter by the fully-qualified class of what was paid for.' example: App\Models\Reservation - in: query name: 'filter[created_at]' description: 'Filter by creation date with operators.' example: '>2024-01-01' required: false schema: type: string description: 'Filter by creation date with operators.' example: '>2024-01-01' - in: query name: 'filter[completed_at]' description: 'Filter by completion date with operators.' example: '>2024-01-01' required: false schema: type: string description: 'Filter by completion date with operators.' example: '>2024-01-01' - in: query name: sort description: 'Sort field. Allowed: created_at, completed_at, amount. Use - for descending.' example: '-created_at' required: false schema: type: string description: 'Sort field. Allowed: created_at, completed_at, amount. Use - for descending.' example: '-created_at' - in: query name: include description: 'Comma-separated relationships to include. Allowed: refunds, refundingPayment.' example: refunds required: false schema: type: string description: 'Comma-separated relationships to include. Allowed: refunds, refundingPayment.' example: refunds - in: query name: per_page description: 'Number of records per page.' example: 15 required: false schema: type: integer description: 'Number of records per page.' example: 15 responses: 200: description: '' content: application/json: schema: oneOf: - description: 'List of payments' type: object example: data: - id: 1 type: payment method: commerce_pay reference_id: TXN-1234567890 amount: 150.0 currency_code: MYR status: 1 is_manual: false payment_type: null source: null attempted_at: '2025-01-01T00:00:00+00:00' completed_at: '2025-01-01T00:01:00+00:00' failed_at: null failure_reason: null created_at: '2025-01-01T00:00:00+00:00' links: first: ... last: ... prev: null next: null meta: current_page: 1 per_page: 15 total: 1 properties: data: type: array example: - id: 1 type: payment method: commerce_pay reference_id: TXN-1234567890 amount: 150 currency_code: MYR status: 1 is_manual: false payment_type: null source: null attempted_at: '2025-01-01T00:00:00+00:00' completed_at: '2025-01-01T00:01:00+00:00' failed_at: null failure_reason: null created_at: '2025-01-01T00:00:00+00:00' items: type: object properties: id: type: integer example: 1 type: type: string example: payment method: type: string example: commerce_pay reference_id: type: string example: TXN-1234567890 amount: type: number example: 150.0 currency_code: type: string example: MYR status: type: integer example: 1 is_manual: type: boolean example: false payment_type: type: string example: null nullable: true source: type: string example: null nullable: true attempted_at: type: string example: '2025-01-01T00:00:00+00:00' completed_at: type: string example: '2025-01-01T00:01:00+00:00' failed_at: type: string example: null nullable: true failure_reason: type: string example: null nullable: true created_at: type: string example: '2025-01-01T00:00:00+00:00' links: type: object properties: first: type: string example: ... last: type: string example: ... prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: current_page: type: integer example: 1 per_page: type: integer example: 15 total: type: integer example: 1 - description: 'No payments found' type: object example: data: [] links: first: ... last: ... prev: null next: null meta: current_page: 1 per_page: 15 total: 0 properties: data: type: array example: [] links: type: object properties: first: type: string example: ... last: type: string example: ... prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: current_page: type: integer example: 1 per_page: type: integer example: 15 total: type: integer example: 0 401: description: Unauthenticated content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. 403: description: Unauthorized content: application/json: schema: type: object example: message: 'This action is unauthorized.' properties: message: type: string example: 'This action is unauthorized.' tags: - Payments '/api/payments/{id}': get: summary: 'Show Payment' operationId: showPayment description: 'Retrieve detailed information about a specific payment or refund.' parameters: - in: query name: include description: 'Comma-separated relationships to include. Allowed: refunds, refundingPayment.' example: refunds required: false schema: type: string description: 'Comma-separated relationships to include. Allowed: refunds, refundingPayment.' example: refunds responses: 200: description: Success content: application/json: schema: type: object example: data: id: 1 type: payment method: commerce_pay reference_id: TXN-1234567890 amount: 150.0 currency_code: MYR status: 1 is_manual: false payment_type: null source: null attempted_at: '2025-01-01T00:00:00+00:00' completed_at: '2025-01-01T00:01:00+00:00' failed_at: null failure_reason: null created_at: '2025-01-01T00:00:00+00:00' properties: data: type: object properties: id: type: integer example: 1 type: type: string example: payment method: type: string example: commerce_pay reference_id: type: string example: TXN-1234567890 amount: type: number example: 150.0 currency_code: type: string example: MYR status: type: integer example: 1 is_manual: type: boolean example: false payment_type: type: string example: null nullable: true source: type: string example: null nullable: true attempted_at: type: string example: '2025-01-01T00:00:00+00:00' completed_at: type: string example: '2025-01-01T00:01:00+00:00' failed_at: type: string example: null nullable: true failure_reason: type: string example: null nullable: true created_at: type: string example: '2025-01-01T00:00:00+00:00' 401: description: Unauthenticated content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. 403: description: Unauthorized content: application/json: schema: type: object example: message: 'This action is unauthorized.' properties: message: type: string example: 'This action is unauthorized.' 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'Record not found.' properties: message: type: string example: 'Record not found.' tags: - Payments parameters: - in: path name: id description: 'The ID of the payment.' example: 1 required: true schema: type: integer - in: path name: payment description: 'The ID of the payment.' example: 1 required: true schema: type: integer /api/plans: get: summary: 'List Plans' operationId: listPlans description: 'Returns a paginated list of active subscription plans, including their prices.' parameters: - in: query name: 'filter[name]' description: 'Filter by plan name (partial match).' example: Premium required: false schema: type: string description: 'Filter by plan name (partial match).' example: Premium - in: query name: sort description: 'Sort field. Allowed: id, name, created_at.' example: '-id' required: false schema: type: string description: 'Sort field. Allowed: id, name, created_at.' example: '-id' - in: query name: per_page description: 'Records per page.' example: 15 required: false schema: type: integer description: 'Records per page.' example: 15 responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 name: Premium description: 'Full access to all features' is_active: true features: loyalty_enabled: true credits_enabled: true tiering_enabled: true max_spaces: 3 max_customers: 10000 prices: - id: 1 currency: MYR billing_period: monthly amount: 350 stripe_price_id: price_1ABC is_active: true description: null created_at: '2024-01-01T00:00:00.000000Z' updated_at: '2024-01-01T00:00:00.000000Z' links: first: ... last: ... prev: null next: null meta: current_page: 1 from: 1 last_page: 1 per_page: 15 to: 1 total: 1 properties: data: type: array example: - id: 1 name: Premium description: 'Full access to all features' is_active: true features: loyalty_enabled: true credits_enabled: true tiering_enabled: true max_spaces: 3 max_customers: 10000 prices: - id: 1 currency: MYR billing_period: monthly amount: 350 stripe_price_id: price_1ABC is_active: true description: null created_at: '2024-01-01T00:00:00.000000Z' updated_at: '2024-01-01T00:00:00.000000Z' items: type: object properties: id: type: integer example: 1 name: type: string example: Premium description: type: string example: 'Full access to all features' is_active: type: boolean example: true features: type: object properties: loyalty_enabled: type: boolean example: true credits_enabled: type: boolean example: true tiering_enabled: type: boolean example: true max_spaces: type: integer example: 3 max_customers: type: integer example: 10000 prices: type: array example: - id: 1 currency: MYR billing_period: monthly amount: 350 stripe_price_id: price_1ABC is_active: true description: null items: type: object properties: id: type: integer example: 1 currency: type: string example: MYR billing_period: type: string example: monthly amount: type: integer example: 350 stripe_price_id: type: string example: price_1ABC is_active: type: boolean example: true description: type: string example: null nullable: true created_at: type: string example: '2024-01-01T00:00:00.000000Z' updated_at: type: string example: '2024-01-01T00:00:00.000000Z' links: type: object properties: first: type: string example: ... last: type: string example: ... prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 1 per_page: type: integer example: 15 to: type: integer example: 1 total: type: integer example: 1 tags: - Plans '/api/plans/{id}': get: summary: 'Show Plan' operationId: showPlan description: '' parameters: [] responses: 200: description: Success content: application/json: schema: type: object example: data: id: 1 name: Premium features: loyalty_enabled: true prices: [] properties: data: type: object properties: id: type: integer example: 1 name: type: string example: Premium features: type: object properties: loyalty_enabled: type: boolean example: true prices: type: array example: [] 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'No query results for model [Plan] 1' properties: message: type: string example: 'No query results for model [Plan] 1' tags: - Plans parameters: - in: path name: id description: 'The plan ID.' example: 1 required: true schema: type: integer /api/customer_rewards/validate: post: summary: 'Validate Reward Code' operationId: validateRewardCode description: "Validates a 6-digit reward redemption code and returns validation status along with complete reward details.\nThis endpoint is designed for cashiers and POS systems to verify reward codes before processing redemptions.\n\nThe validation process checks:\n- Code format (must be exactly 6 digits)\n- Code existence in the system\n- Code expiration status\n- Whether the code has already been used\n- Organisation-level multi-tenancy\n\nThe response includes the full reward object with `properties` field containing POS integration settings.\n**For iVend POS:** Check `reward.properties.ivend.enabled` to determine if the reward can be processed.\nUse `reward.properties.ivend.amount` to apply the discount amount." parameters: [] responses: 200: description: 'Valid Code' content: application/json: schema: type: object example: valid: true message: 'Code is valid' data: id: 101 status: Pending code: '123456' expired_at: '2024-12-31T23:59:59.000000Z' used_at: null customer_id: 42 reward_id: 5 customer: id: 42 name: 'John Doe' phone_number: '+601234567890' email: john@example.com current_point: 2500 current_credits: 150 reward: id: 5 name: 'Free Coffee' description: 'Complimentary coffee of your choice' terms: 'Valid at all outlets. One redemption per visit.' is_active: true start_at: null end_at: null valid_days: 30 amount: 100 point_is_active: true credit_is_active: false credit_amount: null credit_earn_point: false available_currency_types: - point allow_customer_self_reward: true is_one_time_reward: false is_direct_link_accessible: false is_redeemable: true is_custom_valid_days: true is_limit_total_availability: false total_availability: null total_redeemed: 15 cover: gift automations: [] notification_settings: { } properties: ivend: enabled: true type: amount amount: 5 created_at: '2024-01-15T08:30:00.000000Z' updated_at: '2024-02-10T10:45:00.000000Z' properties: valid: type: boolean example: true message: type: string example: 'Code is valid' data: type: object properties: id: type: integer example: 101 status: type: string example: Pending code: type: string example: '123456' expired_at: type: string example: '2024-12-31T23:59:59.000000Z' used_at: type: string example: null nullable: true customer_id: type: integer example: 42 reward_id: type: integer example: 5 customer: type: object properties: id: type: integer example: 42 name: type: string example: 'John Doe' phone_number: type: string example: '+601234567890' email: type: string example: john@example.com current_point: type: integer example: 2500 current_credits: type: integer example: 150 reward: type: object properties: id: type: integer example: 5 name: type: string example: 'Free Coffee' description: type: string example: 'Complimentary coffee of your choice' terms: type: string example: 'Valid at all outlets. One redemption per visit.' is_active: type: boolean example: true start_at: type: string example: null nullable: true end_at: type: string example: null nullable: true valid_days: type: integer example: 30 amount: type: integer example: 100 point_is_active: type: boolean example: true credit_is_active: type: boolean example: false credit_amount: type: string example: null nullable: true credit_earn_point: type: boolean example: false available_currency_types: type: array example: - point items: type: string allow_customer_self_reward: type: boolean example: true is_one_time_reward: type: boolean example: false is_direct_link_accessible: type: boolean example: false is_redeemable: type: boolean example: true is_custom_valid_days: type: boolean example: true is_limit_total_availability: type: boolean example: false total_availability: type: string example: null nullable: true total_redeemed: type: integer example: 15 cover: type: string example: gift automations: type: array example: [] notification_settings: type: object properties: { } properties: type: object properties: ivend: type: object properties: enabled: type: boolean example: true type: type: string example: amount amount: type: integer example: 5 created_at: type: string example: '2024-01-15T08:30:00.000000Z' updated_at: type: string example: '2024-02-10T10:45:00.000000Z' 404: description: 'Organisation Not Found' content: application/json: schema: type: object example: message: 'Organisation not found' properties: message: type: string example: 'Organisation not found' 422: description: '' content: application/json: schema: oneOf: - description: 'Code Expired' type: object example: valid: false message: 'Code has expired' error_code: CODE_EXPIRED properties: valid: type: boolean example: false message: type: string example: 'Code has expired' error_code: type: string example: CODE_EXPIRED - description: 'Code Already Used' type: object example: valid: false message: 'Code has already been used' error_code: CODE_ALREADY_USED properties: valid: type: boolean example: false message: type: string example: 'Code has already been used' error_code: type: string example: CODE_ALREADY_USED - description: 'Invalid Code' type: object example: valid: false message: 'Invalid redemption code' error_code: CODE_NOT_FOUND properties: valid: type: boolean example: false message: type: string example: 'Invalid redemption code' error_code: type: string example: CODE_NOT_FOUND - description: 'Invalid Format' type: object example: valid: false message: 'Code must be exactly 6 digits' error_code: INVALID_FORMAT properties: valid: type: boolean example: false message: type: string example: 'Code must be exactly 6 digits' error_code: type: string example: INVALID_FORMAT - description: 'Validation Error' type: object example: message: 'The given data was invalid.' errors: code: - 'Code must be exactly 6 digits.' organisation_id: - 'Organisation ID is required.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: code: type: array example: - 'Code must be exactly 6 digits.' items: type: string organisation_id: type: array example: - 'Organisation ID is required.' items: type: string tags: - 'Reward Validation' requestBody: required: true content: application/json: schema: type: object properties: code: type: string description: 'The 6-digit reward redemption code to validate. Must be exactly 6 digits.' example: '123456' organisation_id: type: integer description: 'The ID of the organisation the validation is being performed for. Must be a valid organisation ID.' example: 123 required: - code - organisation_id /api/rewards: get: summary: 'List Rewards' operationId: listRewards description: "Returns a paginated list of rewards belonging to the authenticated user's organisation spaces.
\nSupports filtering, sorting and relationship inclusion through query parameters." parameters: - in: query name: include description: "Comma-separated list of relationships to include. Available relationships:\n- space\n- customerRewards\n- rewardVouchers\n- inviterReferralItem\n- inviteeReferralItem\n- rewardBundleItems\n- pendingRewardVouchers" example: 'space,customerRewards' required: false schema: type: string description: "Comma-separated list of relationships to include. Available relationships:\n- space\n- customerRewards\n- rewardVouchers\n- inviterReferralItem\n- inviteeReferralItem\n- rewardBundleItems\n- pendingRewardVouchers" example: 'space,customerRewards' - in: query name: 'filter[space_id]' description: 'Filter by space ID.' example: 1 required: false schema: type: integer description: 'Filter by space ID.' example: 1 - in: query name: 'filter[is_active]' description: 'Filter by active status.' example: true required: false schema: type: boolean description: 'Filter by active status.' example: true - in: query name: 'filter[is_redeemable]' description: 'Filter by redeemable status.' example: true required: false schema: type: boolean description: 'Filter by redeemable status.' example: true - in: query name: 'filter[is_one_time_reward]' description: 'Filter by one-time reward status.' example: false required: false schema: type: boolean description: 'Filter by one-time reward status.' example: false - in: query name: 'filter[allow_customer_self_reward]' description: 'Filter by self-reward permission.' example: true required: false schema: type: boolean description: 'Filter by self-reward permission.' example: true - in: query name: 'filter[point_is_active]' description: 'Filter by point redemption availability.' example: true required: false schema: type: boolean description: 'Filter by point redemption availability.' example: true - in: query name: 'filter[credit_is_active]' description: 'Filter by credit redemption availability.' example: true required: false schema: type: boolean description: 'Filter by credit redemption availability.' example: true - in: query name: 'filter[available]' description: 'Filter by availability (considers active status and date range).' example: true required: false schema: type: boolean description: 'Filter by availability (considers active status and date range).' example: true - in: query name: 'filter[name]' description: 'Filter by reward name (partial match).' example: Birthday required: false schema: type: string description: 'Filter by reward name (partial match).' example: Birthday - in: query name: sort description: "Sort field and direction. Note: Prefix with `-` for descending order.\nAvailable sort fields:\n- name\n- start_at\n- end_at\n- created_at\n- amount" example: '-created_at' required: false schema: type: string description: "Sort field and direction. Note: Prefix with `-` for descending order.\nAvailable sort fields:\n- name\n- start_at\n- end_at\n- created_at\n- amount" example: '-created_at' - in: query name: per_page description: 'Number of records per page. Defaults to 15.' example: 15 required: false schema: type: integer description: 'Number of records per page. Defaults to 15.' example: 15 responses: 200: description: Success content: application/json: schema: type: object example: data: - id: 1 name: 'Birthday Reward' description: 'Special reward for customer birthdays' terms: 'Valid for 30 days from issue' is_active: true start_at: '2025-01-01T00:00:00.000000Z' end_at: '2025-12-31T23:59:59.000000Z' valid_days: 30 amount: 1000 allow_customer_self_reward: false is_one_time_reward: true is_direct_link_accessible: false is_redeemable: true is_custom_valid_days: true is_limit_total_availability: true total_availability: 100 total_redeemed: 0 cover: gift automations: [] notification_settings: new_reward: is_enabled: true message: "You've received a birthday reward!" properties: ivend: enabled: true type: amount amount: 10 created_at: '2025-01-31T21:16:49.000000Z' updated_at: '2025-01-31T21:16:49.000000Z' links: first: '@{{$baseUrl}}/api/rewards?page=1' last: '@{{$baseUrl}}/api/rewards?page=1' prev: null next: null meta: current_page: 1 from: 1 last_page: 1 path: '@{{$baseUrl}}/api/rewards' per_page: 15 to: 1 total: 1 properties: data: type: array example: - id: 1 name: 'Birthday Reward' description: 'Special reward for customer birthdays' terms: 'Valid for 30 days from issue' is_active: true start_at: '2025-01-01T00:00:00.000000Z' end_at: '2025-12-31T23:59:59.000000Z' valid_days: 30 amount: 1000 allow_customer_self_reward: false is_one_time_reward: true is_direct_link_accessible: false is_redeemable: true is_custom_valid_days: true is_limit_total_availability: true total_availability: 100 total_redeemed: 0 cover: gift automations: [] notification_settings: new_reward: is_enabled: true message: "You've received a birthday reward!" properties: ivend: enabled: true type: amount amount: 10 created_at: '2025-01-31T21:16:49.000000Z' updated_at: '2025-01-31T21:16:49.000000Z' items: type: object properties: id: type: integer example: 1 name: type: string example: 'Birthday Reward' description: type: string example: 'Special reward for customer birthdays' terms: type: string example: 'Valid for 30 days from issue' is_active: type: boolean example: true start_at: type: string example: '2025-01-01T00:00:00.000000Z' end_at: type: string example: '2025-12-31T23:59:59.000000Z' valid_days: type: integer example: 30 amount: type: integer example: 1000 allow_customer_self_reward: type: boolean example: false is_one_time_reward: type: boolean example: true is_direct_link_accessible: type: boolean example: false is_redeemable: type: boolean example: true is_custom_valid_days: type: boolean example: true is_limit_total_availability: type: boolean example: true total_availability: type: integer example: 100 total_redeemed: type: integer example: 0 cover: type: string example: gift automations: type: array example: [] notification_settings: type: object properties: new_reward: type: object properties: is_enabled: type: boolean example: true message: type: string example: "You've received a birthday reward!" properties: type: object properties: ivend: type: object properties: enabled: type: boolean example: true type: type: string example: amount amount: type: integer example: 10 created_at: type: string example: '2025-01-31T21:16:49.000000Z' updated_at: type: string example: '2025-01-31T21:16:49.000000Z' required: - cover links: type: object properties: first: type: string example: '@{{$baseUrl}}/api/rewards?page=1' last: type: string example: '@{{$baseUrl}}/api/rewards?page=1' prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 1 path: type: string example: '@{{$baseUrl}}/api/rewards' per_page: type: integer example: 15 to: type: integer example: 1 total: type: integer example: 1 tags: - Rewards post: summary: 'Create Reward' operationId: createReward description: "Creates a new reward in the system.
\nThe reward will be associated with the specified space in the authenticated user's organisation." parameters: [] responses: 201: description: 'Created successfully' content: application/json: schema: type: object example: data: id: 1 name: 'Birthday Reward' description: 'Special reward for customer birthdays' terms: 'Valid for 30 days from issue' is_active: true start_at: '2025-01-01T00:00:00.000000Z' end_at: '2025-12-31T23:59:59.000000Z' valid_days: 30 amount: 1000 allow_customer_self_reward: false is_one_time_reward: true is_direct_link_accessible: false is_redeemable: true is_custom_valid_days: true is_limit_total_availability: true total_availability: 100 total_redeemed: 0 cover: gift automations: [] notification_settings: new_reward: is_enabled: true message: "You've received a birthday reward!" properties: ivend: enabled: true type: amount amount: 10 created_at: '2025-01-31T21:16:49.000000Z' updated_at: '2025-01-31T21:16:49.000000Z' message: 'Reward created successfully' properties: data: type: object properties: id: type: integer example: 1 name: type: string example: 'Birthday Reward' description: type: string example: 'Special reward for customer birthdays' terms: type: string example: 'Valid for 30 days from issue' is_active: type: boolean example: true start_at: type: string example: '2025-01-01T00:00:00.000000Z' end_at: type: string example: '2025-12-31T23:59:59.000000Z' valid_days: type: integer example: 30 amount: type: integer example: 1000 allow_customer_self_reward: type: boolean example: false is_one_time_reward: type: boolean example: true is_direct_link_accessible: type: boolean example: false is_redeemable: type: boolean example: true is_custom_valid_days: type: boolean example: true is_limit_total_availability: type: boolean example: true total_availability: type: integer example: 100 total_redeemed: type: integer example: 0 cover: type: string example: gift automations: type: array example: [] notification_settings: type: object properties: new_reward: type: object properties: is_enabled: type: boolean example: true message: type: string example: "You've received a birthday reward!" properties: type: object properties: ivend: type: object properties: enabled: type: boolean example: true type: type: string example: amount amount: type: integer example: 10 created_at: type: string example: '2025-01-31T21:16:49.000000Z' updated_at: type: string example: '2025-01-31T21:16:49.000000Z' message: type: string example: 'Reward created successfully' 422: description: 'Validation Error' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: name: - 'The name field is required.' space_id: - 'The selected space id is invalid.' amount: - 'The amount must be at least 0.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: name: type: array example: - 'The name field is required.' items: type: string space_id: type: array example: - 'The selected space id is invalid.' items: type: string amount: type: array example: - 'The amount must be at least 0.' items: type: string tags: - Rewards requestBody: required: true content: multipart/form-data: schema: type: object properties: name: type: string description: "The reward's name." example: 'Birthday Reward' description: type: string description: 'Description of the reward.' example: 'Special reward for customer birthdays' terms: type: string description: 'Terms and conditions for the reward.' example: 'Valid for 30 days from issue' space_id: type: integer description: "The ID of the space this reward belongs to. Must be from user's organisation." example: 1 is_active: type: boolean description: 'Whether the reward is active. Default: true.' example: true start_at: type: datetime description: 'nullable Start date of the reward availability.' example: '2025-01-01' nullable: true end_at: type: datetime description: 'nullable required_without:valid_days End date of the reward availability.' example: '2025-12-31' nullable: true is_redeemable: type: boolean description: 'Whether the reward can be redeemed. Default: true.' example: true is_custom_valid_days: type: boolean description: 'Whether to use custom validity period.' example: true valid_days: type: integer description: 'nullable required_without:end_at Number of days the reward is valid after issuance.' example: 30 nullable: true amount: type: integer description: 'Value/points for the reward.' example: 1000 point_is_active: type: boolean description: 'Whether point-based redemption is enabled for this reward.' example: true credit_is_active: type: boolean description: 'Whether credit-based redemption is enabled for this reward.' example: true credit_amount: type: numeric description: 'Amount of credits required to redeem this reward (in currency units).' example: '10.50' nullable: true credit_earn_point: type: boolean description: 'Whether customers earn points when redeeming this reward with credits.' example: false allow_customer_self_reward: type: boolean description: 'Whether customers can claim the reward themselves.' example: true is_one_time_reward: type: boolean description: 'Whether the reward can only be claimed once per customer.' example: true is_direct_link_accessible: type: boolean description: 'Whether the reward can be accessed via direct link.' example: false is_limit_total_availability: type: boolean description: 'Whether to limit total number of rewards.' example: true total_availability: type: integer description: 'required_if:is_limit_total_availability,true Maximum number of rewards available.' example: 100 cover: type: string description: 'Cover preset type. Must be one of: discount, gift, voucher, custom.' example: gift custom_cover: type: string format: binary description: 'required_if:cover.preset,custom Square image file (1:1 ratio).' automations: type: array description: 'optional Automation settings for the reward.' example: null items: type: string nullable: true notification_settings: type: object description: 'optional Notification settings for various reward events.' example: [] properties: new_reward: type: object description: 'Notification settings for new rewards.' example: [] properties: is_enabled: type: boolean description: 'Whether to send notifications for new rewards.' example: false message: type: string description: "Template message for new reward notification.\nAvailable variables:\n- @{{NAME}} Customer's name\n- @{{SPACE_NAME}} Space name\n- @{{REWARD_NAME}} Reward name\n- @{{REWARD_POINTS}} Points required for reward\n- @{{REWARD_EXPIRED}} Reward expiration date\n- @{{REWARD_REDEEM}} Reward redemption link/code" example: 'Hi @{{NAME}}, you have received a new reward from @{{SPACE_NAME}}!' expired_reward: type: object description: '' example: is_enabled: true message: 'Hi @{{NAME}}, your reward has expired.' properties: is_enabled: type: boolean description: '' example: true message: type: string description: "Template message for expired reward notification.\nAvailable variables:\n- @{{NAME}} Customer's name\n- @{{SPACE_NAME}} Space name\n- @{{REWARD_NAME}} Reward name\n- @{{REWARD_EXPIRED}} Reward expiration date" example: 'Hi @{{NAME}}, your reward from @{{SPACE_NAME}} has expired.' expiring_reward: type: object description: '' example: is_enabled: true message: 'Hi @{{NAME}}, your reward will expire soon!' send_notification_before: '7' properties: is_enabled: type: boolean description: '' example: true message: type: string description: "Template message for expiring reward notification.\nAvailable variables:\n- @{{NAME}} Customer's name\n- @{{SPACE_NAME}} Space name\n- @{{REWARD_NAME}} Reward name\n- @{{REWARD_POINTS}} Points required for reward\n- @{{REWARD_EXPIRED}} Reward expiration date\n- @{{REWARD_REDEEM}} Reward redemption link/code" example: 'Hi @{{NAME}}, your reward from @{{SPACE_NAME}} is expiring soon.' send_notification_before: type: string description: "Days before expiry to send notification. Must be one of:\n `10` - 10 days before\n `7` - 7 days before\n `5` - 5 days before\n `3` - 3 days before\n `2` - 2 days before\n `1` - 1 day before" example: facere nullable: true required: - name - description - terms - space_id - amount - cover '/api/rewards/{id}': get: summary: 'Show Reward Details' operationId: showRewardDetails description: 'Retrieves detailed information about a specific reward, including any requested relationships.' parameters: - in: query name: include description: "Comma-separated list of relationships to include. Available relationships:\n- space\n- customerRewards\n- rewardVouchers\n- inviterReferralItem\n- inviteeReferralItem\n- rewardBundleItems\n- pendingRewardVouchers" example: 'space,customerRewards' required: false schema: type: string description: "Comma-separated list of relationships to include. Available relationships:\n- space\n- customerRewards\n- rewardVouchers\n- inviterReferralItem\n- inviteeReferralItem\n- rewardBundleItems\n- pendingRewardVouchers" example: 'space,customerRewards' responses: 200: description: Success content: application/json: schema: type: object example: data: id: 1 name: 'Birthday Reward' description: 'Special reward for customer birthdays' terms: 'Valid for 30 days from issue' is_active: true start_at: '2025-01-01T00:00:00.000000Z' end_at: '2025-12-31T23:59:59.000000Z' valid_days: 30 amount: 1000 allow_customer_self_reward: false is_one_time_reward: true is_direct_link_accessible: false is_redeemable: true is_custom_valid_days: true is_limit_total_availability: true total_availability: 100 total_redeemed: 0 cover: gift automations: [] notification_settings: new_reward: is_enabled: true message: "You've received a birthday reward!" properties: ivend: enabled: true type: amount amount: 10 created_at: '2025-01-31T21:16:49.000000Z' updated_at: '2025-01-31T21:16:49.000000Z' properties: data: type: object properties: id: type: integer example: 1 name: type: string example: 'Birthday Reward' description: type: string example: 'Special reward for customer birthdays' terms: type: string example: 'Valid for 30 days from issue' is_active: type: boolean example: true start_at: type: string example: '2025-01-01T00:00:00.000000Z' end_at: type: string example: '2025-12-31T23:59:59.000000Z' valid_days: type: integer example: 30 amount: type: integer example: 1000 allow_customer_self_reward: type: boolean example: false is_one_time_reward: type: boolean example: true is_direct_link_accessible: type: boolean example: false is_redeemable: type: boolean example: true is_custom_valid_days: type: boolean example: true is_limit_total_availability: type: boolean example: true total_availability: type: integer example: 100 total_redeemed: type: integer example: 0 cover: type: string example: gift automations: type: array example: [] notification_settings: type: object properties: new_reward: type: object properties: is_enabled: type: boolean example: true message: type: string example: "You've received a birthday reward!" properties: type: object properties: ivend: type: object properties: enabled: type: boolean example: true type: type: string example: amount amount: type: integer example: 10 created_at: type: string example: '2025-01-31T21:16:49.000000Z' updated_at: type: string example: '2025-01-31T21:16:49.000000Z' 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'Record not found' properties: message: type: string example: 'Record not found' tags: - Rewards put: summary: 'Update Reward' operationId: updateReward description: "Updates an existing reward's information.
\nAll fields are optional." parameters: [] responses: 200: description: Success content: application/json: schema: type: object example: data: id: 1 name: 'Birthday Reward' description: 'Special reward for customer birthdays' terms: 'Valid for 30 days from issue' is_active: true start_at: '2025-01-01T00:00:00.000000Z' end_at: '2025-12-31T23:59:59.000000Z' valid_days: 30 amount: 1000 allow_customer_self_reward: false is_one_time_reward: true is_direct_link_accessible: false is_redeemable: true is_custom_valid_days: true is_limit_total_availability: true total_availability: 100 total_redeemed: 0 cover: gift automations: [] notification_settings: new_reward: is_enabled: true message: "You've received a birthday reward!" properties: ivend: enabled: true type: amount amount: 10 created_at: '2025-01-31T21:16:49.000000Z' updated_at: '2025-01-31T21:16:49.000000Z' message: 'Reward updated successfully' properties: data: type: object properties: id: type: integer example: 1 name: type: string example: 'Birthday Reward' description: type: string example: 'Special reward for customer birthdays' terms: type: string example: 'Valid for 30 days from issue' is_active: type: boolean example: true start_at: type: string example: '2025-01-01T00:00:00.000000Z' end_at: type: string example: '2025-12-31T23:59:59.000000Z' valid_days: type: integer example: 30 amount: type: integer example: 1000 allow_customer_self_reward: type: boolean example: false is_one_time_reward: type: boolean example: true is_direct_link_accessible: type: boolean example: false is_redeemable: type: boolean example: true is_custom_valid_days: type: boolean example: true is_limit_total_availability: type: boolean example: true total_availability: type: integer example: 100 total_redeemed: type: integer example: 0 cover: type: string example: gift automations: type: array example: [] notification_settings: type: object properties: new_reward: type: object properties: is_enabled: type: boolean example: true message: type: string example: "You've received a birthday reward!" properties: type: object properties: ivend: type: object properties: enabled: type: boolean example: true type: type: string example: amount amount: type: integer example: 10 created_at: type: string example: '2025-01-31T21:16:49.000000Z' updated_at: type: string example: '2025-01-31T21:16:49.000000Z' message: type: string example: 'Reward updated successfully' 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'Record not found' properties: message: type: string example: 'Record not found' 422: description: 'Validation Error' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: name: - 'The name field is required.' space_id: - 'The selected space id is invalid.' amount: - 'The amount must be at least 0.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: name: type: array example: - 'The name field is required.' items: type: string space_id: type: array example: - 'The selected space id is invalid.' items: type: string amount: type: array example: - 'The amount must be at least 0.' items: type: string tags: - Rewards requestBody: required: true content: multipart/form-data: schema: type: object properties: name: type: string description: "The reward's name." example: 'Birthday Reward' description: type: string description: 'Description of the reward.' example: 'Special reward for customer birthdays' terms: type: string description: 'Terms and conditions for the reward.' example: 'Valid for 30 days from issue' space_id: type: integer description: "The ID of the space this reward belongs to. Must be from user's organisation." example: 1 is_active: type: boolean description: 'Whether the reward is active.' example: true start_at: type: datetime description: 'nullable Start date of the reward availability.' example: '2025-01-01' nullable: true end_at: type: datetime description: 'nullable required_without:valid_days End date of the reward availability.' example: '2025-12-31' nullable: true is_redeemable: type: boolean description: 'Whether the reward can be redeemed.' example: true is_custom_valid_days: type: boolean description: 'Whether to use custom validity period.' example: true valid_days: type: integer description: 'nullable required_without:end_at Number of days the reward is valid after issuance.' example: 30 nullable: true amount: type: integer description: 'Value/points for the reward.' example: 1000 point_is_active: type: boolean description: 'Whether point-based redemption is enabled for this reward.' example: true credit_is_active: type: boolean description: 'Whether credit-based redemption is enabled for this reward.' example: true credit_amount: type: numeric description: 'Amount of credits required to redeem this reward (in currency units).' example: '10.50' nullable: true credit_earn_point: type: boolean description: 'Whether customers earn points when redeeming this reward with credits.' example: false allow_customer_self_reward: type: boolean description: 'Whether customers can claim the reward themselves.' example: true is_one_time_reward: type: boolean description: 'Whether the reward can only be claimed once per customer.' example: true is_direct_link_accessible: type: boolean description: 'Whether the reward can be accessed via direct link.' example: false is_limit_total_availability: type: boolean description: 'Whether to limit total number of rewards.' example: true total_availability: type: integer description: 'required_if:is_limit_total_availability,true Maximum number of rewards available.' example: 100 cover: type: string description: 'Cover preset type. Must be one of: discount, gift, voucher, custom.' example: gift custom_cover: type: string format: binary description: 'Square image file (1:1 ratio).' automations: type: array description: 'Automation settings for the reward.' example: null items: type: string nullable: true notification_settings: type: object description: 'Notification settings for various reward events.' example: [] properties: new_reward: type: object description: 'Notification settings for new rewards.' example: [] properties: is_enabled: type: boolean description: 'Whether to send notifications for new rewards.' example: false message: type: string description: 'required_if:notification_settings.new_reward.is_enabled,true Message for new reward notification.' example: laboriosam expired_reward: type: object description: '' example: is_enabled: true message: 'Hi @{{NAME}}, your reward has expired.' properties: is_enabled: type: boolean description: '' example: true message: type: string description: 'This field is required when notification_settings.expired_reward.is_enabled is true.' example: 'Hi @{{NAME}}, your reward has expired.' expiring_reward: type: object description: '' example: is_enabled: true message: 'Hi @{{NAME}}, your reward will expire soon!' send_notification_before: '7' properties: is_enabled: type: boolean description: '' example: true message: type: string description: 'This field is required when notification_settings.expiring_reward.is_enabled is true.' example: 'Hi @{{NAME}}, your reward will expire soon!' send_notification_before: type: string description: 'This field is required when notification_settings.expiring_reward.is_enabled is true.' example: '7' enum: - 10 - 7 - 5 - 3 - 2 - 1 nullable: true required: - cover delete: summary: 'Delete Reward' operationId: deleteReward description: "Deletes a reward from the system.
\nOnly rewards that haven't been assigned to any customers can be deleted." parameters: [] responses: 204: description: 'No Content' content: application/json: schema: type: object example: { } properties: { } 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'Record not found' properties: message: type: string example: 'Record not found' 422: description: 'Cannot Delete' content: application/json: schema: type: object example: message: 'Cannot delete reward that has been assigned to customers' properties: message: type: string example: 'Cannot delete reward that has been assigned to customers' tags: - Rewards parameters: - in: path name: id description: 'The ID of the reward.' example: 1 required: true schema: type: integer '/api/rewards/{reward_id}/vouchers': get: summary: 'List reward vouchers' operationId: listRewardVouchers description: 'Get a paginated list of vouchers for a specific reward.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 code: REWARD123 status: available expired_at: '2024-12-31T23:59:59Z' given_at: null used_at: null customer_reward: customer: id: 1 name: 'John Doe' phone_number: '+1234567890' links: first: '@{{$baseUrl}}/api/rewards/@{{reward_id}}/vouchers?page=1' last: '@{{$baseUrl}}/api/rewards/@{{reward_id}}/vouchers?page=1' prev: null next: null meta: current_page: 1 last_page: 1 per_page: 15 total: 1 properties: data: type: array example: - id: 1 code: REWARD123 status: available expired_at: '2024-12-31T23:59:59Z' given_at: null used_at: null customer_reward: customer: id: 1 name: 'John Doe' phone_number: '+1234567890' items: type: object properties: id: type: integer example: 1 code: type: string example: REWARD123 status: type: string example: available expired_at: type: string example: '2024-12-31T23:59:59Z' given_at: type: string example: null nullable: true used_at: type: string example: null nullable: true customer_reward: type: object properties: customer: type: object properties: id: type: integer example: 1 name: type: string example: 'John Doe' phone_number: type: string example: '+1234567890' links: type: object properties: first: type: string example: '@{{$baseUrl}}/api/rewards/@{{reward_id}}/vouchers?page=1' last: type: string example: '@{{$baseUrl}}/api/rewards/@{{reward_id}}/vouchers?page=1' prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: current_page: type: integer example: 1 last_page: type: integer example: 1 per_page: type: integer example: 15 total: type: integer example: 1 404: description: 'Record not found' content: application/json: schema: type: object example: message: 'Record not found.' properties: message: type: string example: 'Record not found.' tags: - Rewards requestBody: required: false content: application/json: schema: type: object properties: status: type: string description: 'Filter vouchers by status.' example: Active enum: - Pending - Reserved - Active - Expired - Used nullable: true search: type: string description: 'Search vouchers by code or customer information.' example: REWARD123 nullable: true page: type: integer description: 'Page number for pagination. Must be at least 1.' example: 1 nullable: true per_page: type: integer description: 'Number of items per page (max: 100). Must be at least 1. Must not be greater than 100.' example: 15 nullable: true post: summary: 'Create reward voucher' operationId: createRewardVoucher description: "Creates a reward voucher for a specific reward. The voucher is initially created\nin a pending status, which means it has been generated but not yet assigned.\nThe voucher will remain in this state until it is given to a recipient\nor further processed." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: id: 1 code: REWARD123 status: available expired_at: '2024-12-31T23:59:59Z' given_at: null used_at: null properties: id: type: integer example: 1 description: 'The unique identifier of the reward voucher.' code: type: string example: REWARD123 description: 'The unique code for the voucher.' status: type: string example: available description: 'The current status of the voucher.' expired_at: type: string example: '2024-12-31T23:59:59Z' description: 'datetime|null The expiration date and time of the voucher.' given_at: type: string example: null description: 'datetime|null The date and time when the voucher was given to a customer.' used_at: type: string example: null description: 'datetime|null The date and time when the voucher was used.' tags: - Rewards requestBody: required: true content: application/json: schema: type: object properties: code: type: string description: 'Unique voucher code for the reward. Must not be greater than 255 characters.' example: REWARD123XYZ expired_at: type: string description: 'The date and time when the voucher expires. Must be a valid date. Must be a date after now.' example: '2024-12-31T23:59:59Z' required: - code - expired_at parameters: - in: path name: reward_id description: 'The ID of the reward.' example: 10 required: true schema: type: integer '/api/rewards/{reward_id}/vouchers/{id}': get: summary: 'Show reward voucher details' operationId: showRewardVoucherDetails description: "Retrieve detailed information about a specific reward voucher.\nThis endpoint allows fetching a single voucher with optional related resources." parameters: - in: query name: include description: "Optional related resources to include.\nPossible values: customerReward, customerReward.customer, reward" example: 'customerReward,reward' required: false schema: type: string description: "Optional related resources to include.\nPossible values: customerReward, customerReward.customer, reward" example: 'customerReward,reward' responses: 200: description: '' content: application/json: schema: type: object example: id: 1 code: REWARD123 status: available expired_at: '2024-12-31T23:59:59Z' given_at: null used_at: null customer_reward: id: 12 status: pending reward: id: 1 name: 'Loyalty Discount' description: '10% off next purchase' properties: id: type: integer example: 1 description: 'The unique identifier of the reward voucher.' code: type: string example: REWARD123 description: 'The unique code for the voucher.' status: type: string example: available description: "The current status of the voucher.\n Possible values: available, used, expired, pending." expired_at: type: string example: '2024-12-31T23:59:59Z' description: 'datetime|null The expiration date and time of the voucher.' given_at: type: string example: null description: 'datetime|null The date and time when the voucher was given to a customer.' used_at: type: string example: null description: 'datetime|null The date and time when the voucher was used.' customer_reward: type: object properties: id: type: integer example: 12 status: type: string example: pending description: 'Details of the customer reward associated with this voucher.' reward: type: object properties: id: type: integer example: 1 name: type: string example: 'Loyalty Discount' description: type: string example: '10% off next purchase' description: 'Details of the reward associated with this voucher.' 404: description: 'Record not found' content: application/json: schema: type: object example: message: 'Record not found.' properties: message: type: string example: 'Record not found.' tags: - Rewards put: summary: 'Update reward voucher status' operationId: updateRewardVoucherStatus description: 'Update the status of a specific voucher.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: id: 1 code: REWARD123 status: used expired_at: '2024-12-31T23:59:59Z' given_at: '2024-02-09T10:00:00Z' used_at: '2024-02-09T12:00:00Z' properties: id: type: integer example: 1 description: 'The unique identifier of the reward voucher.' code: type: string example: REWARD123 description: 'The unique code for the voucher.' status: type: string example: used description: 'The current status of the voucher.' expired_at: type: string example: '2024-12-31T23:59:59Z' description: 'datetime|null The expiration date and time of the voucher.' given_at: type: string example: '2024-02-09T10:00:00Z' description: 'datetime|null The date and time when the voucher was given to a customer.' used_at: type: string example: '2024-02-09T12:00:00Z' description: 'datetime|null The date and time when the voucher was used.' 404: description: 'Record not found' content: application/json: schema: type: object example: message: 'Record not found.' properties: message: type: string example: 'Record not found.' tags: - Rewards requestBody: required: true content: application/json: schema: type: object properties: status: type: string description: 'The new status for the voucher.' example: used enum: - Used - Expired required: - status delete: summary: 'Delete reward voucher' operationId: deleteRewardVoucher description: 'Delete a specific voucher.' parameters: [] responses: 204: description: '' content: application/json: schema: type: object example: { } properties: { } 404: description: 'Record not found' content: application/json: schema: type: object example: message: 'Record not found.' properties: message: type: string example: 'Record not found.' tags: - Rewards parameters: - in: path name: reward_id description: 'The unique identifier of the reward.' example: 1 required: true schema: type: integer - in: path name: id description: 'The unique identifier of the voucher.' example: 42 required: true schema: type: integer '/api/rewards/{reward_id}/vouchers/bulk-update': post: summary: 'Bulk Update Reward Vouchers' operationId: bulkUpdateRewardVouchers description: "Updates the status of multiple reward vouchers asynchronously. The operation is queued as a background job\nand processed in chunks of 100 records. This endpoint is rate limited to 10 requests per minute." parameters: [] responses: 202: description: '' content: application/json: schema: type: object example: message: 'Bulk voucher update has been queued and will be processed shortly' updated_count: 3 properties: message: type: string example: 'Bulk voucher update has been queued and will be processed shortly' updated_count: type: integer example: 3 404: description: 'Record not found' content: application/json: schema: type: object example: message: 'Record not found.' properties: message: type: string example: 'Record not found.' 422: description: '' content: application/json: schema: type: object example: message: 'Validation failed' errors: voucher_codes: - 'Either voucher_codes or voucher_ids must be provided.' properties: message: type: string example: 'Validation failed' errors: type: object properties: voucher_codes: type: array example: - 'Either voucher_codes or voucher_ids must be provided.' items: type: string 429: description: 'Too Many Requests' content: application/json: schema: type: object example: message: 'Too Many Attempts.' properties: message: type: string example: 'Too Many Attempts.' tags: - Rewards requestBody: required: true content: application/json: schema: type: object properties: voucher_codes: type: array description: 'A valid voucher code string. This field is required when voucher_codes is present. The code of an existing record in the reward_vouchers table.' example: - REWARD123 items: type: string voucher_ids: type: array description: 'A valid voucher ID integer. This field is required when voucher_ids is present. The id of an existing record in the reward_vouchers table.' example: - 1 items: type: integer status: type: string description: 'The new status to be applied to the selected vouchers.' example: Used enum: - Used - Expired required: - status parameters: - in: path name: reward_id description: 'The ID of the reward these vouchers belong to.' example: 1 required: true schema: type: integer '/api/rewards/{reward_id}/vouchers/bulk-import': post: summary: 'Bulk import vouchers' operationId: bulkImportVouchers description: "Import multiple vouchers for a reward asynchronously. The operation is queued and processed in chunks of 100 records.\nLimited to 10,000 vouchers per request and rate limited to 10 requests per minute." parameters: [] responses: 202: description: '' content: application/json: schema: type: object example: message: 'Bulk voucher import has been queued and will be processed shortly' properties: message: type: string example: 'Bulk voucher import has been queued and will be processed shortly' 404: description: 'Record not found' content: application/json: schema: type: object example: message: 'Record not found.' properties: message: type: string example: 'Record not found.' 422: description: '' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: vouchers: - 'The vouchers must not have more than 10000 items.' 'vouchers.*.code': - 'The vouchers.0.expired_at has already been taken.' 'vouchers.*.expired_at': - 'The vouchers.0.expired_at must be a date after now.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: vouchers: type: array example: - 'The vouchers must not have more than 10000 items.' items: type: string 'vouchers.*.code': type: array example: - 'The vouchers.0.expired_at has already been taken.' items: type: string 'vouchers.*.expired_at': type: array example: - 'The vouchers.0.expired_at must be a date after now.' items: type: string 429: description: 'Too Many Requests' content: application/json: schema: type: object example: message: 'Too Many Attempts.' properties: message: type: string example: 'Too Many Attempts.' tags: - Rewards requestBody: required: true content: application/json: schema: type: object properties: vouchers: type: array description: 'Array of vouchers to import (max 10,000).' example: - code: REWARD123 expired_at: '2024-12-31T23:59:59Z' items: type: string auto_reassign: type: boolean description: 'Whether to automatically trigger voucher reassignment to pending customer rewards after import completes. Defaults to false.' example: false required: - vouchers parameters: - in: path name: reward_id description: 'The ID of the reward.' example: 2 required: true schema: type: integer /api/roles: get: summary: 'List Roles' operationId: listRoles description: 'Returns a paginated list of all available roles.' parameters: - in: query name: 'filter[name]' description: 'Filter by role name (partial match).' example: Admin required: false schema: type: string description: 'Filter by role name (partial match).' example: Admin - in: query name: sort description: 'Sort field. Allowed: id, name, created_at.' example: '-id' required: false schema: type: string description: 'Sort field. Allowed: id, name, created_at.' example: '-id' - in: query name: per_page description: 'Records per page.' example: 15 required: false schema: type: integer description: 'Records per page.' example: 15 responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 name: 'Super Admin' guard_name: web created_at: '2024-01-01T00:00:00.000000Z' updated_at: '2024-01-01T00:00:00.000000Z' links: first: ... last: ... prev: null next: null meta: current_page: 1 from: 1 last_page: 1 per_page: 15 to: 1 total: 1 properties: data: type: array example: - id: 1 name: 'Super Admin' guard_name: web created_at: '2024-01-01T00:00:00.000000Z' updated_at: '2024-01-01T00:00:00.000000Z' items: type: object properties: id: type: integer example: 1 name: type: string example: 'Super Admin' guard_name: type: string example: web created_at: type: string example: '2024-01-01T00:00:00.000000Z' updated_at: type: string example: '2024-01-01T00:00:00.000000Z' links: type: object properties: first: type: string example: ... last: type: string example: ... prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 1 per_page: type: integer example: 15 to: type: integer example: 1 total: type: integer example: 1 tags: - Roles '/api/roles/{id}': get: summary: 'Show Role' operationId: showRole description: '' parameters: [] responses: 200: description: Success content: application/json: schema: type: object example: data: id: 1 name: Admin guard_name: web properties: data: type: object properties: id: type: integer example: 1 name: type: string example: Admin guard_name: type: string example: web 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'No query results for model [Role] 1' properties: message: type: string example: 'No query results for model [Role] 1' tags: - Roles parameters: - in: path name: id description: 'The role ID.' example: 1 required: true schema: type: integer /api/spaces: get: summary: 'List Spaces' operationId: listSpaces description: "Returns a paginated list of spaces belonging to the authenticated user's organisation.
\nSupports filtering, sorting and relationship inclusion through query parameters.
" parameters: - in: query name: include description: "Comma-separated list of relationships to include.\nAvailable relationships:\n- category (Space category details)\n- address (Physical address details)" example: 'category,address' required: false schema: type: string description: "Comma-separated list of relationships to include.\nAvailable relationships:\n- category (Space category details)\n- address (Physical address details)" example: 'category,address' - in: query name: 'filter[visibility]' description: "Filter by visibility status.\nAvailable options:\n- public (Listed and viewable by anyone)\n- private (Not listed, viewable with link)\n- hidden (Not listed, cannot be searched)\n- draft (Not listed, cannot be searched)" example: public required: false schema: type: string description: "Filter by visibility status.\nAvailable options:\n- public (Listed and viewable by anyone)\n- private (Not listed, viewable with link)\n- hidden (Not listed, cannot be searched)\n- draft (Not listed, cannot be searched)" example: public - in: query name: 'filter[category_id]' description: 'Filter by category ID.' example: 1 required: false schema: type: integer description: 'Filter by category ID.' example: 1 - in: query name: 'filter[name]' description: 'Filter by space name (partial match).' example: Coworking required: false schema: type: string description: 'Filter by space name (partial match).' example: Coworking - in: query name: 'filter[description]' description: 'Filter by space description (partial match).' example: modern required: false schema: type: string description: 'Filter by space description (partial match).' example: modern - in: query name: 'filter[email]' description: 'Filter by space email (partial match).' example: space@example.com required: false schema: type: string description: 'Filter by space email (partial match).' example: space@example.com - in: query name: 'filter[organisation_id]' description: 'Filter by organisation ID.' example: 1 required: false schema: type: integer description: 'Filter by organisation ID.' example: 1 - in: query name: 'filter[has_posts]' description: 'Filter spaces that have posts.' example: true required: false schema: type: boolean description: 'Filter spaces that have posts.' example: true - in: query name: sort description: "Sort field and direction.\nAvailable fields:\n- name (Sort by space name)\n- created_at (Sort by creation date)\n- updated_at (Sort by last update)\n- visits (Sort by number of visits)\nDefault: -created_at" example: '-visits' required: false schema: type: string description: "Sort field and direction.\nAvailable fields:\n- name (Sort by space name)\n- created_at (Sort by creation date)\n- updated_at (Sort by last update)\n- visits (Sort by number of visits)\nDefault: -created_at" example: '-visits' - in: query name: per_page description: 'Number of records per page. Default: 15.' example: 15 required: false schema: type: integer description: 'Number of records per page. Default: 15.' example: 15 responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 uuid: f2d75bf6-3cb8-305c-8f70-f71fe697cba3 name: 'Modern Coworking Space' slug: modern-coworking-space matterport_model_id: null description: 'A modern coworking space in the heart of the city' visibility: public email: space@example.com phone_number: '+60123456789' website: '@{{$baseUrl}}/spaces/modern-coworking' social_media: - name: Twitter username: '@modernspace' - name: Facebook username: modernspacekl organisation_id: 1 category_id: 2 visits: 1250 created_at: '2024-01-15T08:00:00Z' updated_at: '2024-02-08T14:30:00Z' deleted_at: null address: id: 1 address_line_1: '123 Jalan Sultan Ismail' address_line_2: 'Level 23' postal_code: '50250' city: 'Kuala Lumpur' state: 'Wilayah Persekutuan' country: Malaysia category: id: 2 name: 'Coworking Space' slug: coworking-space links: public_url: '@{{$baseUrl}}/spaces/modern-coworking-space' - id: 2 name: 'Creative Studio' slug: creative-studio matterport_model_id: abc123xyz description: 'A creative studio space perfect for photographers and content creators' visibility: public email: studio@example.com phone_number: '+60123456790' website: '@{{$baseUrl}}/spaces/creative-studio' social_media: - name: Instagram username: '@creativestudiomy' organisation_id: 1 category_id: 3 visits: 890 created_at: '2024-01-20T10:00:00Z' updated_at: '2024-02-07T16:45:00Z' deleted_at: null address: id: 2 address_line_1: '45 Jalan Telawi' address_line_2: null postal_code: '59100' city: Bangsar state: 'Kuala Lumpur' country: Malaysia category: id: 3 name: 'Studio Space' slug: studio-space links: public_url: '@{{$baseUrl}}/spaces/creative-studio' links: first: '@{{$baseUrl}}/api/v1/spaces?page=1' last: '@{{$baseUrl}}/api/v1/spaces?page=5' prev: null next: '@{{$baseUrl}}/api/v1/spaces?page=2' meta: current_page: 1 from: 1 last_page: 5 path: '@{{$baseUrl}}/api/v1/spaces' per_page: 15 to: 15 total: 68 properties: data: type: array example: - id: 1 uuid: f2d75bf6-3cb8-305c-8f70-f71fe697cba3 name: 'Modern Coworking Space' slug: modern-coworking-space matterport_model_id: null description: 'A modern coworking space in the heart of the city' visibility: public email: space@example.com phone_number: '+60123456789' website: '@{{$baseUrl}}/spaces/modern-coworking' social_media: - name: Twitter username: '@modernspace' - name: Facebook username: modernspacekl organisation_id: 1 category_id: 2 visits: 1250 created_at: '2024-01-15T08:00:00Z' updated_at: '2024-02-08T14:30:00Z' deleted_at: null address: id: 1 address_line_1: '123 Jalan Sultan Ismail' address_line_2: 'Level 23' postal_code: '50250' city: 'Kuala Lumpur' state: 'Wilayah Persekutuan' country: Malaysia category: id: 2 name: 'Coworking Space' slug: coworking-space links: public_url: '@{{$baseUrl}}/spaces/modern-coworking-space' - id: 2 name: 'Creative Studio' slug: creative-studio matterport_model_id: abc123xyz description: 'A creative studio space perfect for photographers and content creators' visibility: public email: studio@example.com phone_number: '+60123456790' website: '@{{$baseUrl}}/spaces/creative-studio' social_media: - name: Instagram username: '@creativestudiomy' organisation_id: 1 category_id: 3 visits: 890 created_at: '2024-01-20T10:00:00Z' updated_at: '2024-02-07T16:45:00Z' deleted_at: null address: id: 2 address_line_1: '45 Jalan Telawi' address_line_2: null postal_code: '59100' city: Bangsar state: 'Kuala Lumpur' country: Malaysia category: id: 3 name: 'Studio Space' slug: studio-space links: public_url: '@{{$baseUrl}}/spaces/creative-studio' items: type: object properties: id: type: integer example: 1 uuid: type: string example: f2d75bf6-3cb8-305c-8f70-f71fe697cba3 name: type: string example: 'Modern Coworking Space' slug: type: string example: modern-coworking-space matterport_model_id: type: string example: null nullable: true description: type: string example: 'A modern coworking space in the heart of the city' visibility: type: string example: public email: type: string example: space@example.com phone_number: type: string example: '+60123456789' website: type: string example: '@{{$baseUrl}}/spaces/modern-coworking' social_media: type: array example: - name: Twitter username: '@modernspace' - name: Facebook username: modernspacekl items: type: object properties: name: type: string example: Twitter username: type: string example: '@modernspace' organisation_id: type: integer example: 1 category_id: type: integer example: 2 visits: type: integer example: 1250 created_at: type: string example: '2024-01-15T08:00:00Z' updated_at: type: string example: '2024-02-08T14:30:00Z' deleted_at: type: string example: null nullable: true address: type: object properties: id: type: integer example: 1 address_line_1: type: string example: '123 Jalan Sultan Ismail' address_line_2: type: string example: 'Level 23' postal_code: type: string example: '50250' city: type: string example: 'Kuala Lumpur' state: type: string example: 'Wilayah Persekutuan' country: type: string example: Malaysia category: type: object properties: id: type: integer example: 2 name: type: string example: 'Coworking Space' slug: type: string example: coworking-space links: type: object properties: public_url: type: string example: '@{{$baseUrl}}/spaces/modern-coworking-space' links: type: object properties: first: type: string example: '@{{$baseUrl}}/api/v1/spaces?page=1' last: type: string example: '@{{$baseUrl}}/api/v1/spaces?page=5' prev: type: string example: null nullable: true next: type: string example: '@{{$baseUrl}}/api/v1/spaces?page=2' description: "Navigation URLs:\n- public_url: string (Full URL to public space page)" meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 5 path: type: string example: '@{{$baseUrl}}/api/v1/spaces' per_page: type: integer example: 15 to: type: integer example: 15 total: type: integer example: 68 tags: - Spaces post: summary: 'Create Space' operationId: createSpace description: "Creates a new space in the system.
\nThe space will be associated with the authenticated user's organisation.
" parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: data: id: 1 uuid: f2d75bf6-3cb8-305c-8f70-f71fe697cba3 name: 'Modern Coworking Space' slug: modern-coworking-space matterport_model_id: null description: 'A modern coworking space in the heart of the city offering flexible workspace solutions for professionals and teams' visibility: public email: space@example.com phone_number: '+60123456789' website: 'https://pixalink.io/spaces/modern-coworking' social_media: - name: Twitter username: '@modernspace' - name: Facebook username: modernspacekl - name: Instagram username: '@modernspace.kl' organisation_id: 1 category_id: 2 visits: 0 created_at: '2024-02-08T15:00:00Z' updated_at: '2024-02-08T15:00:00Z' deleted_at: null address: id: 1 address_line_1: '123 Jalan Sultan Ismail' address_line_2: 'Level 23' postal_code: '50250' city: 'Kuala Lumpur' state: 'Wilayah Persekutuan' country: Malaysia links: public_url: '@{{$baseUrl}}/spaces/modern-coworking-space' properties: data: type: object properties: id: type: integer example: 1 uuid: type: string example: f2d75bf6-3cb8-305c-8f70-f71fe697cba3 name: type: string example: 'Modern Coworking Space' slug: type: string example: modern-coworking-space matterport_model_id: type: string example: null nullable: true description: type: string example: 'A modern coworking space in the heart of the city offering flexible workspace solutions for professionals and teams' visibility: type: string example: public email: type: string example: space@example.com phone_number: type: string example: '+60123456789' website: type: string example: 'https://pixalink.io/spaces/modern-coworking' social_media: type: array example: - name: Twitter username: '@modernspace' - name: Facebook username: modernspacekl - name: Instagram username: '@modernspace.kl' items: type: object properties: name: type: string example: Twitter username: type: string example: '@modernspace' organisation_id: type: integer example: 1 category_id: type: integer example: 2 visits: type: integer example: 0 created_at: type: string example: '2024-02-08T15:00:00Z' updated_at: type: string example: '2024-02-08T15:00:00Z' deleted_at: type: string example: null nullable: true address: type: object properties: id: type: integer example: 1 address_line_1: type: string example: '123 Jalan Sultan Ismail' address_line_2: type: string example: 'Level 23' postal_code: type: string example: '50250' city: type: string example: 'Kuala Lumpur' state: type: string example: 'Wilayah Persekutuan' country: type: string example: Malaysia links: type: object properties: public_url: type: string example: '@{{$baseUrl}}/spaces/modern-coworking-space' 422: description: 'Validation Error' content: text/plain: schema: type: string example: "{\n \"message\": \"The given data was invalid.\",\n \"errors\": {\n \"name\": [\n \"The name field is required.\"\n ],\n \"description\": [\n \"The description field is required.\"\n ],\n \"visibility\": [\n \"The visibility field is required.\",\n \"The selected visibility is invalid.\"\n ],\n \"category_id\": [\n \"The category id field is required.\",\n \"The selected category id is invalid.\"\n ],\n \"address.address_line_1\": [\n \"The address line 1 field is required.\"\n ],\n \"address.postal_code\": [\n \"The postal code must be 5 digits.\"\n ],\n \"address.country\": [\n \"The selected country is invalid.\"\n ],\n \"phone_number\": [\n \"The phone number format is invalid.\"\n ]\n }" tags: - Spaces requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'The name of the space.' example: 'Modern Coworking Space' description: type: string description: 'The description of the space (max: 65535 characters).' example: 'A modern coworking space in the heart of the city' visibility: type: string description: "The visibility of the space. Must be one of:\n- Public (Listed and viewable by anyone)\n- Private (Not listed, viewable with link)\n- Hidden (Not listed, cannot be searched)\n- Draft (Not listed, cannot be searched)" example: Public email: type: string description: 'optional A valid email address.' example: space@example.com nullable: true phone_number: type: string description: 'The contact phone number in E.164 format (e.g. +60125452222). Starting with + followed by country code and number with no spaces or special characters.' example: '+60123456789' website: type: string description: 'optional A valid URL.' example: '@{{$baseUrl}}' nullable: true category_id: type: integer description: 'The ID of a subcategory.' example: 1 social_media: type: array description: 'optional Array of social media details.' example: - qui items: type: string nullable: true address: type: array description: "The space's address." example: - aliquam items: type: string tags: type: array description: 'optional Array of tag names.' example: - Coworking - 24/7 items: type: string classification: type: array description: 'optional Array of classification tags.' example: - Premium - Featured items: type: string media: type: object description: 'optional Media files for the space.' example: null properties: preview: type: string format: binary description: 'optional Preview image/video (400x225, max 10MB). Supports png, jpeg, ico.' featured_image: type: string format: binary description: 'optional Featured image for SEO (max 10MB). Supports images only.' gallery: type: array description: 'optional Array of gallery images (1920x1080, max 10MB each). Min 1 required if sent.' example: null items: type: string logo: type: string format: binary description: 'optional Company logo (1:1 ratio, max 5MB). Supports images only.' required: - name - description - visibility - category_id - address '/api/spaces/{id}': get: summary: 'Show Space Details' operationId: showSpaceDetails description: 'Retrieves detailed information about a specific space, including any requested relationships.' parameters: - in: query name: include description: "Comma-separated list of relationships to include. Available relationships:\n- category\n- address\n- posts\n- widgets\n- activeWidgets\n- activeCalendars" example: 'category,address' required: false schema: type: string description: "Comma-separated list of relationships to include. Available relationships:\n- category\n- address\n- posts\n- widgets\n- activeWidgets\n- activeCalendars" example: 'category,address' responses: 200: description: Success content: application/json: schema: type: object example: data: id: 1 uuid: f2d75bf6-3cb8-305c-8f70-f71fe697cba3 name: 'Modern Coworking Space' slug: modern-coworking-space matterport_model_id: null description: 'A modern coworking space in the heart of the city' visibility: public email: space@example.com phone_number: '+60123456789' website: '@{{$baseUrl}}' social_media: - name: Twitter username: '@modernspace' tags: - Coworking - 24/7 classification: - Premium - Featured category_id: 1 organisation_id: 1 visits: 150 created_at: '2024-02-02T12:00:00Z' updated_at: '2024-02-02T12:00:00Z' address: id: 1 address_line_1: '123 Main Street' address_line_2: 'Suite 45' postal_code: '50450' city: 'Petaling Jaya' state: Selangor country: Malaysia media: preview: url: '@{{$baseUrl}}/media/preview.jpg' type: image featured_image: url: '@{{$baseUrl}}/media/featured.jpg' gallery: - url: '@{{$baseUrl}}/media/gallery1.jpg' logo: url: '@{{$baseUrl}}/media/logo.jpg' properties: data: type: object properties: id: type: integer example: 1 uuid: type: string example: f2d75bf6-3cb8-305c-8f70-f71fe697cba3 name: type: string example: 'Modern Coworking Space' slug: type: string example: modern-coworking-space matterport_model_id: type: string example: null nullable: true description: type: string example: 'A modern coworking space in the heart of the city' visibility: type: string example: public email: type: string example: space@example.com phone_number: type: string example: '+60123456789' website: type: string example: '@{{$baseUrl}}' social_media: type: array example: - name: Twitter username: '@modernspace' items: type: object properties: name: type: string example: Twitter username: type: string example: '@modernspace' tags: type: array example: - Coworking - 24/7 items: type: string classification: type: array example: - Premium - Featured items: type: string category_id: type: integer example: 1 organisation_id: type: integer example: 1 visits: type: integer example: 150 created_at: type: string example: '2024-02-02T12:00:00Z' updated_at: type: string example: '2024-02-02T12:00:00Z' address: type: object properties: id: type: integer example: 1 address_line_1: type: string example: '123 Main Street' address_line_2: type: string example: 'Suite 45' postal_code: type: string example: '50450' city: type: string example: 'Petaling Jaya' state: type: string example: Selangor country: type: string example: Malaysia media: type: object properties: preview: type: object properties: url: type: string example: '@{{$baseUrl}}/media/preview.jpg' type: type: string example: image featured_image: type: object properties: url: type: string example: '@{{$baseUrl}}/media/featured.jpg' gallery: type: array example: - url: '@{{$baseUrl}}/media/gallery1.jpg' items: type: object properties: url: type: string example: '@{{$baseUrl}}/media/gallery1.jpg' logo: type: object properties: url: type: string example: '@{{$baseUrl}}/media/logo.jpg' 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'Record not found' properties: message: type: string example: 'Record not found' tags: - Spaces put: summary: 'Update Space' operationId: updateSpace description: "Updates an existing space's information.
All fields are optional.
\nOmitted fields will retain their current values." parameters: [] responses: 200: description: Success content: application/json: schema: type: object example: data: id: 1 uuid: f2d75bf6-3cb8-305c-8f70-f71fe697cba3 name: 'Modern Coworking Space' slug: modern-coworking-space matterport_model_id: null description: 'A modern coworking space in the heart of the city' visibility: public email: space@example.com phone_number: '+60123456789' website: '@{{$baseUrl}}' social_media: - name: Twitter username: '@modernspace' tags: - Coworking - 24/7 classification: - Premium - Featured category_id: 1 organisation_id: 1 visits: 150 created_at: '2024-02-02T12:00:00Z' updated_at: '2024-02-02T12:00:00Z' properties: data: type: object properties: id: type: integer example: 1 uuid: type: string example: f2d75bf6-3cb8-305c-8f70-f71fe697cba3 name: type: string example: 'Modern Coworking Space' slug: type: string example: modern-coworking-space matterport_model_id: type: string example: null nullable: true description: type: string example: 'A modern coworking space in the heart of the city' visibility: type: string example: public email: type: string example: space@example.com phone_number: type: string example: '+60123456789' website: type: string example: '@{{$baseUrl}}' social_media: type: array example: - name: Twitter username: '@modernspace' items: type: object properties: name: type: string example: Twitter username: type: string example: '@modernspace' tags: type: array example: - Coworking - 24/7 items: type: string classification: type: array example: - Premium - Featured items: type: string category_id: type: integer example: 1 organisation_id: type: integer example: 1 visits: type: integer example: 150 created_at: type: string example: '2024-02-02T12:00:00Z' updated_at: type: string example: '2024-02-02T12:00:00Z' 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'Record not found' properties: message: type: string example: 'Record not found' 422: description: 'Validation Error' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: email: - 'The email must be a valid email address.' phone_number: - 'The phone number must be a valid phone number.' visibility: - 'The selected visibility is invalid.' address.country: - 'The selected country is invalid. Only Malaysia and Singapore are allowed.' media.preview: - 'The preview must be an image file of type: png, jpg, jpeg, ico.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: email: type: array example: - 'The email must be a valid email address.' items: type: string phone_number: type: array example: - 'The phone number must be a valid phone number.' items: type: string visibility: type: array example: - 'The selected visibility is invalid.' items: type: string address.country: type: array example: - 'The selected country is invalid. Only Malaysia and Singapore are allowed.' items: type: string media.preview: type: array example: - 'The preview must be an image file of type: png, jpg, jpeg, ico.' items: type: string tags: - Spaces requestBody: required: false content: application/json: schema: type: object properties: name: type: string description: 'optional The name of the space.' example: 'Modern Coworking Space' description: type: string description: 'optional The description of the space (max: 65535 characters).' example: 'A modern coworking space in the heart of the city' visibility: type: string description: "optional The visibility status. Must be one of:\n- Public (Listed and viewable by anyone)\n- Private (Not listed, viewable with link)\n- Hidden (Not listed, cannot be searched)\n- Draft (Not listed, cannot be searched)" example: Public email: type: string description: 'optional A valid email address.' example: space@example.com nullable: true phone_number: type: string description: 'The contact phone number in E.164 format (e.g. +60125452222). Starting with + followed by country code and number with no spaces or special characters.' example: ipsum website: type: string description: 'optional A valid URL.' example: '@{{$baseUrl}}' nullable: true category_id: type: integer description: 'optional The ID of a subcategory.' example: 1 social_media: type: array description: 'optional Array of social media details. Will replace existing entries.' example: - amet items: type: string nullable: true address: type: array description: "optional The space's address details to update." example: - quasi items: type: string tags: type: array description: 'optional Array of tag names to update.' example: - Coworking - 24/7 items: type: string classification: type: array description: 'optional Array of classification tags to update.' example: - Premium - Featured items: type: string media: type: object description: 'optional Media files to update.' example: null properties: preview: type: string format: binary description: 'optional Preview image/video (400x225, max 10MB). Supports png, jpeg, ico.' featured_image: type: string format: binary description: 'optional Featured image for SEO (max 10MB). Supports images only.' gallery: type: array description: 'optional Array of gallery images (1920x1080, max 10MB each). Min 1 required if sent.' example: null items: type: string logo: type: string format: binary description: 'optional Company logo (1:1 ratio, max 5MB). Supports images only.' delete: summary: 'Delete Space' operationId: deleteSpace description: "Deletes a space and all its associated data including:\n- Address\n- Media (preview, gallery, logo)\n- Tags and classifications\n- Widgets\n- Posts\n- POS integration settings" parameters: [] responses: 204: description: 'No Content' content: application/json: schema: type: object example: { } properties: { } 403: description: 'Not Authorized' content: application/json: schema: type: object example: message: 'You are not authorized to delete this space' properties: message: type: string example: 'You are not authorized to delete this space' 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'Record not found' properties: message: type: string example: 'Record not found' tags: - Spaces parameters: - in: path name: id description: 'The ID of the space.' example: 1 required: true schema: type: integer /api/transactions: get: summary: 'List Transactions' operationId: listTransactions description: 'Get a paginated list of transactions for the organisation.' parameters: - in: query name: 'filter[customer_id]' description: 'Filter by customer ID.' example: 1 required: false schema: type: integer description: 'Filter by customer ID.' example: 1 - in: query name: 'filter[type]' description: "Filter by transaction type. Available types:\n- Purchase: Points earned from purchases\n- Review: Points earned from reviews\n- Referral: Points earned from referrals\n- Point Reward Redeem: Points spent on reward redemption" example: Purchase required: false schema: type: string description: "Filter by transaction type. Available types:\n- Purchase: Points earned from purchases\n- Review: Points earned from reviews\n- Referral: Points earned from referrals\n- Point Reward Redeem: Points spent on reward redemption" example: Purchase - in: query name: 'filter[space_id]' description: 'Filter by space ID.' example: 1 required: false schema: type: integer description: 'Filter by space ID.' example: 1 - in: query name: 'filter[amount]' description: 'Filter by amount with operators (>, <, =).' example: '>100' required: false schema: type: string description: 'Filter by amount with operators (>, <, =).' example: '>100' - in: query name: 'filter[created_at]' description: 'Filter by date with operators.' example: '>2024-01-01' required: false schema: type: string description: 'Filter by date with operators.' example: '>2024-01-01' - in: query name: 'filter[updated_at]' description: 'Filter by updated date with operators.' example: '>2024-01-01' required: false schema: type: string description: 'Filter by updated date with operators.' example: '>2024-01-01' - in: query name: sort description: 'Sort field (created_at, amount). Use -field for descending.' example: '-created_at' required: false schema: type: string description: 'Sort field (created_at, amount). Use -field for descending.' example: '-created_at' - in: query name: include description: 'Include relationships (customer, reward, space, customerRewards).' example: 'customer,reward,customerRewards' required: false schema: type: string description: 'Include relationships (customer, reward, space, customerRewards).' example: 'customer,reward,customerRewards' - in: query name: per_page description: 'Number of records per page.' example: 15 required: false schema: type: integer description: 'Number of records per page.' example: 15 responses: 200: description: '' content: application/json: schema: oneOf: - description: 'List of transactions' type: object example: data: - id: 1 amount: 1000 valid_amount: 1000 operation: + type: Purchase remarks: 'Purchase at Store #123' customer_id: 1 space_id: 1 reward_id: null custom_properties: purchase_amount: 1000 receipt_number: R12345 expired_at: '2024-02-10T23:59:59.000000Z' created_at: '2024-02-09T10:00:00.000000Z' updated_at: '2024-02-09T10:00:00.000000Z' customer: id: 1 name: 'John Smith' email: john.smith@example.com phone_number: '+60123456789' current_point: 2500 current_credits: 150 - id: 2 amount: 500 valid_amount: 500 operation: '-' type: Point_Reward_Redeem remarks: 'Reward: Free Coffee' customer_id: 1 space_id: 1 reward_id: 5 custom_properties: null expired_at: null created_at: '2024-02-08T15:30:00.000000Z' updated_at: '2024-02-08T15:30:00.000000Z' reward: id: 5 name: 'Free Coffee' amount: 500 description: 'Redeem a free coffee at any of our locations' customerRewards: - id: 1 status: Used code: null expired_at: '2024-02-08T15:30:00.000000Z' used_at: '2024-02-08T15:30:00.000000Z' customer_id: 1 reward_id: 5 links: first: '@{{$baseUrl}}/api/v1/transactions?page=1' last: '@{{$baseUrl}}/api/v1/transactions?page=5' prev: null next: '@{{$baseUrl}}/api/v1/transactions?page=2' meta: current_page: 1 from: 1 last_page: 5 links: - url: null label: '« Previous' active: false - url: '@{{$baseUrl}}/api/v1/transactions?page=1' label: '1' active: true - url: '@{{$baseUrl}}/api/v1/transactions?page=2' label: '2' active: false path: '@{{$baseUrl}}/api/v1/transactions' per_page: 15 to: 15 total: 68 properties: data: type: array example: - id: 1 amount: 1000 valid_amount: 1000 operation: + type: Purchase remarks: 'Purchase at Store #123' customer_id: 1 space_id: 1 reward_id: null custom_properties: purchase_amount: 1000 receipt_number: R12345 expired_at: '2024-02-10T23:59:59.000000Z' created_at: '2024-02-09T10:00:00.000000Z' updated_at: '2024-02-09T10:00:00.000000Z' customer: id: 1 name: 'John Smith' email: john.smith@example.com phone_number: '+60123456789' current_point: 2500 current_credits: 150 - id: 2 amount: 500 valid_amount: 500 operation: '-' type: Point_Reward_Redeem remarks: 'Reward: Free Coffee' customer_id: 1 space_id: 1 reward_id: 5 custom_properties: null expired_at: null created_at: '2024-02-08T15:30:00.000000Z' updated_at: '2024-02-08T15:30:00.000000Z' reward: id: 5 name: 'Free Coffee' amount: 500 description: 'Redeem a free coffee at any of our locations' customerRewards: - id: 1 status: Used code: null expired_at: '2024-02-08T15:30:00.000000Z' used_at: '2024-02-08T15:30:00.000000Z' customer_id: 1 reward_id: 5 items: type: object properties: id: type: integer example: 1 amount: type: integer example: 1000 valid_amount: type: integer example: 1000 operation: type: string example: + type: type: string example: Purchase remarks: type: string example: 'Purchase at Store #123' customer_id: type: integer example: 1 space_id: type: integer example: 1 reward_id: type: string example: null nullable: true custom_properties: type: object properties: purchase_amount: type: integer example: 1000 receipt_number: type: string example: R12345 expired_at: type: string example: '2024-02-10T23:59:59.000000Z' created_at: type: string example: '2024-02-09T10:00:00.000000Z' updated_at: type: string example: '2024-02-09T10:00:00.000000Z' customer: type: object properties: id: type: integer example: 1 name: type: string example: 'John Smith' email: type: string example: john.smith@example.com phone_number: type: string example: '+60123456789' current_point: type: integer example: 2500 current_credits: type: integer example: 150 links: type: object properties: first: type: string example: '@{{$baseUrl}}/api/v1/transactions?page=1' last: type: string example: '@{{$baseUrl}}/api/v1/transactions?page=5' prev: type: string example: null nullable: true next: type: string example: '@{{$baseUrl}}/api/v1/transactions?page=2' meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 5 links: type: array example: - url: null label: '« Previous' active: false - url: '@{{$baseUrl}}/api/v1/transactions?page=1' label: '1' active: true - url: '@{{$baseUrl}}/api/v1/transactions?page=2' label: '2' active: false items: type: object properties: url: type: string example: null nullable: true label: type: string example: '« Previous' active: type: boolean example: false path: type: string example: '@{{$baseUrl}}/api/v1/transactions' per_page: type: integer example: 15 to: type: integer example: 15 total: type: integer example: 68 - description: 'No transactions found' type: object example: data: [] links: first: '@{{$baseUrl}}/api/v1/transactions?page=1' last: '@{{$baseUrl}}/api/v1/transactions?page=1' prev: null next: null meta: current_page: 1 from: null last_page: 1 links: - url: null label: '« Previous' active: false - url: '@{{$baseUrl}}/api/v1/transactions?page=1' label: '1' active: true path: '@{{$baseUrl}}/api/v1/transactions' per_page: 15 to: null total: 0 properties: data: type: array example: [] links: type: object properties: first: type: string example: '@{{$baseUrl}}/api/v1/transactions?page=1' last: type: string example: '@{{$baseUrl}}/api/v1/transactions?page=1' prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: current_page: type: integer example: 1 from: type: string example: null nullable: true last_page: type: integer example: 1 links: type: array example: - url: null label: '« Previous' active: false - url: '@{{$baseUrl}}/api/v1/transactions?page=1' label: '1' active: true items: type: object properties: url: type: string example: null nullable: true label: type: string example: '« Previous' active: type: boolean example: false path: type: string example: '@{{$baseUrl}}/api/v1/transactions' per_page: type: integer example: 15 to: type: string example: null nullable: true total: type: integer example: 0 tags: - Transactions post: summary: 'Create Transaction' operationId: createTransaction description: "Creates a new transaction record for tracking customer points/rewards. If no existing customer is found,\na new customer will be automatically created with the provided phone number.\nKey features:\n- Points are multiplied by the customer's tier multiplier if applicable\n- Points may expire based on organisation settings\n- Handles referral tracking and rewards\n- Sends notifications if enabled in organisation settings\n- Can mark linked customer rewards as used during transaction" parameters: [] responses: 201: description: Success content: application/json: schema: type: object example: data: id: 1 amount: 1000 valid_amount: 1000 operation: + type: Purchase remarks: 'Purchase at Store #123' customer_id: 1 space_id: 1 expired_at: '2024-02-10T23:59:59.000000Z' created_at: '2024-02-09T10:00:00.000000Z' updated_at: '2024-02-09T10:00:00.000000Z' customer_rewards: - id: 1 customer_id: 1 reward_id: 5 status: Used code: REWARD123 expired_at: '2024-12-31T23:59:59.000000Z' used_at: '2024-02-09T10:00:00.000000Z' - id: 2 customer_id: 1 reward_id: 6 status: Used code: REWARD456 expired_at: '2024-12-31T23:59:59.000000Z' used_at: '2024-02-09T10:00:00.000000Z' properties: data: type: object properties: id: type: integer example: 1 amount: type: integer example: 1000 valid_amount: type: integer example: 1000 operation: type: string example: + type: type: string example: Purchase remarks: type: string example: 'Purchase at Store #123' customer_id: type: integer example: 1 space_id: type: integer example: 1 expired_at: type: string example: '2024-02-10T23:59:59.000000Z' created_at: type: string example: '2024-02-09T10:00:00.000000Z' updated_at: type: string example: '2024-02-09T10:00:00.000000Z' customer_rewards: type: array example: - id: 1 customer_id: 1 reward_id: 5 status: Used code: REWARD123 expired_at: '2024-12-31T23:59:59.000000Z' used_at: '2024-02-09T10:00:00.000000Z' - id: 2 customer_id: 1 reward_id: 6 status: Used code: REWARD456 expired_at: '2024-12-31T23:59:59.000000Z' used_at: '2024-02-09T10:00:00.000000Z' items: type: object properties: id: type: integer example: 1 customer_id: type: integer example: 1 reward_id: type: integer example: 5 status: type: string example: Used code: type: string example: REWARD123 expired_at: type: string example: '2024-12-31T23:59:59.000000Z' used_at: type: string example: '2024-02-09T10:00:00.000000Z' 422: description: '' content: application/json: schema: oneOf: - description: 'Validation Error' type: object example: message: 'The given data was invalid.' errors: customer_id: - 'The customer id field is required when phone number is not present.' phone_number: - 'The phone number field is required when customer id is not present.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: customer_id: type: array example: - 'The customer id field is required when phone number is not present.' items: type: string phone_number: type: array example: - 'The phone number field is required when customer id is not present.' items: type: string - description: 'Invalid Customer Rewards' type: object example: message: 'The given data was invalid.' errors: customer_reward_ids.0: - 'One or more of the selected customer rewards are invalid, used, or expired.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: customer_reward_ids.0: type: array example: - 'One or more of the selected customer rewards are invalid, used, or expired.' items: type: string tags: - Transactions requestBody: required: true content: application/json: schema: type: object properties: customer_id: type: integer description: "Customer's ID. Required if phone_number not provided." example: 1 phone_number: type: string description: "Customer's phone number. Required if customer_id not provided." example: '+60123456789' amount: type: integer description: 'Transaction amount/points.' example: 1000 space_id: type: integer description: 'optional Space ID where transaction occurred.' example: 1 remarks: type: string description: 'optional Additional remarks.' example: 'Purchase at Store #123' custom_properties: type: object description: 'optional Custom fields for the transaction.' example: [] properties: { } customer_reward_ids: type: array description: 'optional An array of customer reward IDs to be marked as used in this transaction. Only pending rewards that have not expired can be used.' example: - 1 - 2 - 3 items: type: string required: - amount '/api/transactions/{id}': get: summary: 'Get Transaction Details' operationId: getTransactionDetails description: 'Retrieve detailed information about a specific transaction.' parameters: - in: query name: include description: 'Relationships to include (customer, reward, space, customerRewards).' example: 'customer,reward,customerRewards' required: false schema: type: string description: 'Relationships to include (customer, reward, space, customerRewards).' example: 'customer,reward,customerRewards' responses: 200: description: Success content: application/json: schema: type: object example: data: id: 1 amount: 1000 valid_amount: 1000 operation: + type: Purchase remarks: 'Purchase at Store #123' customer_id: 1 space_id: 1 reward_id: null expired_at: '2024-12-31T23:59:59.000000Z' created_at: '2024-02-09T10:00:00.000000Z' updated_at: '2024-02-09T10:00:00.000000Z' properties: data: type: object properties: id: type: integer example: 1 amount: type: integer example: 1000 valid_amount: type: integer example: 1000 operation: type: string example: + type: type: string example: Purchase remarks: type: string example: 'Purchase at Store #123' customer_id: type: integer example: 1 space_id: type: integer example: 1 reward_id: type: string example: null nullable: true expired_at: type: string example: '2024-12-31T23:59:59.000000Z' created_at: type: string example: '2024-02-09T10:00:00.000000Z' updated_at: type: string example: '2024-02-09T10:00:00.000000Z' 404: description: 'Not found' content: application/json: schema: type: object example: message: 'Record not found.' properties: message: type: string example: 'Record not found.' tags: - Transactions parameters: - in: path name: id description: 'The ID of the transaction.' example: 1 required: true schema: type: integer - in: path name: transaction description: 'The ID of the transaction.' example: 1 required: true schema: type: integer /api/users: get: summary: 'List Users' operationId: listUsers description: "Returns a paginated list of users belonging to the authenticated user's organisation." parameters: - in: query name: 'filter[name]' description: 'Filter by user name (partial match).' example: Alice required: false schema: type: string description: 'Filter by user name (partial match).' example: Alice - in: query name: 'filter[email]' description: 'Filter by email (partial match).' example: alice@example.com required: false schema: type: string description: 'Filter by email (partial match).' example: alice@example.com - in: query name: sort description: 'Sort field. Allowed: id, name, created_at.' example: '-id' required: false schema: type: string description: 'Sort field. Allowed: id, name, created_at.' example: '-id' - in: query name: per_page description: 'Records per page.' example: 15 required: false schema: type: integer description: 'Records per page.' example: 15 responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 name: 'Alice Tan' email: alice@acme.com organisation_id: 1 roles: - 'Super Admin' last_login_at: '2024-06-01T08:00:00.000000Z' created_at: '2024-01-01T00:00:00.000000Z' updated_at: '2024-01-01T00:00:00.000000Z' links: first: ... last: ... prev: null next: null meta: current_page: 1 from: 1 last_page: 1 per_page: 15 to: 1 total: 1 properties: data: type: array example: - id: 1 name: 'Alice Tan' email: alice@acme.com organisation_id: 1 roles: - 'Super Admin' last_login_at: '2024-06-01T08:00:00.000000Z' created_at: '2024-01-01T00:00:00.000000Z' updated_at: '2024-01-01T00:00:00.000000Z' items: type: object properties: id: type: integer example: 1 name: type: string example: 'Alice Tan' email: type: string example: alice@acme.com organisation_id: type: integer example: 1 roles: type: array example: - 'Super Admin' items: type: string last_login_at: type: string example: '2024-06-01T08:00:00.000000Z' created_at: type: string example: '2024-01-01T00:00:00.000000Z' updated_at: type: string example: '2024-01-01T00:00:00.000000Z' links: type: object properties: first: type: string example: ... last: type: string example: ... prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 1 per_page: type: integer example: 15 to: type: integer example: 1 total: type: integer example: 1 tags: - Users '/api/users/{id}': get: summary: 'Show User' operationId: showUser description: '' parameters: [] responses: 200: description: Success content: application/json: schema: type: object example: data: id: 1 name: Alice email: alice@example.com properties: data: type: object properties: id: type: integer example: 1 name: type: string example: Alice email: type: string example: alice@example.com 403: description: Forbidden content: application/json: schema: type: object example: message: 'This action is unauthorized.' properties: message: type: string example: 'This action is unauthorized.' tags: - Users parameters: - in: path name: id description: 'The user ID.' example: 1 required: true schema: type: integer /api/calendars: get: summary: 'List Calendars' operationId: listCalendars description: "Returns a paginated list of calendars belonging to the authenticated user's organisation spaces.
\nSupports filtering, sorting and relationship inclusion through query parameters." parameters: - in: query name: include description: "Comma-separated list of relationships to include. Available relationships:\n- space\n- reservations" example: 'space,reservations' required: false schema: type: string description: "Comma-separated list of relationships to include. Available relationships:\n- space\n- reservations" example: 'space,reservations' - in: query name: 'filter[space_id]' description: 'Filter by space ID.' example: 1 required: false schema: type: integer description: 'Filter by space ID.' example: 1 - in: query name: 'filter[is_active]' description: 'Filter by active status.' example: true required: false schema: type: boolean description: 'Filter by active status.' example: true - in: query name: 'filter[visibility]' description: 'Filter by visibility. Must be one of: Public, Private.' example: Public required: false schema: type: string description: 'Filter by visibility. Must be one of: Public, Private.' example: Public - in: query name: 'filter[name]' description: 'Filter by calendar name (partial match).' example: 'Meeting Room' required: false schema: type: string description: 'Filter by calendar name (partial match).' example: 'Meeting Room' - in: query name: sort description: "Sort field and direction. Note: Prefix with `-` for descending order.\nAvailable sort fields:\n- name\n- created_at\n- updated_at" example: '-created_at' required: false schema: type: string description: "Sort field and direction. Note: Prefix with `-` for descending order.\nAvailable sort fields:\n- name\n- created_at\n- updated_at" example: '-created_at' - in: query name: per_page description: 'Number of records per page. Defaults to 15.' example: 15 required: false schema: type: integer description: 'Number of records per page. Defaults to 15.' example: 15 responses: 200: description: Success content: application/json: schema: type: object example: data: - id: 1 name: 'Meeting Room A' slug: meeting-room-a description: 'Large meeting room for up to 20 people' is_active: true space_id: 1 visibility: Public min_capacity: 1 max_capacity: 20 is_required_approval: false is_auto_approval: true cancel_before_minutes: 60 book_before_minutes: 30 reminder_minutes: 15 crm_auto_import: true is_unavailable_timeslots_visible: false is_qr_code_visible: true default_timeslots: - start_time: '09:00' end_time: '10:00' days: - monday - tuesday - wednesday - thursday - friday block_dates: [] notification_settings: new_reservation: enabled: true message: 'Your reservation has been confirmed' custom_properties: { } channels: - WhatsApp - Sms terms: 'Please arrive 5 minutes before your scheduled time' created_at: '2025-01-31T21:16:49.000000Z' updated_at: '2025-01-31T21:16:49.000000Z' links: first: '@{{$baseUrl}}/api/calendars?page=1' last: '@{{$baseUrl}}/api/calendars?page=1' prev: null next: null meta: current_page: 1 from: 1 last_page: 1 path: '@{{$baseUrl}}/api/calendars' per_page: 15 to: 1 total: 1 properties: data: type: array example: - id: 1 name: 'Meeting Room A' slug: meeting-room-a description: 'Large meeting room for up to 20 people' is_active: true space_id: 1 visibility: Public min_capacity: 1 max_capacity: 20 is_required_approval: false is_auto_approval: true cancel_before_minutes: 60 book_before_minutes: 30 reminder_minutes: 15 crm_auto_import: true is_unavailable_timeslots_visible: false is_qr_code_visible: true default_timeslots: - start_time: '09:00' end_time: '10:00' days: - monday - tuesday - wednesday - thursday - friday block_dates: [] notification_settings: new_reservation: enabled: true message: 'Your reservation has been confirmed' custom_properties: [] channels: - WhatsApp - Sms terms: 'Please arrive 5 minutes before your scheduled time' created_at: '2025-01-31T21:16:49.000000Z' updated_at: '2025-01-31T21:16:49.000000Z' items: type: object properties: id: type: integer example: 1 name: type: string example: 'Meeting Room A' slug: type: string example: meeting-room-a description: type: string example: 'Large meeting room for up to 20 people' is_active: type: boolean example: true space_id: type: integer example: 1 visibility: type: string example: Public min_capacity: type: integer example: 1 max_capacity: type: integer example: 20 is_required_approval: type: boolean example: false is_auto_approval: type: boolean example: true cancel_before_minutes: type: integer example: 60 book_before_minutes: type: integer example: 30 reminder_minutes: type: integer example: 15 crm_auto_import: type: boolean example: true is_unavailable_timeslots_visible: type: boolean example: false is_qr_code_visible: type: boolean example: true default_timeslots: type: array example: - start_time: '09:00' end_time: '10:00' days: - monday - tuesday - wednesday - thursday - friday items: type: object properties: start_time: type: string example: '09:00' end_time: type: string example: '10:00' days: type: array example: - monday - tuesday - wednesday - thursday - friday items: type: string block_dates: type: array example: [] notification_settings: type: object properties: new_reservation: type: object properties: enabled: type: boolean example: true message: type: string example: 'Your reservation has been confirmed' custom_properties: type: object properties: { } channels: type: array example: - WhatsApp - Sms items: type: string terms: type: string example: 'Please arrive 5 minutes before your scheduled time' created_at: type: string example: '2025-01-31T21:16:49.000000Z' updated_at: type: string example: '2025-01-31T21:16:49.000000Z' links: type: object properties: first: type: string example: '@{{$baseUrl}}/api/calendars?page=1' last: type: string example: '@{{$baseUrl}}/api/calendars?page=1' prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 1 path: type: string example: '@{{$baseUrl}}/api/calendars' per_page: type: integer example: 15 to: type: integer example: 1 total: type: integer example: 1 tags: - Calendars post: summary: 'Create Calendar' operationId: createCalendar description: "Creates a new calendar in the system.
\nThe calendar will be associated with the specified space in the authenticated user's organisation." parameters: [] responses: 201: description: 'Created successfully' content: application/json: schema: type: object example: data: id: 1 name: 'Meeting Room A' slug: meeting-room-a description: 'Large meeting room for up to 20 people' is_active: true space_id: 1 visibility: Public min_capacity: 1 max_capacity: 20 is_required_approval: false is_auto_approval: true cancel_before_minutes: 60 book_before_minutes: 30 reminder_minutes: 15 crm_auto_import: true is_unavailable_timeslots_visible: false is_qr_code_visible: true default_timeslots: - start_time: '09:00' end_time: '10:00' days: - monday - tuesday - wednesday - thursday - friday block_dates: [] notification_settings: new_reservation: enabled: true message: 'Your reservation has been confirmed' custom_properties: { } channels: - WhatsApp - Sms terms: 'Please arrive 5 minutes before your scheduled time' created_at: '2025-01-31T21:16:49.000000Z' updated_at: '2025-01-31T21:16:49.000000Z' message: 'Calendar created successfully' properties: data: type: object properties: id: type: integer example: 1 name: type: string example: 'Meeting Room A' slug: type: string example: meeting-room-a description: type: string example: 'Large meeting room for up to 20 people' is_active: type: boolean example: true space_id: type: integer example: 1 visibility: type: string example: Public min_capacity: type: integer example: 1 max_capacity: type: integer example: 20 is_required_approval: type: boolean example: false is_auto_approval: type: boolean example: true cancel_before_minutes: type: integer example: 60 book_before_minutes: type: integer example: 30 reminder_minutes: type: integer example: 15 crm_auto_import: type: boolean example: true is_unavailable_timeslots_visible: type: boolean example: false is_qr_code_visible: type: boolean example: true default_timeslots: type: array example: - start_time: '09:00' end_time: '10:00' days: - monday - tuesday - wednesday - thursday - friday items: type: object properties: start_time: type: string example: '09:00' end_time: type: string example: '10:00' days: type: array example: - monday - tuesday - wednesday - thursday - friday items: type: string block_dates: type: array example: [] notification_settings: type: object properties: new_reservation: type: object properties: enabled: type: boolean example: true message: type: string example: 'Your reservation has been confirmed' custom_properties: type: object properties: { } channels: type: array example: - WhatsApp - Sms items: type: string terms: type: string example: 'Please arrive 5 minutes before your scheduled time' created_at: type: string example: '2025-01-31T21:16:49.000000Z' updated_at: type: string example: '2025-01-31T21:16:49.000000Z' message: type: string example: 'Calendar created successfully' 422: description: 'Validation Error' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: name: - 'The name field is required.' space_id: - 'The selected space id is invalid.' max_capacity: - 'The max capacity must be greater than min capacity.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: name: type: array example: - 'The name field is required.' items: type: string space_id: type: array example: - 'The selected space id is invalid.' items: type: string max_capacity: type: array example: - 'The max capacity must be greater than min capacity.' items: type: string tags: - Calendars requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: "The calendar's name." example: 'Meeting Room A' slug: type: string description: 'The URL-friendly slug. Will be auto-generated if not provided.' example: meeting-room-a nullable: true description: type: string description: 'Description of the calendar.' example: 'Large meeting room for up to 20 people' nullable: true space_id: type: integer description: "The ID of the space this calendar belongs to. Must be from user's organisation." example: 1 is_active: type: boolean description: 'Whether the calendar is active and accepting reservations. Default: true.' example: true visibility: type: string description: 'The visibility of the calendar. Must be one of: Public, Private. Default: Public.' example: Public min_capacity: type: integer description: 'Minimum capacity for reservations.' example: 1 nullable: true max_capacity: type: integer description: 'Maximum capacity for reservations.' example: 20 nullable: true is_required_approval: type: boolean description: 'Whether reservations require approval. Default: false.' example: false is_auto_approval: type: boolean description: 'Whether approved reservations are automatically confirmed. Default: true.' example: true cancel_before_minutes: type: integer description: 'Minutes before reservation start when cancellation is allowed.' example: 60 nullable: true book_before_minutes: type: integer description: 'Minutes before reservation start when booking is allowed.' example: 30 nullable: true is_max_advance_booking_enabled: type: boolean description: 'Whether to enable a maximum advance booking limit for this calendar.' example: false max_advance_booking_days: type: integer description: 'Maximum number of days in advance a customer can make a booking. Required when is_max_advance_booking_enabled is true. This field is required when is_max_advance_booking_enabled is true. Must be at least 1. Must not be greater than 365.' example: 30 nullable: true reminder_minutes: type: integer description: 'Minutes before reservation to send reminders.' example: 15 nullable: true crm_auto_import: type: boolean description: 'Whether to auto-import customers from reservations. Default: true.' example: true is_unavailable_timeslots_visible: type: boolean description: 'Whether to show unavailable timeslots to customers. Default: false.' example: false is_qr_code_visible: type: boolean description: 'Whether to display QR codes for reservations. Default: true.' example: true default_timeslots: type: array description: 'Default available time slots for the calendar.' example: - eos items: type: string nullable: true block_dates: type: array description: 'Array of blocked dates and time slots.' example: - autem items: type: string nullable: true notification_settings: type: object description: 'Notification settings for calendar events.' example: [] properties: new_reservation: type: object description: 'Settings for new reservation notifications.' example: [] properties: enabled: type: boolean description: 'Whether to send new reservation notifications.' example: true message: type: string description: 'Template message for new reservation notifications.' example: 'Your reservation has been confirmed' nullable: true custom_properties: type: object description: 'Custom properties for the calendar.' example: [] properties: { } nullable: true channels: type: array description: 'Communication channels for notifications.' example: - WhatsApp - Sms items: type: string terms: type: string description: 'Terms and conditions for using the calendar.' example: 'Please arrive 5 minutes before your scheduled time' nullable: true required: - name - space_id '/api/calendars/{id}': get: summary: 'Show Calendar Details' operationId: showCalendarDetails description: 'Retrieves detailed information about a specific calendar, including any requested relationships.' parameters: - in: query name: include description: "Comma-separated list of relationships to include. Available relationships:\n- space\n- reservations" example: 'space,reservations' required: false schema: type: string description: "Comma-separated list of relationships to include. Available relationships:\n- space\n- reservations" example: 'space,reservations' responses: 200: description: Success content: application/json: schema: type: object example: data: id: 1 name: 'Meeting Room A' slug: meeting-room-a description: 'Large meeting room for up to 20 people' is_active: true space_id: 1 visibility: Public min_capacity: 1 max_capacity: 20 is_required_approval: false is_auto_approval: true cancel_before_minutes: 60 book_before_minutes: 30 reminder_minutes: 15 crm_auto_import: true is_unavailable_timeslots_visible: false is_qr_code_visible: true default_timeslots: - start_time: '09:00' end_time: '10:00' days: - monday - tuesday - wednesday - thursday - friday block_dates: [] notification_settings: new_reservation: enabled: true message: 'Your reservation has been confirmed' custom_properties: { } channels: - WhatsApp - Sms terms: 'Please arrive 5 minutes before your scheduled time' created_at: '2025-01-31T21:16:49.000000Z' updated_at: '2025-01-31T21:16:49.000000Z' properties: data: type: object properties: id: type: integer example: 1 name: type: string example: 'Meeting Room A' slug: type: string example: meeting-room-a description: type: string example: 'Large meeting room for up to 20 people' is_active: type: boolean example: true space_id: type: integer example: 1 visibility: type: string example: Public min_capacity: type: integer example: 1 max_capacity: type: integer example: 20 is_required_approval: type: boolean example: false is_auto_approval: type: boolean example: true cancel_before_minutes: type: integer example: 60 book_before_minutes: type: integer example: 30 reminder_minutes: type: integer example: 15 crm_auto_import: type: boolean example: true is_unavailable_timeslots_visible: type: boolean example: false is_qr_code_visible: type: boolean example: true default_timeslots: type: array example: - start_time: '09:00' end_time: '10:00' days: - monday - tuesday - wednesday - thursday - friday items: type: object properties: start_time: type: string example: '09:00' end_time: type: string example: '10:00' days: type: array example: - monday - tuesday - wednesday - thursday - friday items: type: string block_dates: type: array example: [] notification_settings: type: object properties: new_reservation: type: object properties: enabled: type: boolean example: true message: type: string example: 'Your reservation has been confirmed' custom_properties: type: object properties: { } channels: type: array example: - WhatsApp - Sms items: type: string terms: type: string example: 'Please arrive 5 minutes before your scheduled time' created_at: type: string example: '2025-01-31T21:16:49.000000Z' updated_at: type: string example: '2025-01-31T21:16:49.000000Z' 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'Record not found' properties: message: type: string example: 'Record not found' tags: - Calendars put: summary: 'Update Calendar' operationId: updateCalendar description: "Updates an existing calendar's information.
\nAll fields are optional." parameters: [] responses: 200: description: Success content: application/json: schema: type: object example: data: id: 1 name: 'Meeting Room A' slug: meeting-room-a description: 'Large meeting room for up to 20 people' is_active: true space_id: 1 visibility: Public min_capacity: 1 max_capacity: 20 is_required_approval: false is_auto_approval: true cancel_before_minutes: 60 book_before_minutes: 30 reminder_minutes: 15 crm_auto_import: true is_unavailable_timeslots_visible: false is_qr_code_visible: true default_timeslots: - start_time: '09:00' end_time: '10:00' days: - monday - tuesday - wednesday - thursday - friday block_dates: [] notification_settings: new_reservation: enabled: true message: 'Your reservation has been confirmed' custom_properties: { } channels: - WhatsApp - Sms terms: 'Please arrive 5 minutes before your scheduled time' created_at: '2025-01-31T21:16:49.000000Z' updated_at: '2025-01-31T21:16:49.000000Z' message: 'Calendar updated successfully' properties: data: type: object properties: id: type: integer example: 1 name: type: string example: 'Meeting Room A' slug: type: string example: meeting-room-a description: type: string example: 'Large meeting room for up to 20 people' is_active: type: boolean example: true space_id: type: integer example: 1 visibility: type: string example: Public min_capacity: type: integer example: 1 max_capacity: type: integer example: 20 is_required_approval: type: boolean example: false is_auto_approval: type: boolean example: true cancel_before_minutes: type: integer example: 60 book_before_minutes: type: integer example: 30 reminder_minutes: type: integer example: 15 crm_auto_import: type: boolean example: true is_unavailable_timeslots_visible: type: boolean example: false is_qr_code_visible: type: boolean example: true default_timeslots: type: array example: - start_time: '09:00' end_time: '10:00' days: - monday - tuesday - wednesday - thursday - friday items: type: object properties: start_time: type: string example: '09:00' end_time: type: string example: '10:00' days: type: array example: - monday - tuesday - wednesday - thursday - friday items: type: string block_dates: type: array example: [] notification_settings: type: object properties: new_reservation: type: object properties: enabled: type: boolean example: true message: type: string example: 'Your reservation has been confirmed' custom_properties: type: object properties: { } channels: type: array example: - WhatsApp - Sms items: type: string terms: type: string example: 'Please arrive 5 minutes before your scheduled time' created_at: type: string example: '2025-01-31T21:16:49.000000Z' updated_at: type: string example: '2025-01-31T21:16:49.000000Z' message: type: string example: 'Calendar updated successfully' 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'Record not found' properties: message: type: string example: 'Record not found' 422: description: 'Validation Error' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: name: - 'The name field is required.' space_id: - 'The selected space id is invalid.' max_capacity: - 'The max capacity must be greater than min capacity.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: name: type: array example: - 'The name field is required.' items: type: string space_id: type: array example: - 'The selected space id is invalid.' items: type: string max_capacity: type: array example: - 'The max capacity must be greater than min capacity.' items: type: string tags: - Calendars requestBody: required: false content: application/json: schema: type: object properties: name: type: string description: "The calendar's name." example: 'Meeting Room A' slug: type: string description: 'The URL-friendly slug.' example: meeting-room-a nullable: true description: type: string description: 'Description of the calendar.' example: 'Large meeting room for up to 20 people' nullable: true space_id: type: integer description: "The ID of the space this calendar belongs to. Must be from user's organisation." example: 1 is_active: type: boolean description: 'Whether the calendar is active and accepting reservations.' example: true visibility: type: string description: 'The visibility of the calendar. Must be one of: Public, Private.' example: Public min_capacity: type: integer description: 'Minimum capacity for reservations.' example: 1 nullable: true max_capacity: type: integer description: 'Maximum capacity for reservations.' example: 20 nullable: true is_required_approval: type: boolean description: 'Whether reservations require approval.' example: false is_auto_approval: type: boolean description: 'Whether approved reservations are automatically confirmed.' example: true cancel_before_minutes: type: integer description: 'Minutes before reservation start when cancellation is allowed.' example: 60 nullable: true book_before_minutes: type: integer description: 'Minutes before reservation start when booking is allowed.' example: 30 nullable: true is_max_advance_booking_enabled: type: boolean description: 'Whether to enable a maximum advance booking limit for this calendar.' example: false max_advance_booking_days: type: integer description: 'Maximum number of days in advance a customer can make a booking. Required when is_max_advance_booking_enabled is true. This field is required when is_max_advance_booking_enabled is true. Must be at least 1. Must not be greater than 365.' example: 30 nullable: true reminder_minutes: type: integer description: 'Minutes before reservation to send reminders.' example: 15 nullable: true crm_auto_import: type: boolean description: 'Whether to auto-import customers from reservations.' example: true is_unavailable_timeslots_visible: type: boolean description: 'Whether to show unavailable timeslots to customers.' example: false is_qr_code_visible: type: boolean description: 'Whether to display QR codes for reservations.' example: true default_timeslots: type: array description: 'Default available time slots for the calendar.' example: - et items: type: string nullable: true block_dates: type: array description: 'Array of blocked dates and time slots.' example: - deleniti items: type: string nullable: true notification_settings: type: object description: 'Notification settings for calendar events.' example: [] properties: new_reservation: type: object description: 'Settings for notifications sent when a new reservation is created.' example: enabled: true message: 'Your reservation has been confirmed.' properties: enabled: type: boolean description: 'Whether to send notifications for new reservations.' example: true message: type: string description: 'Template message for new reservation notifications. Available variables: @{{CUSTOMER_NAME}}, @{{CALENDAR_NAME}}, @{{RESERVATION_DATE}}, @{{RESERVATION_TIME}}.' example: 'Your reservation for @{{CALENDAR_NAME}} has been confirmed for @{{RESERVATION_DATE}} at @{{RESERVATION_TIME}}.' nullable: true custom_properties: type: object description: 'Custom properties for the calendar.' example: [] properties: { } nullable: true channels: type: array description: 'Communication channels for notifications.' example: - WhatsApp - Sms items: type: string terms: type: string description: 'Terms and conditions for using the calendar.' example: 'Please arrive 5 minutes before your scheduled time' nullable: true delete: summary: 'Delete Calendar' operationId: deleteCalendar description: "Deletes a calendar from the system.
\nOnly calendars without active reservations can be deleted." parameters: [] responses: 204: description: 'No Content' content: application/json: schema: type: object example: { } properties: { } 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'Record not found' properties: message: type: string example: 'Record not found' 422: description: 'Cannot Delete' content: application/json: schema: type: object example: message: 'Cannot delete calendar with active reservations' properties: message: type: string example: 'Cannot delete calendar with active reservations' tags: - Calendars parameters: - in: path name: id description: 'The ID of the calendar.' example: 1 required: true schema: type: integer /api/reservations: get: summary: 'List Reservations' operationId: listReservations description: "Returns a paginated list of reservations belonging to the authenticated user's organisation.
\nResults can be filtered, sorted and include related data through query parameters." parameters: - in: query name: include description: "Comma-separated list of relationships to include:\n- calendar\n- customer\n- calendar.space" example: 'calendar,customer' required: false schema: type: string description: "Comma-separated list of relationships to include:\n- calendar\n- customer\n- calendar.space" example: 'calendar,customer' - in: query name: 'filter[calendar_id]' description: 'Filter by calendar ID.' example: 1 required: false schema: type: integer description: 'Filter by calendar ID.' example: 1 - in: query name: 'filter[customer_id]' description: 'Filter by customer ID.' example: 1 required: false schema: type: integer description: 'Filter by customer ID.' example: 1 - in: query name: 'filter[status]' description: "Filter by reservation status. Must be one of:\n- pending\n- rejected\n- reserved\n- cancelled\n- no_show" example: reserved required: false schema: type: string description: "Filter by reservation status. Must be one of:\n- pending\n- rejected\n- reserved\n- cancelled\n- no_show" example: reserved - in: query name: 'filter[date]' description: 'Filter by reservation date. Use ISO 8601 format (YYYY-MM-DD).' example: '2024-01-01' required: false schema: type: string description: 'Filter by reservation date. Use ISO 8601 format (YYYY-MM-DD).' example: '2024-01-01' - in: query name: 'filter[date_from]' description: 'Filter reservations from this date onwards (inclusive). Use ISO 8601 format (YYYY-MM-DD).' example: '2024-01-01' required: false schema: type: string description: 'Filter reservations from this date onwards (inclusive). Use ISO 8601 format (YYYY-MM-DD).' example: '2024-01-01' - in: query name: 'filter[date_to]' description: 'Filter reservations up to this date (inclusive). Use ISO 8601 format (YYYY-MM-DD).' example: '2024-01-31' required: false schema: type: string description: 'Filter reservations up to this date (inclusive). Use ISO 8601 format (YYYY-MM-DD).' example: '2024-01-31' - in: query name: 'filter[name]' description: 'Filter by customer name (partial match).' example: John required: false schema: type: string description: 'Filter by customer name (partial match).' example: John - in: query name: 'filter[email]' description: 'Filter by customer email (partial match).' example: john@example.com required: false schema: type: string description: 'Filter by customer email (partial match).' example: john@example.com - in: query name: 'filter[phone_number]' description: 'Filter by customer phone number (partial match).' example: '+60123' required: false schema: type: string description: 'Filter by customer phone number (partial match).' example: '+60123' - in: query name: sort description: 'Sort field and direction. Allowed fields: date, start_at, created_at, status, name.' example: '-date' required: false schema: type: string description: 'Sort field and direction. Allowed fields: date, start_at, created_at, status, name.' example: '-date' - in: query name: per_page description: 'Number of records per page.' example: 15 required: false schema: type: integer description: 'Number of records per page.' example: 15 responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 ref_id: RSV001234 date: '2024-03-15' start_at: '2024-03-15T14:30:00.000000Z' end_at: '2024-03-15T16:00:00.000000Z' name: 'John Smith' email: john.smith@example.com phone_number: '+60123456789' number_of_guests: 4 status: reserved remarks: 'Birthday celebration' custom_fields: dietary_requirements: Vegetarian special_requests: 'Window seat preferred' timeslot_id: 5 calendar_id: 1 customer_id: 123 reminder_sent_at: '2024-03-14T14:30:00.000000Z' rejection_reason: null created_at: '2024-03-01T10:15:30.000000Z' updated_at: '2024-03-10T16:22:45.000000Z' calendar: id: 1 name: 'Restaurant Reservations' slug: restaurant-reservations description: 'Main dining room reservations' is_active: true space_id: 1 visibility: public min_capacity: 1 max_capacity: 8 is_required_approval: true is_auto_approval: false cancel_before_minutes: 60 book_before_minutes: 30 reminder_minutes: 1440 crm_auto_import: true is_unavailable_timeslots_visible: false is_qr_code_visible: true space: id: 1 name: 'Main Branch' slug: main-branch description: 'Our flagship restaurant location' customer: id: 123 name: 'John Smith' email: john.smith@example.com phone_number: '+60123456789' current_point: 2500 tier_id: 2 links: first: '@{{$baseUrl}}/reservations?page=1' last: '@{{$baseUrl}}/reservations?page=5' prev: null next: '@{{$baseUrl}}/reservations?page=2' meta: current_page: 1 from: 1 last_page: 5 links: - url: null label: '« Previous' active: false - url: '@{{$baseUrl}}/reservations?page=1' label: '1' active: true - url: '@{{$baseUrl}}/reservations?page=2' label: 'Next »' active: false path: '@{{$baseUrl}}/reservations' per_page: 15 to: 15 total: 68 properties: data: type: array example: - id: 1 ref_id: RSV001234 date: '2024-03-15' start_at: '2024-03-15T14:30:00.000000Z' end_at: '2024-03-15T16:00:00.000000Z' name: 'John Smith' email: john.smith@example.com phone_number: '+60123456789' number_of_guests: 4 status: reserved remarks: 'Birthday celebration' custom_fields: dietary_requirements: Vegetarian special_requests: 'Window seat preferred' timeslot_id: 5 calendar_id: 1 customer_id: 123 reminder_sent_at: '2024-03-14T14:30:00.000000Z' rejection_reason: null created_at: '2024-03-01T10:15:30.000000Z' updated_at: '2024-03-10T16:22:45.000000Z' calendar: id: 1 name: 'Restaurant Reservations' slug: restaurant-reservations description: 'Main dining room reservations' is_active: true space_id: 1 visibility: public min_capacity: 1 max_capacity: 8 is_required_approval: true is_auto_approval: false cancel_before_minutes: 60 book_before_minutes: 30 reminder_minutes: 1440 crm_auto_import: true is_unavailable_timeslots_visible: false is_qr_code_visible: true space: id: 1 name: 'Main Branch' slug: main-branch description: 'Our flagship restaurant location' customer: id: 123 name: 'John Smith' email: john.smith@example.com phone_number: '+60123456789' current_point: 2500 tier_id: 2 items: type: object properties: id: type: integer example: 1 ref_id: type: string example: RSV001234 date: type: string example: '2024-03-15' start_at: type: string example: '2024-03-15T14:30:00.000000Z' end_at: type: string example: '2024-03-15T16:00:00.000000Z' name: type: string example: 'John Smith' email: type: string example: john.smith@example.com phone_number: type: string example: '+60123456789' number_of_guests: type: integer example: 4 status: type: string example: reserved remarks: type: string example: 'Birthday celebration' custom_fields: type: object properties: dietary_requirements: type: string example: Vegetarian special_requests: type: string example: 'Window seat preferred' timeslot_id: type: integer example: 5 calendar_id: type: integer example: 1 customer_id: type: integer example: 123 reminder_sent_at: type: string example: '2024-03-14T14:30:00.000000Z' rejection_reason: type: string example: null nullable: true created_at: type: string example: '2024-03-01T10:15:30.000000Z' updated_at: type: string example: '2024-03-10T16:22:45.000000Z' calendar: type: object properties: id: type: integer example: 1 name: type: string example: 'Restaurant Reservations' slug: type: string example: restaurant-reservations description: type: string example: 'Main dining room reservations' is_active: type: boolean example: true space_id: type: integer example: 1 visibility: type: string example: public min_capacity: type: integer example: 1 max_capacity: type: integer example: 8 is_required_approval: type: boolean example: true is_auto_approval: type: boolean example: false cancel_before_minutes: type: integer example: 60 book_before_minutes: type: integer example: 30 reminder_minutes: type: integer example: 1440 crm_auto_import: type: boolean example: true is_unavailable_timeslots_visible: type: boolean example: false is_qr_code_visible: type: boolean example: true space: type: object properties: id: type: integer example: 1 name: type: string example: 'Main Branch' slug: type: string example: main-branch description: type: string example: 'Our flagship restaurant location' customer: type: object properties: id: type: integer example: 123 name: type: string example: 'John Smith' email: type: string example: john.smith@example.com phone_number: type: string example: '+60123456789' current_point: type: integer example: 2500 tier_id: type: integer example: 2 links: type: object properties: first: type: string example: '@{{$baseUrl}}/reservations?page=1' last: type: string example: '@{{$baseUrl}}/reservations?page=5' prev: type: string example: null nullable: true next: type: string example: '@{{$baseUrl}}/reservations?page=2' meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 5 links: type: array example: - url: null label: '« Previous' active: false - url: '@{{$baseUrl}}/reservations?page=1' label: '1' active: true - url: '@{{$baseUrl}}/reservations?page=2' label: 'Next »' active: false items: type: object properties: url: type: string example: null nullable: true label: type: string example: '« Previous' active: type: boolean example: false path: type: string example: '@{{$baseUrl}}/reservations' per_page: type: integer example: 15 to: type: integer example: 15 total: type: integer example: 68 tags: - Reservations post: summary: 'Create Reservation' operationId: createReservation description: "Creates a new reservation in the system.
\nThe reservation will be associated with a calendar and optionally with a customer.
\nAuto-generates a unique reference ID." parameters: [] responses: 201: description: 'Created successfully' content: application/json: schema: type: object example: data: id: 1 ref_id: RSV001234 date: '2024-03-15' start_at: '2024-03-15T14:30:00.000000Z' end_at: '2024-03-15T16:00:00.000000Z' name: 'John Smith' email: john.smith@example.com phone_number: '+60123456789' number_of_guests: 4 status: pending remarks: 'Birthday celebration' custom_fields: dietary_requirements: Vegetarian timeslot_id: null calendar_id: 1 customer_id: null reminder_sent_at: null rejection_reason: null created_at: '2024-03-01T10:15:30.000000Z' updated_at: '2024-03-01T10:15:30.000000Z' message: 'Reservation created successfully' properties: data: type: object properties: id: type: integer example: 1 ref_id: type: string example: RSV001234 date: type: string example: '2024-03-15' start_at: type: string example: '2024-03-15T14:30:00.000000Z' end_at: type: string example: '2024-03-15T16:00:00.000000Z' name: type: string example: 'John Smith' email: type: string example: john.smith@example.com phone_number: type: string example: '+60123456789' number_of_guests: type: integer example: 4 status: type: string example: pending remarks: type: string example: 'Birthday celebration' custom_fields: type: object properties: dietary_requirements: type: string example: Vegetarian timeslot_id: type: string example: null nullable: true calendar_id: type: integer example: 1 customer_id: type: string example: null nullable: true reminder_sent_at: type: string example: null nullable: true rejection_reason: type: string example: null nullable: true created_at: type: string example: '2024-03-01T10:15:30.000000Z' updated_at: type: string example: '2024-03-01T10:15:30.000000Z' message: type: string example: 'Reservation created successfully' 422: description: 'Validation failed' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: date: - 'The date must be a valid date after today.' start_at: - 'The start at field is required.' calendar_id: - 'The selected calendar id is invalid.' number_of_guests: - 'The number of guests must be at least 1.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: date: type: array example: - 'The date must be a valid date after today.' items: type: string start_at: type: array example: - 'The start at field is required.' items: type: string calendar_id: type: array example: - 'The selected calendar id is invalid.' items: type: string number_of_guests: type: array example: - 'The number of guests must be at least 1.' items: type: string tags: - Reservations requestBody: required: true content: application/json: schema: type: object properties: date: type: string description: 'The date of the reservation in Y-m-d format. Must be a valid date. Must be a date after or equal to today.' example: '2024-03-15' start_at: type: string description: 'The start date and time of the reservation in ISO 8601 format. Must be a valid date. Must be a date after date.' example: '2024-03-15T14:30:00Z' end_at: type: string description: 'The end date and time of the reservation in ISO 8601 format. Must be a valid date. Must be a date after start_at.' example: '2024-03-15T16:00:00Z' name: type: string description: 'The full name of the person making the reservation. Must not be greater than 255 characters.' example: 'John Smith' email: type: string description: 'The email address of the person making the reservation. Must be a valid email address. Must not be greater than 255 characters.' example: john.smith@example.com nullable: true phone_number: type: string description: 'The phone number of the person making the reservation. Must not be greater than 255 characters.' example: '+60123456789' nullable: true number_of_guests: type: integer description: 'The number of guests for the reservation. Must be at least 1. Must be at least 1. Must not be greater than 999.' example: 4 status: type: string description: 'The initial status of the reservation.' example: pending enum: - pending - reserved - cancelled - rejected - no_show remarks: type: string description: 'Additional remarks or notes about the reservation. Must not be greater than 65535 characters.' example: 'Birthday celebration, window seat preferred' nullable: true custom_fields: type: object description: 'Custom fields as key-value pairs for additional reservation data.' example: dietary_requirements: Vegetarian special_requests: 'Birthday cake arrangement' properties: { } nullable: true timeslot_id: type: integer description: 'The ID of the associated timeslot, if applicable.' example: 5 nullable: true calendar_id: type: string description: 'The ID of the calendar this reservation belongs to. Must belong to your organisation and accessible spaces.' example: 1 customer_id: type: string description: 'The ID of the associated customer, if the reservation is linked to an existing customer. The id of an existing record in the customers table.' example: 123 nullable: true rejection_reason: type: string description: 'The reason for rejection if the status is set to rejected. Must not be greater than 65535 characters.' example: 'Fully booked on requested date' nullable: true required: - date - start_at - end_at - name - number_of_guests - status - calendar_id '/api/reservations/{id}': get: summary: 'Show Reservation Details' operationId: showReservationDetails description: 'Retrieves detailed information about a specific reservation, including any requested relationships.' parameters: - in: query name: include description: "Comma-separated list of relationships to include. Available relationships:\n- calendar\n- customer\n- calendar.space" example: 'calendar,customer' required: false schema: type: string description: "Comma-separated list of relationships to include. Available relationships:\n- calendar\n- customer\n- calendar.space" example: 'calendar,customer' responses: 200: description: Success content: application/json: schema: type: object example: data: id: 1 ref_id: RSV001234 date: '2024-03-15' start_at: '2024-03-15T14:30:00.000000Z' end_at: '2024-03-15T16:00:00.000000Z' name: 'John Smith' email: john.smith@example.com phone_number: '+60123456789' number_of_guests: 4 status: reserved remarks: 'Birthday celebration' custom_fields: dietary_requirements: Vegetarian timeslot_id: 5 calendar_id: 1 customer_id: 123 reminder_sent_at: '2024-03-14T14:30:00.000000Z' rejection_reason: null created_at: '2024-03-01T10:15:30.000000Z' updated_at: '2024-03-10T16:22:45.000000Z' calendar: id: 1 name: 'Restaurant Reservations' space_id: 1 properties: data: type: object properties: id: type: integer example: 1 ref_id: type: string example: RSV001234 date: type: string example: '2024-03-15' start_at: type: string example: '2024-03-15T14:30:00.000000Z' end_at: type: string example: '2024-03-15T16:00:00.000000Z' name: type: string example: 'John Smith' email: type: string example: john.smith@example.com phone_number: type: string example: '+60123456789' number_of_guests: type: integer example: 4 status: type: string example: reserved remarks: type: string example: 'Birthday celebration' custom_fields: type: object properties: dietary_requirements: type: string example: Vegetarian timeslot_id: type: integer example: 5 calendar_id: type: integer example: 1 customer_id: type: integer example: 123 reminder_sent_at: type: string example: '2024-03-14T14:30:00.000000Z' rejection_reason: type: string example: null nullable: true created_at: type: string example: '2024-03-01T10:15:30.000000Z' updated_at: type: string example: '2024-03-10T16:22:45.000000Z' calendar: type: object properties: id: type: integer example: 1 name: type: string example: 'Restaurant Reservations' space_id: type: integer example: 1 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'Record not found' properties: message: type: string example: 'Record not found' tags: - Reservations put: summary: 'Update Reservation' operationId: updateReservation description: "Updates an existing reservation's information.
All fields are optional.
\nStatus transitions are validated according to business rules." parameters: [] responses: 200: description: Success content: application/json: schema: type: object example: data: id: 1 ref_id: RSV001234 date: '2024-03-15' start_at: '2024-03-15T14:30:00.000000Z' end_at: '2024-03-15T16:00:00.000000Z' name: 'John Smith' email: john.smith@example.com phone_number: '+60123456789' number_of_guests: 6 status: reserved remarks: 'Anniversary celebration - updated guest count' custom_fields: dietary_requirements: 'Vegetarian, Gluten-free' timeslot_id: 5 calendar_id: 1 customer_id: 123 reminder_sent_at: '2024-03-14T14:30:00.000000Z' rejection_reason: null created_at: '2024-03-01T10:15:30.000000Z' updated_at: '2024-03-10T16:22:45.000000Z' message: 'Reservation updated successfully' properties: data: type: object properties: id: type: integer example: 1 ref_id: type: string example: RSV001234 date: type: string example: '2024-03-15' start_at: type: string example: '2024-03-15T14:30:00.000000Z' end_at: type: string example: '2024-03-15T16:00:00.000000Z' name: type: string example: 'John Smith' email: type: string example: john.smith@example.com phone_number: type: string example: '+60123456789' number_of_guests: type: integer example: 6 status: type: string example: reserved remarks: type: string example: 'Anniversary celebration - updated guest count' custom_fields: type: object properties: dietary_requirements: type: string example: 'Vegetarian, Gluten-free' timeslot_id: type: integer example: 5 calendar_id: type: integer example: 1 customer_id: type: integer example: 123 reminder_sent_at: type: string example: '2024-03-14T14:30:00.000000Z' rejection_reason: type: string example: null nullable: true created_at: type: string example: '2024-03-01T10:15:30.000000Z' updated_at: type: string example: '2024-03-10T16:22:45.000000Z' message: type: string example: 'Reservation updated successfully' 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'Reservation not found' properties: message: type: string example: 'Reservation not found' 422: description: 'Validation Error' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: status: - 'Invalid status transition from reserved to pending.' number_of_guests: - 'The number of guests must be at least 1.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: status: type: array example: - 'Invalid status transition from reserved to pending.' items: type: string number_of_guests: type: array example: - 'The number of guests must be at least 1.' items: type: string tags: - Reservations requestBody: required: false content: application/json: schema: type: object properties: date: type: string description: 'The date of the reservation in Y-m-d format. Must be a valid date. Must be a date after or equal to today.' example: '2024-03-15' start_at: type: string description: 'The start date and time of the reservation in ISO 8601 format. Must be a valid date. Must be a date after date.' example: '2024-03-15T14:30:00Z' end_at: type: string description: 'The end date and time of the reservation in ISO 8601 format. Must be a valid date. Must be a date after start_at.' example: '2024-03-15T16:00:00Z' name: type: string description: 'The full name of the person making the reservation. Must not be greater than 255 characters.' example: 'John Smith' email: type: string description: 'The email address of the person making the reservation. Must be a valid email address. Must not be greater than 255 characters.' example: john.smith@example.com nullable: true phone_number: type: string description: 'The phone number of the person making the reservation. Must not be greater than 255 characters.' example: '+60123456789' nullable: true number_of_guests: type: integer description: 'The number of guests for the reservation. Must be at least 1. Must be at least 1. Must not be greater than 999.' example: 4 status: type: string description: 'The initial status of the reservation.' example: pending enum: - pending - reserved - cancelled - rejected - no_show remarks: type: string description: 'Additional remarks or notes about the reservation. Must not be greater than 65535 characters.' example: 'Birthday celebration, window seat preferred' nullable: true custom_fields: type: object description: 'Custom fields as key-value pairs for additional reservation data.' example: dietary_requirements: Vegetarian special_requests: 'Birthday cake arrangement' properties: { } nullable: true timeslot_id: type: integer description: 'The ID of the associated timeslot, if applicable.' example: 5 nullable: true calendar_id: type: string description: 'The ID of the calendar this reservation belongs to. Must belong to your organisation and accessible spaces.' example: 1 customer_id: type: string description: 'The ID of the associated customer, if the reservation is linked to an existing customer. The id of an existing record in the customers table.' example: 123 nullable: true rejection_reason: type: string description: 'The reason for rejection if the status is set to rejected. Must not be greater than 65535 characters.' example: 'Fully booked on requested date' nullable: true delete: summary: 'Delete Reservation' operationId: deleteReservation description: "Soft deletes a reservation from the system.
\nThe reservation can be restored later if needed." parameters: [] responses: 200: description: Success content: application/json: schema: type: object example: message: 'Reservation deleted successfully' properties: message: type: string example: 'Reservation deleted successfully' 404: description: 'Not Found' content: application/json: schema: type: object example: message: 'Record not found' properties: message: type: string example: 'Record not found' tags: - Reservations parameters: - in: path name: id description: 'The ID of the reservation to show.' example: 1 required: true schema: type: integer /api/user: get: summary: 'Get authenticated user' operationId: getAuthenticatedUser description: "Retrieves the currently authenticated user's information." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: id: 3 organisation_id: 2 name: User email: vendor@pixalink.io email_verified_at: '2025-01-31T21:16:49.000000Z' created_at: '2025-01-31T21:16:49.000000Z' updated_at: '2025-01-31T21:16:49.000000Z' deleted_at: null properties: id: type: integer example: 3 description: 'The unique identifier of the user' organisation_id: type: integer example: 2 description: 'The ID of the organisation the user belongs to' name: type: string example: User description: "The user's full name" email: type: string example: vendor@pixalink.io description: "The user's email address" email_verified_at: type: string example: '2025-01-31T21:16:49.000000Z' description: "datetime The timestamp when the user's email was verified" created_at: type: string example: '2025-01-31T21:16:49.000000Z' description: 'datetime The timestamp when the user was created' updated_at: type: string example: '2025-01-31T21:16:49.000000Z' description: 'datetime The timestamp when the user was last updated' deleted_at: type: string example: null description: 'datetime|null The timestamp when the user was deleted, null if not deleted' tags: - Endpoints /api/countries: get: summary: 'List all countries' operationId: listAllCountries description: 'Get a list of all countries with their details.' parameters: - in: query name: 'filter[name]' description: 'Filter countries by name.' example: Malaysia required: false schema: type: string description: 'Filter countries by name.' example: Malaysia - in: query name: 'filter[nationality]' description: 'Filter countries by nationality.' example: Malaysian required: false schema: type: string description: 'Filter countries by nationality.' example: Malaysian - in: query name: 'filter[iso]' description: 'Filter countries by ISO code.' example: MY required: false schema: type: string description: 'Filter countries by ISO code.' example: MY - in: query name: per_page description: 'Number of records to display per page.' example: 15 required: false schema: type: integer description: 'Number of records to display per page.' example: 15 responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 name: Malaysia nationality: Malaysian iso: MY iso3: MYS links: first: '@{{$baseUrl}}/api/countries?page=1' last: '@{{$baseUrl}}/api/countries?page=1' prev: null next: null meta: current_page: 1 from: 1 last_page: 1 links: - url: null label: '« Previous' active: false - url: '@{{$baseUrl}}/api/countries?page=1' label: '1' active: true - url: null label: 'Next »' active: false path: '@{{$baseUrl}}/api/countries' per_page: 15 to: 1 total: 1 properties: data: type: array example: - id: 1 name: Malaysia nationality: Malaysian iso: MY iso3: MYS items: type: object properties: id: type: integer example: 1 name: type: string example: Malaysia nationality: type: string example: Malaysian iso: type: string example: MY iso3: type: string example: MYS links: type: object properties: first: type: string example: '@{{$baseUrl}}/api/countries?page=1' last: type: string example: '@{{$baseUrl}}/api/countries?page=1' prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 1 links: type: array example: - url: null label: '« Previous' active: false - url: '@{{$baseUrl}}/api/countries?page=1' label: '1' active: true - url: null label: 'Next »' active: false items: type: object properties: url: type: string example: null nullable: true label: type: string example: '« Previous' active: type: boolean example: false path: type: string example: '@{{$baseUrl}}/api/countries' per_page: type: integer example: 15 to: type: integer example: 1 total: type: integer example: 1 tags: - 'Location Management' /api/states: get: summary: 'List all states' operationId: listAllStates description: 'Get a list of all states with their associated countries.' parameters: - in: query name: 'filter[name]' description: 'Filter states by name.' example: Selangor required: false schema: type: string description: 'Filter states by name.' example: Selangor - in: query name: 'filter[country_id]' description: 'Filter states by country ID.' example: 1 required: false schema: type: integer description: 'Filter states by country ID.' example: 1 - in: query name: include description: 'Include related models (Possible values: country).' example: country required: false schema: type: string description: 'Include related models (Possible values: country).' example: country - in: query name: per_page description: 'Number of records to display per page.' example: 15 required: false schema: type: integer description: 'Number of records to display per page.' example: 15 responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 name: Selangor country: id: 1 name: Malaysia nationality: Malaysian iso: MY iso3: MYS phone_code: '60' links: first: '@{{$baseUrl}}/api/states?page=1' last: '@{{$baseUrl}}/api/states?page=5' prev: null next: '@{{$baseUrl}}/api/states?page=2' meta: current_page: 1 from: 1 last_page: 5 path: '@{{$baseUrl}}/api/states' per_page: 15 to: 15 total: 75 properties: data: type: array example: - id: 1 name: Selangor country: id: 1 name: Malaysia nationality: Malaysian iso: MY iso3: MYS phone_code: '60' items: type: object properties: id: type: integer example: 1 name: type: string example: Selangor country: type: object properties: id: type: integer example: 1 name: type: string example: Malaysia nationality: type: string example: Malaysian iso: type: string example: MY iso3: type: string example: MYS phone_code: type: string example: '60' links: type: object properties: first: type: string example: '@{{$baseUrl}}/api/states?page=1' last: type: string example: '@{{$baseUrl}}/api/states?page=5' prev: type: string example: null nullable: true next: type: string example: '@{{$baseUrl}}/api/states?page=2' meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 5 path: type: string example: '@{{$baseUrl}}/api/states' per_page: type: integer example: 15 to: type: integer example: 15 total: type: integer example: 75 tags: - 'Location Management' /api/cities: get: summary: 'List all cities' operationId: listAllCities description: 'Get a list of all cities with their associated states.' parameters: - in: query name: 'filter[name]' description: 'Filter cities by name.' example: 'Petaling Jaya' required: false schema: type: string description: 'Filter cities by name.' example: 'Petaling Jaya' - in: query name: 'filter[state_id]' description: 'Filter cities by state ID.' example: 1 required: false schema: type: integer description: 'Filter cities by state ID.' example: 1 - in: query name: include description: 'Include related models (Possible values: state, state.country).' example: 'state,state.country' required: false schema: type: string description: 'Include related models (Possible values: state, state.country).' example: 'state,state.country' - in: query name: sort description: 'Sort by specified field. Prefix with `-` for descending sort. Possible values: name' example: '-name' required: false schema: type: string description: 'Sort by specified field. Prefix with `-` for descending sort. Possible values: name' example: '-name' - in: query name: per_page description: 'Number of records to display per page.' example: 15 required: false schema: type: integer description: 'Number of records to display per page.' example: 15 responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 name: 'Petaling Jaya' state: id: 1 name: Selangor country: id: 1 name: Malaysia nationality: Malaysian iso: MY iso3: MYS phone_code: '60' links: first: '@{{$baseUrl}}/api/cities?page=1' last: '@{{$baseUrl}}/api/cities?page=1' prev: null next: null meta: current_page: 1 from: 1 last_page: 1 links: - url: null label: '« Previous' active: false - url: '@{{$baseUrl}}/api/cities?page=1' label: '1' active: true - url: null label: 'Next »' active: false path: '@{{$baseUrl}}/api/cities' per_page: 15 to: 1 total: 1 properties: data: type: array example: - id: 1 name: 'Petaling Jaya' state: id: 1 name: Selangor country: id: 1 name: Malaysia nationality: Malaysian iso: MY iso3: MYS phone_code: '60' items: type: object properties: id: type: integer example: 1 name: type: string example: 'Petaling Jaya' state: type: object properties: id: type: integer example: 1 name: type: string example: Selangor country: type: object properties: id: type: integer example: 1 name: type: string example: Malaysia nationality: type: string example: Malaysian iso: type: string example: MY iso3: type: string example: MYS phone_code: type: string example: '60' links: type: object properties: first: type: string example: '@{{$baseUrl}}/api/cities?page=1' last: type: string example: '@{{$baseUrl}}/api/cities?page=1' prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 1 links: type: array example: - url: null label: '« Previous' active: false - url: '@{{$baseUrl}}/api/cities?page=1' label: '1' active: true - url: null label: 'Next »' active: false items: type: object properties: url: type: string example: null nullable: true label: type: string example: '« Previous' active: type: boolean example: false path: type: string example: '@{{$baseUrl}}/api/cities' per_page: type: integer example: 15 to: type: integer example: 1 total: type: integer example: 1 tags: - 'Location Management' /api/sso/redirect: post: summary: 'Generate a signed SSO login URL for a customer.' operationId: generateASignedSSOLoginURLForACustomer description: "Verifies the HMAC-signed request, finds or creates the customer by phone number,\nthen returns a temporary signed URL that logs the customer into the loyalty portal.\nThe signed URL is valid for 10 minutes." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: redirect_url: 'https://explore.pixalink.io/space/1/loyalty/login/42?expires=1746518400&signature=abc123...' properties: redirect_url: type: string example: 'https://explore.pixalink.io/space/1/loyalty/login/42?expires=1746518400&signature=abc123...' description: 'Temporary signed URL that logs the customer into the loyalty portal.' 401: description: 'Missing or invalid authentication credentials' content: application/json: schema: type: object example: message: 'Invalid signature.' properties: message: type: string example: 'Invalid signature.' 403: description: 'Client is not configured for SSO or OAuth2 is disabled' content: application/json: schema: type: object example: message: 'Client is not configured for SSO.' properties: message: type: string example: 'Client is not configured for SSO.' 404: description: 'SSO feature disabled for this deployment' content: application/json: schema: type: object example: message: 'Not Found' properties: message: type: string example: 'Not Found' 422: description: 'Validation failed' content: application/json: schema: type: object example: message: 'The customer phone field is required.' errors: customer_phone: - 'The customer phone field is required.' properties: message: type: string example: 'The customer phone field is required.' errors: type: object properties: customer_phone: type: array example: - 'The customer phone field is required.' items: type: string tags: - SSO requestBody: required: true content: application/json: schema: type: object properties: customer_phone: type: string description: 'Phone number of the customer in E.164 format. Must match the regex /^\+[1-9]\d{7,14}$/.' example: '+60123456789' space_id: type: integer description: 'ID of the Pixalink space. Must belong to your organisation. The id of an existing record in the spaces table.' example: 42 timestamp: type: integer description: 'Current Unix timestamp in seconds. Must be within ±60s of server time.' example: 1746518400 redirect_to: type: string description: 'Optional page to land on after login. Defaults to home.' example: rewards enum: - home - profile - rewards - points - credits - membership - order - reservations - referral required: - customer_phone - space_id - timestamp /api/sso/resolve: post: summary: 'Resolve an outbound SSO ticket.' operationId: resolveAnOutboundSSOTicket description: "Verifies the HMAC-signed request, then exchanges a single-use ticket for the customer\nidentity it was minted against. The ticket is consumed on first use, so a second call\nwith the same ticket returns 410. Only the organisation that owns the ticket may resolve it." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: customer: id: 42 name: 'Jane Doe' phone_number: '+60123456789' email: jane@example.com space_id: 1 organisation_id: 7 context: { } properties: customer: type: object properties: id: type: integer example: 42 name: type: string example: 'Jane Doe' phone_number: type: string example: '+60123456789' email: type: string example: jane@example.com space_id: type: integer example: 1 organisation_id: type: integer example: 7 description: 'The resolved customer identity.' context: type: object properties: { } description: 'Any context stored when the ticket was minted (e.g. an outlet or table reference).' 401: description: 'Missing or invalid HMAC credentials' content: application/json: schema: type: object example: message: 'Invalid signature.' properties: message: type: string example: 'Invalid signature.' 410: description: 'Ticket not found, already used, or expired' content: application/json: schema: type: object example: message: 'Ticket not found, already used, or expired.' properties: message: type: string example: 'Ticket not found, already used, or expired.' tags: - SSO requestBody: required: true content: application/json: schema: type: object properties: ticket: type: string description: 'The opaque ticket received on the redirect URL.' example: kP3x…opaque…9Qz timestamp: type: integer description: 'Current Unix timestamp in seconds. Must be within ±60s of server time.' example: 1746518400 required: - ticket - timestamp