{"openapi":"3.1.0","info":{"title":"KatalystOS Partner API","description":"REST API of the KatalystOS restaurant platform.","contact":{"name":"KatalystOS developers","url":"https://developers.katalystos.com"},"license":{"name":"Proprietary","url":"https://katalystos.com"},"version":"1.0"},"servers":[{"url":"https://api.katalystos.com","description":"Production"},{"url":"https://dev-api.katalystos.com","description":"Sandbox"}],"paths":{"/v1/external/restaurant/{restaurant_id}/reports/menu-item-sales":{"get":{"tags":["Sales [Reports]"],"summary":"Menu item sales (product mix)","description":"Per menu item and sales unit: quantity sold, prices, plate cost, discounts and margins, for closed checks in the period. Money is in dollars. Dates are in the restaurant's timezone.","operationId":"MenuItemSales_index","security":[{"AuthToken":[]}],"parameters":[{"name":"restaurant_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/MongoId"}},{"name":"start_at","in":"query","description":"First day, `m/d/Y`. Defaults to today.","required":false,"schema":{"type":"string","example":"11/01/2026"}},{"name":"end_at","in":"query","description":"Last day, `m/d/Y`, not before start_at.","required":false,"schema":{"type":"string","example":"11/05/2026"}},{"name":"rc_id","in":"query","description":"Limit to one revenue center.","required":false,"schema":{"type":"string"}},{"name":"sub_category_ids","in":"query","description":"Comma-separated sub-category ids.","required":false,"schema":{"type":"string"}},{"name":"modifier_ids","in":"query","description":"Comma-separated modifier ids.","required":false,"schema":{"type":"string"}},{"name":"service_area_ids","in":"query","description":"Comma-separated service area ids.","required":false,"schema":{"type":"string"}},{"name":"sort_by","in":"query","description":"Item field to sort by. Default `item_name`.","required":false,"schema":{"type":"string","example":"qty"}},{"name":"sort_direction","in":"query","required":false,"schema":{"type":"string","enum":["asc","desc"]}},{"name":"with_id","in":"query","description":"Also return item, unit and sales-category ids.","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"Report rows.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"start_at":{"type":"string","example":"2026-11-01 00:00:00"},"end_at":{"type":"string","example":"2026-11-05 23:59:59"},"items":{"type":"array","items":{"properties":{"item_name":{"type":"string","example":"Cheeseburger"},"item_unit":{"type":"string","example":"Regular"},"item_sales_category_name":{"type":"string","example":"Food"},"qty":{"description":"Items sold, comped included.","type":"number","example":42},"item_price":{"type":"number","example":12.5},"plate_cost":{"type":"number","example":4.1},"gross_amount":{"description":"qty * item_price","type":"number","example":525},"gross_margin":{"description":"item_price - plate_cost","type":"number","example":8.4},"discount_amount":{"description":"Discounts on this item, total.","type":"number","example":25},"discounted_price":{"description":"Unit price after discounts.","type":"number","example":11.9},"net_margin":{"description":"discounted_price - plate_cost","type":"number","example":7.8},"net_amount":{"description":"qty * discounted_price","type":"number","example":499.8},"item_id":{"description":"With with_id=1.","type":"string"},"item_unit_id":{"description":"With with_id=1.","type":"string"},"item_sales_category_id":{"description":"With with_id=1.","type":"string"}},"type":"object"}}},"type":"object"}]}}}},"403":{"description":"The token lacks a required permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}}},"x-validation":["validate"],"x-audience":"partner","x-permissions":["cogs-well.view"]}},"/v1/integration/loyalty/card":{"post":{"tags":["Cards [Loyalty]"],"summary":"Create a card for an external card number","description":"Creates the loyalty card for `number` in the restaurant's loyalty program, or returns the existing one. The external number is the card's identity; sending it again is safe (idempotent). `provider` is updated if it differs.","operationId":"Card_store","security":[{"AuthToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"required":["number"],"properties":{"number":{"description":"Card number in the partner's system.","type":"string","maxLength":30,"example":"WD-000123456"},"provider":{"description":"External loyalty provider. Defaults to `wedu`.","type":"string","example":"wedu"},"cardholder_email":{"type":"string","format":"email","example":"ann@example.com"},"cardholder_phone":{"type":"string","maxLength":15,"example":"5551234567"},"cardholder_first_name":{"type":"string","maxLength":40,"example":"Ann"},"cardholder_last_name":{"type":"string","maxLength":40,"example":"Lee"},"restaurant_id":{"$ref":"#/components/schemas/MongoId","description":"Restaurant whose loyalty program to use. Required when the token is not bound to a restaurant."}},"type":"object"}}}},"responses":{"200":{"description":"The created or existing card. On failure `success` is false and `errors` names the field (e.g. `card_id`: Loyalty card not found.).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoyaltyCardResponse"}}}},"401":{"description":"Missing or invalid token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}},"403":{"description":"The token lacks a required permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}}},"x-validation":["validate"],"x-audience":"partner","x-permissions":["integration.loyalty"]},"get":{"tags":["Cards [Loyalty]"],"summary":"Find a card","description":"Looks a card up by `number`, `cardholder_phone` or `cardholder_email`; send at least one. When several are sent, `number` wins, then phone, then email. Phone is matched on digits only, email case-insensitively. A phone or email that matches more than one card is an error: look it up by number.","operationId":"Card_show","security":[{"AuthToken":[]}],"parameters":[{"name":"number","in":"query","required":false,"schema":{"type":"string","maxLength":30}},{"name":"cardholder_phone","in":"query","required":false,"schema":{"type":"string","maxLength":15}},{"name":"cardholder_email","in":"query","required":false,"schema":{"type":"string","format":"email"}},{"name":"restaurant_id","in":"query","description":"Restaurant whose loyalty program to use. Required when the token is not bound to a restaurant.","required":false,"schema":{"$ref":"#/components/schemas/MongoId"}}],"responses":{"200":{"description":"The card. On failure `success` is false and `errors` names the field (e.g. `card_id`: Loyalty card not found.).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoyaltyCardResponse"}}}},"401":{"description":"Missing or invalid token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}},"403":{"description":"The token lacks a required permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}}},"x-validation":["validate"],"x-audience":"partner","x-permissions":["integration.loyalty"]},"put":{"tags":["Cards [Loyalty]"],"summary":"Correct a card number, finding the card by email","description":"Same as the card-id version, for when you don't have our card id. The card is found by `cardholder_email`; if the email matches more than one card, use the card-id endpoint.","operationId":"Card_updateByEmail","security":[{"AuthToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"required":["cardholder_email","number"],"properties":{"cardholder_email":{"type":"string","format":"email","example":"ann@example.com"},"number":{"type":"string","maxLength":30,"example":"WD-000123465"},"restaurant_id":{"$ref":"#/components/schemas/MongoId","description":"Restaurant whose loyalty program to use. Required when the token is not bound to a restaurant."}},"type":"object"}}}},"responses":{"200":{"description":"The updated card. On failure `success` is false and `errors` names the field (e.g. `card_id`: Loyalty card not found.).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoyaltyCardResponse"}}}},"401":{"description":"Missing or invalid token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}},"403":{"description":"The token lacks a required permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}}},"x-validation":["validate"],"x-audience":"partner","x-permissions":["integration.loyalty"]}},"/v1/integration/loyalty/card/{card_id}":{"put":{"tags":["Cards [Loyalty]"],"summary":"Correct a card number","description":"Replaces the card's `number`, e.g. one entered with a typo. Meant for corrections, not routine changes. Past operations keep the number they were recorded with. Fails if another card in the program already has the number.","operationId":"Card_update","security":[{"AuthToken":[]}],"parameters":[{"name":"card_id","in":"path","description":"Card `_id` as returned by the card endpoints.","required":true,"schema":{"$ref":"#/components/schemas/MongoId"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"required":["number"],"properties":{"number":{"type":"string","maxLength":30,"example":"WD-000123465"},"restaurant_id":{"$ref":"#/components/schemas/MongoId","description":"Restaurant whose loyalty program to use. Required when the token is not bound to a restaurant."}},"type":"object"}}}},"responses":{"200":{"description":"The updated card. On failure `success` is false and `errors` names the field (e.g. `card_id`: Loyalty card not found.).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoyaltyCardResponse"}}}},"401":{"description":"Missing or invalid token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}},"403":{"description":"The token lacks a required permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}}},"x-validation":["validate"],"x-audience":"partner","x-permissions":["integration.loyalty"]}},"/v1/integration/loyalty/card/{card_id}/punch":{"post":{"tags":["Rewards & Punches [Loyalty]"],"summary":"Add punches","description":"Adds punches to the card for an active punch-card program, e.g. for purchases made in your system. Completing the program issues its reward automatically.","operationId":"Card_punch","security":[{"AuthToken":[]}],"parameters":[{"name":"card_id","in":"path","description":"Card `_id` as returned by the card endpoints.","required":true,"schema":{"$ref":"#/components/schemas/MongoId"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"required":["punch_card_program_id"],"properties":{"punch_card_program_id":{"$ref":"#/components/schemas/MongoId"},"count":{"type":"integer","default":1,"maximum":100,"minimum":1},"restaurant_id":{"$ref":"#/components/schemas/MongoId","description":"Restaurant whose loyalty program to use. Required when the token is not bound to a restaurant."}},"type":"object"}}}},"responses":{"200":{"description":"Punch count after the call, and the card.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/LoyaltyCardResponse"},{"properties":{"punches":{"type":"integer","example":4},"target_count":{"type":"integer","example":10}},"type":"object"}]}}}},"401":{"description":"Missing or invalid token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}},"403":{"description":"The token lacks a required permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}}},"x-validation":["validate"],"x-audience":"partner","x-permissions":["integration.loyalty"]}},"/v1/integration/loyalty/card/{card_id}/rewards":{"get":{"tags":["Rewards & Punches [Loyalty]"],"summary":"List a card's rewards","description":"Rewards (coupons) the card has earned, newest first, at most 200.","operationId":"Card_rewards","security":[{"AuthToken":[]}],"parameters":[{"name":"card_id","in":"path","description":"Card `_id` as returned by the card endpoints.","required":true,"schema":{"$ref":"#/components/schemas/MongoId"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["issued","redeemed","expired","revoked"]}},{"name":"restaurant_id","in":"query","description":"Restaurant whose loyalty program to use. Required when the token is not bound to a restaurant.","required":false,"schema":{"$ref":"#/components/schemas/MongoId"}}],"responses":{"200":{"description":"The rewards.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/LoyaltyReward"}}},"type":"object"}]}}}},"401":{"description":"Missing or invalid token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}},"403":{"description":"The token lacks a required permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}}},"x-audience":"partner","x-permissions":["integration.loyalty"]}},"/v1/integration/loyalty/card/{card_id}/units":{"post":{"tags":["Units [Loyalty]"],"summary":"Record units (gallons, kWh, ...)","description":"Adds amounts to the card's per-code unit totals and records one ledger entry. Unit codes are yours to choose (`gallons`, `kwh`, ...), but may not contain `.` or start with `$`. Amounts are deltas and may be negative to correct an earlier entry, but no total may go below zero. Units do not change points or balance.","operationId":"Card_accrueUnits","security":[{"AuthToken":[]}],"parameters":[{"name":"card_id","in":"path","description":"Card `_id` as returned by the card endpoints.","required":true,"schema":{"$ref":"#/components/schemas/MongoId"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"required":["units"],"properties":{"units":{"description":"Unit code => amount.","type":"object","example":{"gallons":12.5},"additionalProperties":{"type":"number"}},"restaurant_id":{"$ref":"#/components/schemas/MongoId","description":"Restaurant whose loyalty program to use. Required when the token is not bound to a restaurant."}},"type":"object"}}}},"responses":{"200":{"description":"The card with updated `units` totals. On failure `success` is false and `errors` names the field (e.g. `card_id`: Loyalty card not found.).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoyaltyCardResponse"}}}},"401":{"description":"Missing or invalid token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}},"403":{"description":"The token lacks a required permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}}},"x-validation":["validate"],"x-audience":"partner","x-permissions":["integration.loyalty"]},"get":{"tags":["Units [Loyalty]"],"summary":"Units history","description":"Current per-code totals and the ledger entries, newest first. Each entry's `_id` is what the edit and delete endpoints take.","operationId":"Card_unitsHistory","security":[{"AuthToken":[]}],"parameters":[{"name":"card_id","in":"path","description":"Card `_id` as returned by the card endpoints.","required":true,"schema":{"$ref":"#/components/schemas/MongoId"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"maximum":200,"minimum":1}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"minimum":0}},{"name":"restaurant_id","in":"query","description":"Restaurant whose loyalty program to use. Required when the token is not bound to a restaurant.","required":false,"schema":{"$ref":"#/components/schemas/MongoId"}}],"responses":{"200":{"description":"Totals and entries.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"totals":{"type":"object","example":{"gallons":13.5},"additionalProperties":{"type":"number"}},"total":{"description":"Number of entries.","type":"integer","example":2},"items":{"type":"array","items":{"$ref":"#/components/schemas/LoyaltyUnitsEntry"}}},"type":"object"}]}}}},"401":{"description":"Missing or invalid token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}},"403":{"description":"The token lacks a required permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}}},"x-audience":"partner","x-permissions":["integration.loyalty"]}},"/v1/integration/loyalty/card/{card_id}/units/{operation_id}":{"put":{"tags":["Units [Loyalty]"],"summary":"Edit a units entry","description":"Rewrites one ledger entry in place: its amount for `code`, its code (`new_code`), or both. `amount` is this entry's own change, not the card total. Zero is refused (delete the entry instead), and so is any change that would take a total below zero.","operationId":"Card_updateUnit","security":[{"AuthToken":[]}],"parameters":[{"name":"card_id","in":"path","description":"Card `_id` as returned by the card endpoints.","required":true,"schema":{"$ref":"#/components/schemas/MongoId"}},{"name":"operation_id","in":"path","description":"Entry `_id` from the units history.","required":true,"schema":{"$ref":"#/components/schemas/MongoId"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"required":["code"],"properties":{"code":{"description":"Unit code of the entry to edit.","type":"string","maxLength":60,"example":"gallons"},"new_code":{"type":"string","maxLength":60,"example":"diesel_gallons"},"amount":{"type":"number","example":1},"restaurant_id":{"$ref":"#/components/schemas/MongoId","description":"Restaurant whose loyalty program to use. Required when the token is not bound to a restaurant."}},"type":"object"}}}},"responses":{"200":{"description":"The card with recalculated `units` totals. On failure `success` is false and `errors` names the field (e.g. `card_id`: Loyalty card not found.).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoyaltyCardResponse"}}}},"401":{"description":"Missing or invalid token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}},"403":{"description":"The token lacks a required permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}}},"x-validation":["validate"],"x-audience":"partner","x-permissions":["integration.loyalty"]},"delete":{"tags":["Units [Loyalty]"],"summary":"Delete a units entry","description":"Removes one ledger entry; the card's totals drop by what it recorded (all codes in it). Refused if that would take a total below zero.","operationId":"Card_deleteUnit","security":[{"AuthToken":[]}],"parameters":[{"name":"card_id","in":"path","description":"Card `_id` as returned by the card endpoints.","required":true,"schema":{"$ref":"#/components/schemas/MongoId"}},{"name":"operation_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/MongoId"}},{"name":"restaurant_id","in":"query","description":"Restaurant whose loyalty program to use. Required when the token is not bound to a restaurant.","required":false,"schema":{"$ref":"#/components/schemas/MongoId"}}],"responses":{"200":{"description":"The card with recalculated `units` totals. On failure `success` is false and `errors` names the field (e.g. `card_id`: Loyalty card not found.).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoyaltyCardResponse"}}}},"401":{"description":"Missing or invalid token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}},"403":{"description":"The token lacks a required permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}}},"x-validation":["validate"],"x-audience":"partner","x-permissions":["integration.loyalty"]}},"/v1/integration/wedu/loyalty-user":{"post":{"tags":["Member signup [Loyalty]"],"summary":"Enrol a member (WEDU)","description":"Finds the user by email or phone (or creates one), adds them to the restaurant, and creates their loyalty card with `external_card_number` as the card number. Calling it again for the same person returns the existing card.","operationId":"LoyaltyUser_store","security":[{"AuthToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"required":["email","phone","first_name","last_name","external_card_number"],"properties":{"email":{"type":"string","format":"email","example":"ann@example.com"},"phone":{"description":"10 digits.","type":"string","pattern":"^[0-9]{10}$","example":"5551234567"},"first_name":{"type":"string","maxLength":50,"example":"Ann"},"last_name":{"type":"string","maxLength":50,"example":"Lee"},"external_card_number":{"type":"string","maxLength":30,"example":"WD-000123456"},"restaurant_id":{"$ref":"#/components/schemas/MongoId","description":"Defaults to the token's restaurant."}},"type":"object"}}}},"responses":{"200":{"description":"The user and their card.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"user":{"properties":{"_id":{"$ref":"#/components/schemas/MongoId"},"email":{"type":"string"},"phone":{"type":"string"},"first_name":{"type":"string"},"last_name":{"type":"string"}},"type":"object"},"card":{"properties":{"_id":{"$ref":"#/components/schemas/MongoId"},"number":{"type":"string"},"external_card_number":{"type":"string"},"physical_card_number":{"type":["string","null"]},"cardholder_email":{"type":"string"},"cardholder_phone":{"type":"string"},"cardholder_first_name":{"type":"string"},"cardholder_last_name":{"type":"string"},"balance":{"type":"number"}},"type":"object"}},"type":"object"}]}}}},"401":{"description":"Missing or invalid token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}},"403":{"description":"The token lacks a required permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessDenied"}}}}},"x-validation":["validate"],"x-audience":"partner","x-permissions":["integration.wedu"]}}},"components":{"schemas":{"AccessDenied":{"description":"Missing/invalid token (401) or a token without the required permission (403).","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Access Denied"},"error_code":{"type":"integer","example":403}},"type":"object"},"MongoId":{"type":"string","pattern":"^[0-9a-f]{24}$","example":"65f1c2a9e4b0a1b2c3d4e5f6"},"Envelope":{"description":"Every response is wrapped in this envelope; the endpoint's own fields sit next to these.","required":["success"],"properties":{"success":{"type":"boolean","example":true},"restaurant_timezone":{"description":"Timezone of the restaurant bound to the token; dates in the response are in this zone.","type":["string","null"],"example":"America/New_York"},"fingerprint":{"description":"Fingerprint of the auth token that made the call.","type":["string","null"],"example":"b82418952c"},"response_at":{"description":"Server time of the response, `m/d/Y h:ia`.","type":"string","example":"02/26/2026 11:09pm"},"errors":{"description":"Present when success is false: field => list of messages.","type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}},"error_code":{"description":"Present on some failures, e.g. 101 validation.","type":"integer"}},"type":"object","additionalProperties":true},"LoyaltyCardResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"item":{"$ref":"#/components/schemas/LoyaltyCard"}},"type":"object"}]},"LoyaltyCard":{"properties":{"_id":{"$ref":"#/components/schemas/MongoId"},"number":{"type":"string","example":"WD-000123456"},"physical_card_number":{"type":["string","null"]},"provider":{"type":"string","example":"wedu"},"is_active":{"type":"boolean","example":true},"balance":{"description":"Stored-value balance, dollars.","type":"number","example":0},"points":{"type":"number","example":120},"points_lifetime":{"type":"number","example":480},"units":{"description":"Unit code => running total.","type":["object","null"],"example":{"gallons":13.5},"additionalProperties":{"type":"number"}},"punches":{"description":"Punch-card program id => punches so far.","type":["object","null"],"additionalProperties":{"type":"integer"}},"cardholder_email":{"type":["string","null"],"format":"email"},"cardholder_phone":{"description":"Digits only.","type":["string","null"]},"cardholder_first_name":{"type":["string","null"]},"cardholder_last_name":{"type":["string","null"]}},"type":"object"},"LoyaltyUnitsEntry":{"properties":{"_id":{"$ref":"#/components/schemas/MongoId"},"operation":{"type":"string","example":"accrue_units"},"units":{"description":"What this entry recorded.","type":"object","example":{"gallons":12.5},"additionalProperties":{"type":"number"}},"units_start":{"description":"Totals before the entry.","type":["object","null"],"additionalProperties":{"type":"number"}},"units_end":{"description":"Totals after the entry.","type":["object","null"],"additionalProperties":{"type":"number"}},"payment_id":{"type":["string","null"]},"check_id":{"type":["string","null"]},"restaurant_id":{"type":["string","null"]},"created_at":{"type":["string","null"],"example":"2026-11-02 14:05:11"}},"type":"object"},"LoyaltyReward":{"properties":{"_id":{"$ref":"#/components/schemas/MongoId"},"code":{"type":"string","example":"K7Q2-9XPA"},"name":{"type":"string","example":"Free coffee"},"type":{"type":"string"},"value":{"type":"number"},"source":{"type":"string","example":"punch_card"},"status":{"type":"string","enum":["issued","redeemed","expired","revoked"]},"expires_at":{"type":["string","null"]},"redeemed_at":{"type":["string","null"]},"created_at":{"type":"string"}},"type":"object"}},"securitySchemes":{"AuthToken":{"type":"apiKey","description":"Auth token from login, or an app-to-app token issued by KatalystOS for an integration. What it may call is limited by its permissions (`x-permissions` on each operation).","name":"X-KATALYST-AUTH-TOKEN","in":"header"}}},"tags":[{"name":"Cards [Loyalty]","description":"Link your customers' cards to a KatalystOS loyalty program and look them up."},{"name":"Member signup [Loyalty]","description":"Enrol a customer in a restaurant's loyalty program from your system."},{"name":"Rewards & Punches [Loyalty]","description":"Punch-card progress and the rewards a card has earned."},{"name":"Sales [Reports]","description":"Sales figures for a restaurant you have been given access to."},{"name":"Units [Loyalty]","description":"Store free-form quantities (gallons, kWh, visits, ...) against a card, with an editable ledger."}],"x-tagGroups":[{"name":"Loyalty","tags":["Cards [Loyalty]","Member signup [Loyalty]","Rewards & Punches [Loyalty]","Units [Loyalty]"]},{"name":"Reports","tags":["Sales [Reports]"]}]}