{
  "openapi": "3.0.3",
  "info": {
    "title": "Remy Public API",
    "version": "1.0.0",
    "description": "Public integration endpoints for sending orders and managing reward coupon codes in Remy."
  },
  "servers": [
    {
      "url": "https://api.remyrewards.co.uk",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Authentication",
      "description": "Verify integration credentials."
    },
    {
      "name": "Orders",
      "description": "Send completed orders to Remy."
    },
    {
      "name": "Rewards",
      "description": "Manage issued rewards."
    },
    {
      "name": "Vouchers",
      "description": "Read and redeem issued customer vouchers"
    }
  ],
  "paths": {
    "/v1/public/auth": {
      "get": {
        "tags": ["Authentication"],
        "operationId": "verifyApiKey",
        "summary": "Verify an API key",
        "description": "Verifies the API key and returns the merchant connected to the integration",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The API key is valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthResponse"
                },
                "example": {
                  "success": true,
                  "merchant": "Example Coffee"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/public/orders/create": {
      "post": {
        "tags": ["Orders"],
        "operationId": "createOrder",
        "summary": "Process an order",
        "description": "Sends a completed order to Remy. Depending on the merchant's integration settings, the order can award stamps or points, issue rewards, and redeem an existing coupon",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateOrderRequest"
              },
              "example": {
                "customer_email": "alex@example.com",
                "order_value": 24.5,
                "order_number": "ORDER-1042",
                "order_source": "Shopify",
                "first_name": "Alex",
                "name": "Alex Morgan",
                "coupon_code": "WELCOME10"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The order was processed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateOrderResponse"
                },
                "example": {
                  "success": true,
                  "stamps": 2,
                  "rewards": [],
                  "points": 24,
                  "couponRedeemed": false
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/public/rewards/update-coupon": {
      "post": {
        "tags": ["Rewards"],
        "operationId": "updateRewardCoupon",
        "summary": "Update a reward coupon code",
        "description": "Sets the coupon code on a reward belonging to the authenticated merchant. Redeemed or expired rewards cannot be updated. Coupon codes are stored in uppercase.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateRewardCouponRequest"
              },
              "example": {
                "userRewardId": "66c735e253f2836fbe941234",
                "couponCode": "summer20"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The coupon code was updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateRewardCouponResponse"
                },
                "example": {
                  "success": true,
                  "userRewardId": "66c735e253f2836fbe941234",
                  "couponCode": "SUMMER20"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/public/vouchers": {
      "get": {
        "tags": ["Vouchers"],
        "operationId": "listIssuedVouchers",
        "summary": "Get vouchers",
        "description": "Lists issued customer voucher instances belonging to the authenticated merchant. This endpoint does not return merchant voucher templates.",
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": { "type": "integer", "minimum": 1, "default": 1 },
            "description": "Page number."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 10 },
            "description": "Number of vouchers per page."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["pending", "active", "expired", "redeemed", "cancelled", "partial"]
            },
            "description": "Filter by issued-voucher status."
          },
          {
            "name": "search",
            "in": "query",
            "schema": { "type": "string" },
            "description": "Search voucher codes and titles."
          }
        ],
        "responses": {
          "200": {
            "description": "Issued vouchers retrieved.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/IssuedVoucherListResponse" },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "66c735e253f2836fbe941234",
                      "code": "GIFT-8HD42K",
                      "title": "£25 Gift Voucher",
                      "value": 25,
                      "remainingValue": 15,
                      "currency": "GBP",
                      "status": "partial",
                      "allowPartialRedemption": true,
                      "expiry": "2027-08-23T23:59:59.000Z",
                      "recipient": { "name": "Alex Morgan", "email": "alex@example.com" },
                      "redemptions": [{ "amount": 10, "type": "partial", "date": "2026-08-23T11:00:00.000Z" }],
                      "createdAt": "2026-08-01T09:00:00.000Z",
                      "updatedAt": "2026-08-23T11:00:00.000Z"
                    }
                  ],
                  "pagination": {
                    "currentPage": 1,
                    "totalPages": 1,
                    "totalItems": 1,
                    "itemsPerPage": 10,
                    "hasNextPage": false,
                    "hasPrevPage": false
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/public/vouchers/{id}": {
      "get": {
        "tags": ["Vouchers"],
        "operationId": "getIssuedVoucher",
        "summary": "Get voucher by ID",
        "description": "Returns one issued customer voucher belonging to the authenticated merchant.",
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [{ "$ref": "#/components/parameters/VoucherId" }],
        "responses": {
          "200": {
            "description": "Issued voucher retrieved.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/IssuedVoucherResponse" },
                "example": {
                  "success": true,
                  "data": {
                    "id": "66c735e253f2836fbe941234",
                    "code": "GIFT-8HD42K",
                    "title": "£25 Gift Voucher",
                    "value": 25,
                    "remainingValue": 25,
                    "currency": "GBP",
                    "status": "active",
                    "allowPartialRedemption": true,
                    "expiry": "2027-08-23T23:59:59.000Z",
                    "recipient": { "name": "Alex Morgan", "email": "alex@example.com" },
                    "redemptions": [],
                    "createdAt": "2026-08-01T09:00:00.000Z",
                    "updatedAt": "2026-08-01T09:00:00.000Z"
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/public/vouchers/{id}/pdf": {
      "get": {
        "tags": ["Vouchers"],
        "operationId": "downloadIssuedVoucherPdf",
        "summary": "Get voucher PDF",
        "description": "Generates and downloads a PDF for an issued customer voucher.",
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [{ "$ref": "#/components/parameters/VoucherId" }],
        "responses": {
          "200": {
            "description": "Voucher PDF.",
            "headers": {
              "Content-Disposition": {
                "description": "Attachment filename for the voucher PDF.",
                "schema": { "type": "string" }
              }
            },
            "content": {
              "application/pdf": {
                "schema": { "type": "string", "format": "binary" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalServerError" }
        }
      }
    },
    "/v1/public/vouchers/{id}/redeem": {
      "post": {
        "tags": ["Vouchers"],
        "operationId": "redeemIssuedVoucher",
        "summary": "Redeem voucher",
        "description": "Redeems an issued customer voucher. Omit `amount` to redeem the full remaining value. Supply a positive amount smaller than the remaining value for a partial redemption; the voucher must allow partial redemption.",
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [{ "$ref": "#/components/parameters/VoucherId" }],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/RedeemVoucherRequest" },
              "example": { "amount": 10 }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Voucher redeemed in full or in part.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/RedeemVoucherResponse" },
                "example": {
                  "success": true,
                  "redeemedAmount": 10,
                  "data": {
                    "id": "66c735e253f2836fbe941234",
                    "code": "GIFT-8HD42K",
                    "title": "£25 Gift Voucher",
                    "value": 25,
                    "remainingValue": 15,
                    "currency": "GBP",
                    "status": "partial",
                    "allowPartialRedemption": true,
                    "expiry": "2027-08-23T23:59:59.000Z",
                    "recipient": { "name": "Alex Morgan", "email": "alex@example.com" },
                    "redemptions": [{ "amount": 10, "type": "partial", "date": "2026-08-23T11:00:00.000Z" }],
                    "createdAt": "2026-08-01T09:00:00.000Z",
                    "updatedAt": "2026-08-23T11:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "The API key from the merchant's Remy integration settings."
      }
    },
    "parameters": {
      "VoucherId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The issued voucher's MongoDB `_id`.",
        "schema": {
          "type": "string",
          "pattern": "^[a-fA-F0-9]{24}$",
          "example": "66c735e253f2836fbe941234"
        }
      }
    },
    "schemas": {
      "AuthResponse": {
        "type": "object",
        "required": ["success", "merchant"],
        "properties": {
          "success": {
            "type": "boolean",
            "description": "Whether authentication succeeded."
          },
          "merchant": {
            "type": "string",
            "description": "The name of the connected merchant."
          }
        }
      },
      "CreateOrderRequest": {
        "type": "object",
        "required": ["customer_email", "order_value", "order_source"],
        "properties": {
          "customer_email": {
            "type": "string",
            "format": "email",
            "description": "Email address used to find or create the customer."
          },
          "order_value": {
            "oneOf": [{ "type": "number" }, { "type": "string" }],
            "description": "Order value as a number or a numeric string."
          },
          "order_number": {
            "type": "string",
            "description": "Source-system order identifier. Remy generates an identifier when this is omitted or invalid."
          },
          "order_source": {
            "type": "string",
            "pattern": "^[a-zA-Z0-9]+$",
            "description": "Source system name containing letters and numbers only."
          },
          "first_name": {
            "type": "string",
            "description": "Customer's first name."
          },
          "name": {
            "type": "string",
            "description": "Customer's full name."
          },
          "coupon_code": {
            "type": "string",
            "description": "Coupon to redeem when reward automation is enabled."
          }
        }
      },
      "CreateOrderResponse": {
        "type": "object",
        "required": ["success", "stamps", "rewards", "points", "couponRedeemed"],
        "properties": {
          "success": { "type": "boolean" },
          "stamps": {
            "type": "integer",
            "description": "Number of stamps awarded."
          },
          "rewards": {
            "type": "array",
            "description": "Rewards issued while processing the order.",
            "items": { "type": "object", "additionalProperties": true }
          },
          "points": {
            "type": "number",
            "description": "Number of points awarded."
          },
          "couponRedeemed": {
            "type": "boolean",
            "description": "Whether the supplied coupon matched and redeemed an active reward."
          }
        }
      },
      "UpdateRewardCouponRequest": {
        "type": "object",
        "required": ["userRewardId", "couponCode"],
        "properties": {
          "userRewardId": {
            "type": "string",
            "description": "Identifier of the issued customer reward."
          },
          "couponCode": {
            "type": "string",
            "description": "Coupon code to attach to the reward. Remy converts it to uppercase."
          }
        }
      },
      "UpdateRewardCouponResponse": {
        "type": "object",
        "required": ["success", "userRewardId", "couponCode"],
        "properties": {
          "success": { "type": "boolean" },
          "userRewardId": {
            "type": "string",
            "description": "Identifier of the updated reward."
          },
          "couponCode": {
            "type": "string",
            "description": "The stored uppercase coupon code."
          }
        }
      },
      "IssuedVoucher": {
        "type": "object",
        "required": [
          "id",
          "code",
          "title",
          "value",
          "remainingValue",
          "status",
          "allowPartialRedemption",
          "recipient",
          "redemptions",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Identifier of this issued voucher instance."
          },
          "code": {
            "type": "string",
            "description": "Human-readable voucher code."
          },
          "title": {
            "type": "string",
            "description": "Voucher title captured for the issued instance."
          },
          "value": {
            "type": "number",
            "description": "Original voucher value."
          },
          "remainingValue": {
            "type": "number",
            "description": "Value still available to redeem."
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217 currency code."
          },
          "status": {
            "type": "string",
            "enum": ["pending", "active", "expired", "redeemed", "cancelled", "partial"]
          },
          "allowPartialRedemption": {
            "type": "boolean",
            "description": "Whether the voucher can be redeemed over multiple transactions."
          },
          "expiry": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "recipient": {
            "$ref": "#/components/schemas/VoucherRecipient"
          },
          "redemptions": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/VoucherRedemption" }
          },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "VoucherRecipient": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "email": { "type": "string", "format": "email" }
        }
      },
      "VoucherRedemption": {
        "type": "object",
        "required": ["amount", "type", "date"],
        "properties": {
          "amount": { "type": "number" },
          "type": { "type": "string", "enum": ["full", "partial"] },
          "date": { "type": "string", "format": "date-time" }
        }
      },
      "Pagination": {
        "type": "object",
        "required": ["currentPage", "totalPages", "totalItems", "itemsPerPage", "hasNextPage", "hasPrevPage"],
        "properties": {
          "currentPage": { "type": "integer" },
          "totalPages": { "type": "integer" },
          "totalItems": { "type": "integer" },
          "itemsPerPage": { "type": "integer" },
          "hasNextPage": { "type": "boolean" },
          "hasPrevPage": { "type": "boolean" }
        }
      },
      "IssuedVoucherResponse": {
        "type": "object",
        "required": ["success", "data"],
        "properties": {
          "success": { "type": "boolean" },
          "data": { "$ref": "#/components/schemas/IssuedVoucher" }
        }
      },
      "IssuedVoucherListResponse": {
        "type": "object",
        "required": ["success", "data", "pagination"],
        "properties": {
          "success": { "type": "boolean" },
          "data": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/IssuedVoucher" }
          },
          "pagination": { "$ref": "#/components/schemas/Pagination" }
        }
      },
      "RedeemVoucherRequest": {
        "type": "object",
        "properties": {
          "amount": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Amount to redeem. Omit to redeem the full remaining value."
          }
        }
      },
      "RedeemVoucherResponse": {
        "type": "object",
        "required": ["success", "redeemedAmount", "data"],
        "properties": {
          "success": { "type": "boolean" },
          "redeemedAmount": { "type": "number" },
          "data": { "$ref": "#/components/schemas/IssuedVoucher" }
        }
      },
      "Error": {
        "type": "object",
        "required": ["code", "message"],
        "properties": {
          "code": { "type": "integer" },
          "message": { "type": "string" }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request is missing a required value or contains invalid data.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": {
              "code": 400,
              "message": "Customer email not provided"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "The API key is missing or invalid.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": {
              "code": 401,
              "message": "Invalid API key"
            }
          }
        }
      },
      "NotFound": {
        "description": "The requested integration resource was not found.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": {
              "code": 404,
              "message": "Merchant not found"
            }
          }
        }
      },
      "InternalServerError": {
        "description": "The voucher PDF could not be generated.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": {
              "code": 500,
              "message": "Failed to generate voucher PDF"
            }
          }
        }
      }
    }
  }
}
