{
  "openapi": "3.0.1",
  "info": {
    "title": "walloho API",
    "version": "v1",
    "description": "## walloho REST-API\n\nÖffentliche REST-Schnittstelle zur Erstellung und Verwaltung digitaler Wallet-Pässe (Apple Wallet & Google Wallet).\n\n**Basis-URL:** `https://api.walloho.com`\n\n### Authentifizierung (Client-Credentials)\n1. `POST /api/v1/auth/token` mit `tenantId`, `applicationId`, `clientSecret` aufrufen.\n2. Den zurückgegebenen JWT als `Authorization: Bearer <token>` an alle weiteren Aufrufe anhängen.\nDie öffentlichen Schlüssel zur Token-Prüfung stehen unter `/.well-known/jwks.json`.\n\n### Fehlercodes\n- `400` fachliche Validierungsfehler (eigener `error`-Code + Beschreibung)\n- `401` fehlendes/ungültiges Token · `403` fehlendes Scope\n- `402` Guthaben/Abrechnung: `insufficient_credits` bzw. `contract_cap_reached`\n- `404` nicht gefunden · `409` Konflikt (z. B. doppelte `externalRef`)\n\nSchema-/Feldnamen sind englisch; die Beschreibungen deutsch."
  },
  "servers": [
    {
      "url": "https://api.walloho.com"
    }
  ],
  "paths": {
    "/api/v1/analytics/events": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "List recorded ApiEvents (Pub/Sub etc.) for the tenant.",
        "parameters": [
          {
            "name": "event_type",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pass_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/analytics/summary": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Aggregate counts: events per (day, event_type) plus a coarse active-pass count.",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/auth/token": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Issue a JWT access token using client credentials.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/jwks.json": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "JWKS endpoint for public key discovery.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/batches": {
      "post": {
        "tags": [
          "Batches"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBatchRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBatchRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBatchRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "402": {
            "description": "Payment Required",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Batches"
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/BatchListResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchListResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchListResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/batches/{id}": {
      "get": {
        "tags": [
          "Batches"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Batches"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/billing/packages": {
      "get": {
        "tags": [
          "Billing"
        ],
        "summary": "Lists the active topup packages available for purchase.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/billing/topup": {
      "post": {
        "tags": [
          "Billing"
        ],
        "summary": "Starts a topup purchase: returns the Stripe Checkout URL the buyer is redirected to.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TopupRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/TopupRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/TopupRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/billing/subscribe": {
      "post": {
        "tags": [
          "Billing"
        ],
        "summary": "Starts a subscription: returns the Stripe Checkout URL for the recurring plan.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubscribeRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/SubscribeRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/SubscribeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/jobs/{id}": {
      "get": {
        "tags": [
          "Jobs"
        ],
        "summary": "Get job status by ID. Used by clients to poll async create/update/revoke\r\noperations until the worker reports back.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/messages": {
      "post": {
        "tags": [
          "Messages"
        ],
        "summary": "Queue a push notification for one pass or every active pass of a template.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendMessageRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/SendMessageRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/SendMessageRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/MessageResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Messages"
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          },
          {
            "name": "pass_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "template_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/MessageListResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageListResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageListResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/messages/{id}": {
      "get": {
        "tags": [
          "Messages"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/MessageResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/passes": {
      "post": {
        "tags": [
          "Passes"
        ],
        "summary": "Create a new pass (async). Returns 202 Accepted with job_id.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePassRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePassRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePassRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/PassResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "402": {
            "description": "Payment Required",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Passes"
        ],
        "summary": "List passes for the tenant.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "external_ref",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/PassListResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassListResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassListResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/passes/{id}": {
      "get": {
        "tags": [
          "Passes"
        ],
        "summary": "Get a single pass by ID.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/PassResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Passes"
        ],
        "summary": "Update pass data (async). Returns 202 Accepted with job_id.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePassRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePassRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePassRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/PassResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Passes"
        ],
        "summary": "Revoke a pass (async). Returns 202 Accepted with job_id.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/PassResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/passes/by-external-ref/{externalRef}": {
      "put": {
        "tags": [
          "Passes"
        ],
        "summary": "web-136: UPSERT über external_ref. PUT /api/v1/passes/by-external-ref/{externalRef}.\r\nLegt den Pass an, wenn er (pro Tenant) noch nicht existiert, sonst aktualisiert er ihn\r\n(web-134 REPLACE + Re-Resolve). Immer 202 Accepted (async) — die Response\r\n(status/version) unterscheidet create (Pending/v1) von update (Updating/v++). Body =\r\nCreatePassRequest; der PFAD ist autoritativ. Da die geteilte CreatePassRequest-\r\nValidierung external_ref verlangt, MUSS der Body external_ref == Pfad-Segment tragen\r\n(Mismatch → 400). Hinweis: '/' im ref ist nicht pfad-sicher → dann PATCH /passes/{id}.",
        "parameters": [
          {
            "name": "externalRef",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePassRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePassRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePassRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/PassResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Passes"
        ],
        "summary": "web-136: REINES Update über external_ref. PATCH /api/v1/passes/by-external-ref/{externalRef}.\r\n404, wenn der Pass nicht existiert (KEIN Create — dafür ist PUT/Upsert da). Returns 202.",
        "parameters": [
          {
            "name": "externalRef",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePassRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePassRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePassRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/PassResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/passes/{id}/versions": {
      "get": {
        "tags": [
          "Passes"
        ],
        "summary": "Get pass version history (newest version first).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/passes/{id}/messages": {
      "get": {
        "tags": [
          "Passes"
        ],
        "summary": "List messages targeted at a specific pass.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/MessageListResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageListResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageListResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/templates": {
      "get": {
        "tags": [
          "Templates"
        ],
        "summary": "List tenant + system templates (paged).",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          },
          {
            "name": "template_type",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateListResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateListResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateListResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/templates/{id}": {
      "get": {
        "tags": [
          "Templates"
        ],
        "summary": "Single template by id (tenant- or system-owned).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/templates/{id}/versions/latest": {
      "get": {
        "tags": [
          "Templates"
        ],
        "summary": "Latest template version (the one referenced by `LatestVersion`).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateVersionResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateVersionResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateVersionResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/templates/{id}/sample": {
      "get": {
        "tags": [
          "Templates"
        ],
        "summary": "Download a ready-to-edit sample JSON body for `POST /api/v1/passes`\r\nbased on this template's FieldsJson contract. Static fields are\r\npre-filled with their template default; Placeholder fields show as\r\n`{{key}}`; Computed fields are omitted (server-side). Only\r\navailable for Active templates.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/webhooks": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookListResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookListResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookListResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/webhooks/{id}": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateWebhookRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateWebhookRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateWebhookRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/webhooks/{id}/deliveries": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDeliveryListResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDeliveryListResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDeliveryListResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ApiError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "nullable": true
          },
          "error_description": {
            "type": "string",
            "nullable": true
          },
          "trace_id": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "BatchItemRequest": {
        "type": "object",
        "properties": {
          "external_ref": {
            "type": "string",
            "nullable": true
          },
          "data": {
            "type": "object",
            "additionalProperties": {},
            "nullable": true
          },
          "meta": {
            "type": "object",
            "additionalProperties": {},
            "nullable": true
          },
          "email": {
            "type": "string",
            "description": "Recipient email for this item's pass. When set, the post-create\r\n            email pipeline delivers the pass once all providers are done.",
            "nullable": true
          },
          "lang": {
            "type": "string",
            "description": "ISO 639-1 / BCP-47 language for the email template.",
            "nullable": true
          },
          "email_merge_fields": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Per-item merge fields injected into the email body.",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "BatchItemResult": {
        "type": "object",
        "properties": {
          "external_ref": {
            "type": "string",
            "nullable": true
          },
          "success": {
            "type": "boolean"
          },
          "pass_id": {
            "type": "string",
            "nullable": true
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "email_status": {
            "type": "string",
            "nullable": true
          },
          "bounce_type": {
            "type": "string",
            "nullable": true
          },
          "bounced_at": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "BatchListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BatchResponse"
            },
            "nullable": true
          },
          "total": {
            "type": "integer",
            "format": "int32"
          },
          "page": {
            "type": "integer",
            "format": "int32"
          },
          "page_size": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "BatchResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "nullable": true
          },
          "tenant_id": {
            "type": "string",
            "nullable": true
          },
          "template_id": {
            "type": "string",
            "nullable": true
          },
          "intent": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          },
          "total_count": {
            "type": "integer",
            "format": "int32"
          },
          "completed_count": {
            "type": "integer",
            "format": "int32"
          },
          "failed_count": {
            "type": "integer",
            "format": "int32"
          },
          "progress_percent": {
            "type": "integer",
            "format": "int32"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BatchItemResult"
            },
            "description": "Per-item results — present on GET, omitted on POST to keep the\r\n            create-response small.",
            "nullable": true
          },
          "error_summary": {
            "type": "string",
            "nullable": true
          },
          "job_id": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "nullable": true
          },
          "completed_at": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "CreateBatchRequest": {
        "type": "object",
        "properties": {
          "template_id": {
            "type": "string",
            "nullable": true
          },
          "intent": {
            "type": "string",
            "nullable": true
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BatchItemRequest"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "CreatePassRequest": {
        "type": "object",
        "properties": {
          "template_id": {
            "type": "string",
            "nullable": true
          },
          "external_ref": {
            "type": "string",
            "description": "Customer-owned unique identifier. Used as the idempotency key for create requests.",
            "nullable": true
          },
          "data": {
            "type": "object",
            "additionalProperties": {},
            "description": "Field values used to populate the template's placeholders.",
            "nullable": true
          },
          "meta": {
            "type": "object",
            "additionalProperties": {},
            "description": "Optional metadata; passed through to provider-specific persistence.",
            "nullable": true
          },
          "intent": {
            "type": "string",
            "description": "Snake-case pass intent (mapped via M:WalletPlatform.Core.Wallet.IntentMapping.Parse(System.String)):\r\n\"generic\" | \"loyalty_card\" | \"coupon\" | \"event_ticket\" | \"boarding_pass\".\r\nDefaults to \"generic\" when the field is omitted.",
            "nullable": true
          },
          "providers": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Restricts which providers the worker should target. null = all configured\r\nproviders; [\"google\"] / [\"apple\"] / [\"google\",\"apple\"] = explicit subset.",
            "nullable": true
          },
          "linked_to_external_ref": {
            "type": "string",
            "description": "v3-30 (optional): `external_ref` eines bereits existierenden PRIMÄREN\r\nPasses DESSELBEN Tenants, an den dieser Pass in Google Wallet gehängt werden\r\nsoll (Auto-Linked-Passes). Typ-agnostisch — JEDER Passtyp kann primär oder\r\nsekundär sein. `null`/leer = eigenständiger Pass. Auflösung + Validierung\r\n(Existenz, Tenant-Grenze, Provisioniert, keine Selbst-/Ketten-Referenz,\r\nLimit 50) im WalletPlatform.Api.Services.PassService. Die\r\nVerknüpfung wird HIER nur persistiert; der Google-Patch folgt in v3-31.",
            "nullable": true
          },
          "google_grouping_id": {
            "type": "string",
            "description": "v3-138: Google `groupingInfo.groupingId` (max 64). Gruppiert\r\n            Pässe — auch verschiedener Typen — in Google Wallet. Gespeichert in\r\n            `ApiPass.GroupingId`; überschreibt die abgeleitete `wp-…`-ID.",
            "nullable": true
          },
          "google_sort_index": {
            "type": "integer",
            "description": "v3-138: Google `groupingInfo.sortIndex` (int ≥ 0). Wirkt nur\r\n            zusammen mit einer groupingId.",
            "format": "int32",
            "nullable": true
          },
          "apple_grouping_identifier": {
            "type": "string",
            "description": "v3-138: Apple `groupingIdentifier` (max 64). Apple gruppiert\r\n            NUR eventTicket und boardingPass; bei anderen Styles wird der Wert NICHT\r\n            gesetzt und die API-Antwort enthält einen Hinweis in `warnings`.",
            "nullable": true
          },
          "email": {
            "type": "string",
            "description": "Recipient email address. Required when the parent Application\r\n            has email delivery enabled.",
            "nullable": true
          },
          "lang": {
            "type": "string",
            "description": "ISO 639-1 language code (e.g. \"de\", \"en\"). Determines which\r\n            EmailTemplate is rendered. null falls back to the tenant default.",
            "nullable": true
          },
          "email_merge_fields": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Per-send merge fields injected into the email body as\r\n            `{{email_merge_fields.<key>}}`. Distinct from WalletPlatform.Api.Models.Requests.CreatePassRequest.Data\r\n            so customer-side email-only metadata doesn't pollute the wallet pass\r\n            payload.",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "CreateWebhookRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "nullable": true
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "secret": {
            "type": "string",
            "description": "Optional shared secret used to compute HMAC-SHA256 signatures.\r\n            Stored AES-256-GCM-encrypted server-side; only the first 4 chars are\r\n            echoed back via `secret_prefix`.",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "MessageListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MessageResponse"
            },
            "nullable": true
          },
          "total": {
            "type": "integer",
            "format": "int32"
          },
          "page": {
            "type": "integer",
            "format": "int32"
          },
          "page_size": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "MessageResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "nullable": true
          },
          "tenant_id": {
            "type": "string",
            "nullable": true
          },
          "pass_id": {
            "type": "string",
            "nullable": true
          },
          "template_id": {
            "type": "string",
            "nullable": true
          },
          "header": {
            "type": "string",
            "nullable": true
          },
          "body": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          },
          "affected_passes": {
            "type": "integer",
            "format": "int32"
          },
          "created_at": {
            "type": "string",
            "nullable": true
          },
          "sent_at": {
            "type": "string",
            "nullable": true
          },
          "job_id": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PassListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PassResponse"
            },
            "nullable": true
          },
          "total": {
            "type": "integer",
            "format": "int32"
          },
          "page": {
            "type": "integer",
            "format": "int32"
          },
          "page_size": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "PassResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "nullable": true
          },
          "tenant_id": {
            "type": "string",
            "nullable": true
          },
          "template_id": {
            "type": "string",
            "nullable": true
          },
          "external_ref": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          },
          "version": {
            "type": "integer",
            "format": "int32"
          },
          "google_object_id": {
            "type": "string",
            "nullable": true
          },
          "apple_serial_number": {
            "type": "string",
            "nullable": true
          },
          "data": {
            "type": "object",
            "additionalProperties": {},
            "nullable": true
          },
          "meta": {
            "type": "object",
            "additionalProperties": {},
            "nullable": true
          },
          "job_id": {
            "type": "string",
            "description": "Worker job id; present on async create/update/revoke responses.",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "nullable": true
          },
          "updated_at": {
            "type": "string",
            "nullable": true
          },
          "providers": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/ProviderStatusResponse"
            },
            "description": "API-14: per-provider status + provider-specific result fields. Keys are\r\nlowercase provider names (\"google\", \"apple\"). Absent on the legacy\r\nresponse when no `ApiProviderJob` rows exist for the pass yet.",
            "nullable": true
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "v3-138: non-fatal hints about the request (e.g. an\r\n`apple_grouping_identifier` supplied for a pass style Apple does not\r\ngroup). Absent (null) when there is nothing to report — the field is omitted\r\nfrom the JSON so existing responses are byte-identical.",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ProblemDetails": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "detail": {
            "type": "string",
            "nullable": true
          },
          "instance": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": {}
      },
      "ProviderStatusResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "nullable": true
          },
          "save_link": {
            "type": "string",
            "nullable": true
          },
          "download_url": {
            "type": "string",
            "nullable": true
          },
          "object_id": {
            "type": "string",
            "nullable": true
          },
          "serial_number": {
            "type": "string",
            "nullable": true
          },
          "error": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "SendMessageRequest": {
        "type": "object",
        "properties": {
          "pass_id": {
            "type": "string",
            "nullable": true
          },
          "template_id": {
            "type": "string",
            "nullable": true
          },
          "header": {
            "type": "string",
            "nullable": true
          },
          "body": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "SubscribeRequest": {
        "required": [
          "planCode"
        ],
        "type": "object",
        "properties": {
          "planCode": {
            "minLength": 1,
            "type": "string"
          }
        },
        "additionalProperties": false,
        "description": "stripe-04b: body of `POST /api/v1/billing/subscribe` — which plan the tenant subscribes to."
      },
      "TemplateListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TemplateResponse"
            },
            "nullable": true
          },
          "total": {
            "type": "integer",
            "format": "int32"
          },
          "page": {
            "type": "integer",
            "format": "int32"
          },
          "page_size": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "TemplateResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "nullable": true
          },
          "tenant_id": {
            "type": "string",
            "description": "NULL for system templates.",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "template_type": {
            "type": "string",
            "description": "Raw `Template.TemplateType` string (\"LoyaltyCard\", \"BoardingPass\", …).",
            "nullable": true
          },
          "intent": {
            "type": "string",
            "description": "Snake-case intent derived from `TemplateType` via `IntentMapping`.",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          },
          "latest_version": {
            "type": "integer",
            "format": "int32"
          },
          "is_system_template": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "TemplateVersionResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "nullable": true
          },
          "template_id": {
            "type": "string",
            "nullable": true
          },
          "version": {
            "type": "integer",
            "format": "int32"
          },
          "change_note": {
            "type": "string",
            "nullable": true
          },
          "fields_json": {
            "type": "string",
            "nullable": true
          },
          "intent_json": {
            "type": "string",
            "nullable": true
          },
          "transit_type": {
            "type": "string",
            "description": "BoardingPass-only: TransitType from the TemplateVersion column\r\n(`air`, `train`, `bus`, `boat`). The resolver injects\r\nthis into the worker data-bag as `transit_type` so the Apple\r\nBoardingPass mapper picks it up without the caller needing to send it.",
            "nullable": true
          },
          "barcode_json": {
            "type": "string",
            "description": "web-13: legacy BarcodeJson side-blob (pre-web-13 editor stored the\r\nbarcode config here only). Resolver uses it as fallback when\r\nFieldsJson.barcode is absent.",
            "nullable": true
          },
          "header_json": {
            "type": "string",
            "description": "web-73: Pass-Header-Slot-Definition (Editor's HeaderJson, format\r\n`{\"slot\":{\"elements\":[{\"type\":\"text\",\"text\":\"Mitgliedskarte\"}]}}`).\r\nResolver liest hieraus den static-text-Header und mapped ihn auf\r\n`program_name` wenn die Caller-Daten den nicht bereits setzen.",
            "nullable": true
          },
          "body_json": {
            "type": "string",
            "description": "web-74: Pass-Body-Layout (Editor's BodyJson, format\r\n`{\"rows\":[{\"id\":\"row1\",\"fields\":[{\"type\":\"field\",\"key\":\"name\",\"position\":\"left\"}]}, …]}`).\r\nResolver baut daraus zusammen mit FieldsJson.fields[].labels den\r\n`__google_rows`-Side-Channel für den Google-Mapper's\r\n`cardTemplateOverride.cardRowTemplateInfos`.",
            "nullable": true
          },
          "caller_fields": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "web-100c: the keys a caller MAY send per pass (manifest data +\r\ncaller-per-pass elements only). Chrome/presentation/system elements\r\n(issuer_name, program_name, colors, …) are EXCLUDED — they come from the\r\ntemplate/application, not the caller. This is the authoritative\r\npass-create input contract; clients should build their per-pass input\r\nform from this list, NOT from the raw `fields_json` (which is the\r\nfull template definition incl. chrome). Null for pass-types without a\r\nmanifest (client falls back to fields_json).",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "TokenRequest": {
        "type": "object",
        "properties": {
          "tenant_id": {
            "type": "string",
            "nullable": true
          },
          "application_id": {
            "type": "string",
            "nullable": true
          },
          "application_secret": {
            "type": "string",
            "nullable": true
          },
          "grant_type": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "TokenResponse": {
        "type": "object",
        "properties": {
          "access_token": {
            "type": "string",
            "nullable": true
          },
          "token_type": {
            "type": "string",
            "nullable": true
          },
          "expires_in": {
            "type": "integer",
            "format": "int32"
          },
          "expires_at": {
            "type": "integer",
            "format": "int64"
          },
          "scope": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "TopupRequest": {
        "required": [
          "packageId"
        ],
        "type": "object",
        "properties": {
          "packageId": {
            "type": "string",
            "format": "uuid"
          }
        },
        "additionalProperties": false,
        "description": "stripe-03: body of `POST /api/v1/billing/topup` — which package the tenant wants to buy."
      },
      "UpdatePassRequest": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "additionalProperties": {},
            "nullable": true
          },
          "meta": {
            "type": "object",
            "additionalProperties": {},
            "nullable": true
          },
          "change_note": {
            "type": "string",
            "nullable": true
          },
          "google_grouping_id": {
            "type": "string",
            "description": "v3-138: siehe WalletPlatform.Api.Models.Requests.CreatePassRequest.GoogleGroupingId. \"\"=entfernen.",
            "nullable": true
          },
          "google_sort_index": {
            "type": "integer",
            "description": "v3-138: siehe WalletPlatform.Api.Models.Requests.CreatePassRequest.GoogleSortIndex. null=unverändert.",
            "format": "int32",
            "nullable": true
          },
          "apple_grouping_identifier": {
            "type": "string",
            "description": "v3-138: siehe WalletPlatform.Api.Models.Requests.CreatePassRequest.AppleGroupingIdentifier. \"\"=entfernen.",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "UpdateWebhookRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "nullable": true
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "is_active": {
            "type": "boolean",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "WebhookDeliveryListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookDeliveryResponse"
            },
            "nullable": true
          },
          "total": {
            "type": "integer",
            "format": "int32"
          },
          "page": {
            "type": "integer",
            "format": "int32"
          },
          "page_size": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "WebhookDeliveryResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "nullable": true
          },
          "webhook_id": {
            "type": "string",
            "nullable": true
          },
          "event_type": {
            "type": "string",
            "nullable": true
          },
          "http_status_code": {
            "type": "integer",
            "format": "int32"
          },
          "success": {
            "type": "boolean"
          },
          "error_message": {
            "type": "string",
            "nullable": true
          },
          "attempt_count": {
            "type": "integer",
            "format": "int32"
          },
          "created_at": {
            "type": "string",
            "nullable": true
          },
          "next_retry_at": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "WebhookListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookResponse"
            },
            "nullable": true
          },
          "total": {
            "type": "integer",
            "format": "int32"
          },
          "page": {
            "type": "integer",
            "format": "int32"
          },
          "page_size": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "WebhookResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "nullable": true
          },
          "tenant_id": {
            "type": "string",
            "nullable": true
          },
          "url": {
            "type": "string",
            "nullable": true
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "is_active": {
            "type": "boolean"
          },
          "secret_prefix": {
            "type": "string",
            "description": "First 4 characters of the configured secret, or null when no\r\n            secret was set. The full secret is never returned.",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "nullable": true
          },
          "updated_at": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      }
    },
    "securitySchemes": {
      "Bearer": {
        "type": "http",
        "description": "Obtain token via POST /api/v1/auth/token",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      }
    }
  },
  "security": [
    {
      "Bearer": []
    }
  ]
}