{
  "openapi": "3.0.3",
  "info": {
    "title": "BackPerk Kassen-API",
    "version": "1.0.0",
    "description": "Kassen-API für lokale BackPerk-Partner. Ausführliche Doku: https://backperk.com/partner/api\n\nAlle POST-Aufrufe brauchen zusätzlich den Header BackPerk-Signature: t=<unix>,v1=hex(HMAC-SHA256(Geheimnis, t + '.' + Rohdaten)), höchstens 5 Minuten alt."
  },
  "servers": [
    {
      "url": "https://backperk.com/api/partner/v1"
    }
  ],
  "security": [
    {
      "bearer": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "bp_live_… oder bp_test_…"
      },
      "signature": {
        "type": "apiKey",
        "in": "header",
        "name": "BackPerk-Signature",
        "description": "t=<Unix-Zeit>,v1=<HEX HMAC-SHA256(Signatur-Geheimnis, t + '.' + Rohdaten)>"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "example": "insufficient_balance"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "Transaction": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "tx_5hd91k2m8qzc"
          },
          "receipt_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "available",
              "held",
              "cancelled",
              "expired"
            ]
          },
          "points": {
            "type": "integer"
          },
          "refunded_points": {
            "type": "integer"
          },
          "breakdown": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string",
                  "enum": [
                    "base",
                    "coupon"
                  ]
                },
                "coupon_id": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "points": {
                  "type": "integer"
                }
              }
            }
          },
          "cost_eur": {
            "type": "number"
          },
          "balance_eur": {
            "type": "number",
            "nullable": true
          },
          "test": {
            "type": "boolean"
          },
          "receipt_text": {
            "type": "string"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "sku": {
                  "type": "string"
                },
                "gtin": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "coupon_points": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "Redemption": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "rd_7k2p0m4x9qa1"
          },
          "status": {
            "type": "string",
            "enum": [
              "reserved",
              "captured",
              "released",
              "expired"
            ]
          },
          "points": {
            "type": "integer"
          },
          "value_eur": {
            "type": "number"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "receipt_text": {
            "type": "string"
          }
        }
      }
    }
  },
  "paths": {
    "/cards/lookup": {
      "post": {
        "summary": "Kundenkarte prüfen",
        "security": [
          {
            "bearer": [],
            "signature": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "card"
                ],
                "properties": {
                  "card": {
                    "type": "string",
                    "pattern": "^999\\d{13}$",
                    "example": "9990123456789018"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Karte gültig",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "valid": {
                      "type": "boolean"
                    },
                    "customer_token": {
                      "type": "string"
                    },
                    "display_name": {
                      "type": "string",
                      "example": "Julia M."
                    },
                    "coupons": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "label": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Karte unbekannt oder gesperrt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Schlüssel oder Signatur ungültig",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Ungültige Eingabe oder fachlicher Fehler",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Limit erreicht",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/transactions": {
      "post": {
        "summary": "Einkauf buchen (idempotent über receipt_id)",
        "security": [
          {
            "bearer": [],
            "signature": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "card",
                  "receipt_id",
                  "amount"
                ],
                "properties": {
                  "card": {
                    "type": "string",
                    "example": "9990123456789018"
                  },
                  "receipt_id": {
                    "type": "string",
                    "maxLength": 64
                  },
                  "amount": {
                    "type": "number",
                    "minimum": 0.01,
                    "maximum": 10000
                  },
                  "eligible_amount": {
                    "type": "number"
                  },
                  "currency": {
                    "type": "string",
                    "enum": [
                      "EUR"
                    ]
                  },
                  "occurred_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "store_id": {
                    "type": "integer"
                  },
                  "items": {
                    "type": "array",
                    "maxItems": 500,
                    "description": "Warenkorb (optional) – nötig für Coupons auf Kategorien, Marken oder Artikel. Summe der total = amount (±0,05).",
                    "items": {
                      "type": "object",
                      "required": [
                        "total"
                      ],
                      "properties": {
                        "sku": {
                          "type": "string"
                        },
                        "gtin": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "category": {
                          "type": "string",
                          "example": "Getränke > Wasser"
                        },
                        "brand": {
                          "type": "string"
                        },
                        "quantity": {
                          "type": "number",
                          "default": 1
                        },
                        "unit_price": {
                          "type": "number"
                        },
                        "total": {
                          "type": "number"
                        },
                        "eligible": {
                          "type": "boolean",
                          "default": true
                        }
                      }
                    }
                  },
                  "store_ref": {
                    "type": "string",
                    "maxLength": 64,
                    "description": "Eigene Filialnummer (alternativ zu store_id)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Gebucht",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transaction"
                }
              }
            }
          },
          "200": {
            "description": "Bereits gebucht (Wiederholung)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transaction"
                }
              }
            }
          },
          "402": {
            "description": "Buchungen gesperrt (Guthaben)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Partner inaktiv / eigene Karte",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Karte unbekannt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "receipt_conflict / possible_duplicate",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Schlüssel oder Signatur ungültig",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Ungültige Eingabe oder fachlicher Fehler",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Limit erreicht",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "summary": "Buchungen abrufen",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Liste",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Transaction"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Schlüssel oder Signatur ungültig",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/transactions/{id}/refunds": {
      "post": {
        "summary": "Retoure (ganz oder anteilig)",
        "security": [
          {
            "bearer": [],
            "signature": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount": {
                    "type": "number"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Zurückgebucht",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "refunded_points": {
                      "type": "integer"
                    },
                    "transaction": {
                      "$ref": "#/components/schemas/Transaction"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Bereits storniert",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Schlüssel oder Signatur ungültig",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Ungültige Eingabe oder fachlicher Fehler",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Limit erreicht",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/redemptions/lookup": {
      "post": {
        "summary": "Einlöse-Code prüfen (bucht nichts)",
        "security": [
          {
            "bearer": [],
            "signature": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "pattern": "^998\\d{10}$",
                    "example": "9981234567891"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Code einlösbar",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Redemption"
                }
              }
            }
          },
          "403": {
            "description": "Einlösung hier nicht möglich",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Code ungültig",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bereits verwendet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Abgelaufen",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Schlüssel oder Signatur ungültig",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Ungültige Eingabe oder fachlicher Fehler",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Limit erreicht",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/redemptions/capture": {
      "post": {
        "summary": "Einlöse-Code verbrauchen",
        "security": [
          {
            "bearer": [],
            "signature": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code",
                  "invoice_amount",
                  "receipt_id"
                ],
                "properties": {
                  "code": {
                    "type": "string"
                  },
                  "invoice_amount": {
                    "type": "number",
                    "minimum": 0.01
                  },
                  "receipt_id": {
                    "type": "string"
                  },
                  "store_id": {
                    "type": "integer"
                  },
                  "store_ref": {
                    "type": "string",
                    "maxLength": 64
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Eingelöst",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Redemption"
                }
              }
            }
          },
          "409": {
            "description": "Bereits verwendet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Abgelaufen",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Schlüssel oder Signatur ungültig",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Ungültige Eingabe oder fachlicher Fehler",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Limit erreicht",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/redemptions/{id}/refunds": {
      "post": {
        "summary": "Mit Punkten bezahlten Einkauf zurückgeben",
        "security": [
          {
            "bearer": [],
            "signature": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Punkte zurück beim Kunden",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "refunded_points": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Nicht möglich",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Schlüssel oder Signatur ungültig",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Ungültige Eingabe oder fachlicher Fehler",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Limit erreicht",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/balance": {
      "get": {
        "summary": "Guthaben",
        "responses": {
          "200": {
            "description": "Guthaben",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "balance_eur": {
                      "type": "number"
                    },
                    "low_balance_eur": {
                      "type": "number"
                    },
                    "held_count": {
                      "type": "integer"
                    },
                    "held_cost_eur": {
                      "type": "number"
                    },
                    "bookings_blocked": {
                      "type": "boolean"
                    },
                    "topup_reference": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/stores": {
      "get": {
        "summary": "Filialen",
        "responses": {
          "200": {
            "description": "Filialen",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "name": {
                            "type": "string"
                          },
                          "address": {
                            "type": "string"
                          },
                          "base_rate": {
                            "type": "number"
                          },
                          "active": {
                            "type": "boolean"
                          },
                          "ref": {
                            "type": "string",
                            "nullable": true
                          },
                          "open_now": {
                            "type": "boolean",
                            "nullable": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/coupons": {
      "get": {
        "summary": "Coupons",
        "responses": {
          "200": {
            "description": "Coupons",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "label": {
                            "type": "string"
                          },
                          "type": {
                            "type": "string",
                            "enum": [
                              "multiplier",
                              "fixed",
                              "per_euro",
                              "per_item",
                              "welcome"
                            ]
                          },
                          "value": {
                            "type": "number"
                          },
                          "status": {
                            "type": "string"
                          },
                          "starts_at": {
                            "type": "string"
                          },
                          "ends_at": {
                            "type": "string"
                          },
                          "target_type": {
                            "type": "string",
                            "enum": [
                              "purchase",
                              "category",
                              "brand",
                              "product"
                            ]
                          },
                          "target_values": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "requires_basket": {
                            "type": "boolean"
                          },
                          "stores": {
                            "type": "array",
                            "description": "leer = alle Filialen",
                            "items": {
                              "type": "object",
                              "properties": {
                                "id": {
                                  "type": "integer"
                                },
                                "ref": {
                                  "type": "string",
                                  "nullable": true
                                }
                              }
                            }
                          },
                          "recurrence": {
                            "type": "string",
                            "enum": [
                              "none",
                              "daily",
                              "weekly",
                              "monthly"
                            ]
                          },
                          "per_customer": {
                            "type": "integer"
                          },
                          "weekdays": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "enum": [
                                "mon",
                                "tue",
                                "wed",
                                "thu",
                                "fri",
                                "sat",
                                "sun"
                              ]
                            }
                          },
                          "time_from": {
                            "type": "string",
                            "nullable": true,
                            "example": "22:00"
                          },
                          "time_to": {
                            "type": "string",
                            "nullable": true,
                            "example": "02:00"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}