{
  "openapi": "3.1.0",
  "info": {
    "title": "AddPass pass generation API",
    "version": "1.0.0",
    "description": "Generate signed generic Apple Wallet passes. AddPass handles signing. This describes recommended inputs, not every compatibility alias. Sandbox passes can have field replacements. Successful repeated requests consume allowance; no idempotency guarantee. Installed-pass push updates, revocation, membership verification and native Google Wallet issuance are outside this API.",
    "contact": {
      "email": "support@addpass.io"
    }
  },
  "servers": [
    {
      "url": "https://app.addpass.io"
    }
  ],
  "externalDocs": {
    "description": "Public integration reference",
    "url": "https://www.addpass.io/docs/api"
  },
  "security": [
    {
      "ApiKey": []
    }
  ],
  "paths": {
    "/api/v1/generate": {
      "post": {
        "operationId": "generateWalletPass",
        "summary": "Generate a signed generic Wallet pass",
        "description": "Call from your server. Use Accept: application/json for a temporary link (201), or application/vnd.apple.pkpass for binary bytes (200, default). Optional X-AddPass-Delivery overrides Accept. When both media types appear in Accept, JSON wins. Each successful request consumes generation allowance. Keep JSON smaller than 1 MB.",
        "parameters": [
          {
            "name": "X-AddPass-Delivery",
            "in": "header",
            "required": false,
            "description": "Overrides Accept. Omit to use Accept negotiation.",
            "schema": {
              "type": "string",
              "enum": [
                "stream",
                "link"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PassRequest"
              },
              "examples": {
                "minimal": {
                  "value": {
                    "primaryText": "My First Pass"
                  }
                },
                "membership": {
                  "value": {
                    "primaryText": "Riley Morgan",
                    "logoText": "Northside Club",
                    "description": "Northside Club membership",
                    "backgroundColor": "#211C38",
                    "foregroundColor": "#FFFFFF",
                    "labelColor": "#D5C6F1",
                    "headerLabelRight": "MEMBER",
                    "headerTextRight": "2026",
                    "secondaryLabelLeft": "Member number",
                    "secondaryTextLeft": "NS-1042",
                    "secondaryLabelRight": "Level",
                    "secondaryTextRight": "Community",
                    "backfields": [
                      {
                        "label": "Support",
                        "value": "Contact your club for membership questions."
                      }
                    ],
                    "barcode": {
                      "format": "qr",
                      "message": "https://example.com/members/NS-1042"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed .pkpass bytes (stream delivery). Save with .pkpass extension; do not parse as JSON.",
            "content": {
              "application/vnd.apple.pkpass": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "201": {
            "description": "Temporary download link (link delivery).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassLink"
                }
              }
            }
          },
          "400": {
            "description": "Payload validation or invalid delivery override. Malformed JSON can return a non-JSON error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing Authorization header, or inactive/unknown API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "402": {
            "description": "Monthly generation allowance exhausted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Developer plan required or not recognized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "413": {
            "description": "Body exceeds 1 MB. Response may not be JSON."
          },
          "422": {
            "description": "A remote image could not be downloaded or did not return image content.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until another request may be attempted.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Pass generation failed. Avoid automatic retries after uncertain outcomes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Developer API key configured as ADDPASS_API_KEY on the server. Not a Firebase ID token."
      }
    },
    "schemas": {
      "PassRequest": {
        "type": "object",
        "required": [
          "primaryText"
        ],
        "properties": {
          "primaryText": {
            "type": "string",
            "maxLength": 36,
            "description": "Required main text. Must be nonempty after trimming.",
            "minLength": 1
          },
          "logoText": {
            "type": "string",
            "maxLength": 23,
            "description": "Brand text in the pass header. Omit to keep the default."
          },
          "description": {
            "type": "string",
            "maxLength": 80,
            "description": "Pass description. Defaults to Generated via Add Pass API."
          },
          "headerLabelRight": {
            "type": "string",
            "maxLength": 13,
            "description": "Upper-right field label."
          },
          "headerTextRight": {
            "type": "string",
            "maxLength": 12,
            "description": "Upper-right field value."
          },
          "secondaryLabelLeft": {
            "type": "string",
            "maxLength": 30,
            "description": "Left secondary label. Sandbox markings may replace this field."
          },
          "secondaryTextLeft": {
            "type": "string",
            "maxLength": 40,
            "description": "Left secondary value. Sandbox markings may replace this field."
          },
          "secondaryLabelRight": {
            "type": "string",
            "maxLength": 30,
            "description": "Right secondary label. Sandbox markings may replace this field."
          },
          "secondaryTextRight": {
            "type": "string",
            "maxLength": 40,
            "description": "Right secondary value. Sandbox markings may replace this field."
          },
          "auxiliaryLabelLeft": {
            "type": "string",
            "maxLength": 30,
            "description": "Left auxiliary label. Sandbox markings may replace this field."
          },
          "auxiliaryTextLeft": {
            "type": "string",
            "maxLength": 40,
            "description": "Left auxiliary value. Sandbox markings may replace this field."
          },
          "auxiliaryLabelRight": {
            "type": "string",
            "maxLength": 30,
            "description": "Right auxiliary label. Sandbox markings may replace this field."
          },
          "auxiliaryTextRight": {
            "type": "string",
            "maxLength": 40,
            "description": "Right auxiliary value. Sandbox markings may replace this field."
          },
          "backgroundColor": {
            "type": "string",
            "pattern": "^#?[0-9a-fA-F]{6}$",
            "description": "Background color; six-digit hex, optionally prefixed with #. Invalid colors fall back to defaults."
          },
          "foregroundColor": {
            "type": "string",
            "pattern": "^#?[0-9a-fA-F]{6}$",
            "description": "Text color; six-digit hex, optionally prefixed with #."
          },
          "labelColor": {
            "type": "string",
            "pattern": "^#?[0-9a-fA-F]{6}$",
            "description": "Label color; six-digit hex, optionally prefixed with #."
          },
          "customLogoURL": {
            "type": "string",
            "format": "uri",
            "description": "Public HTTP(S) image URL for the logo. A transparent PNG is recommended. No authentication headers are sent when fetching it."
          },
          "thumbnailURL": {
            "type": "string",
            "format": "uri",
            "description": "Public HTTP(S) image URL for the thumbnail. Use a PNG or JPEG image, not an HTML preview page."
          },
          "backfields": {
            "type": "array",
            "maxItems": 30,
            "description": "Back-of-pass details. Send at most 30 entries; later entries are discarded. Sandbox marking can occupy one entry.",
            "items": {
              "type": "object",
              "required": [
                "value"
              ],
              "properties": {
                "label": {
                  "type": "string",
                  "maxLength": 60,
                  "description": "Optional label."
                },
                "value": {
                  "type": "string",
                  "maxLength": 200,
                  "description": "Required nonempty value.",
                  "minLength": 1
                }
              }
            }
          },
          "barcode": {
            "type": "object",
            "description": "Barcode configuration. Your app supplies the verification or redemption workflow.",
            "required": [
              "message"
            ],
            "properties": {
              "format": {
                "type": "string",
                "enum": [
                  "qr",
                  "aztec",
                  "pdf417"
                ],
                "default": "qr",
                "description": "Barcode format."
              },
              "message": {
                "type": "string",
                "maxLength": 256,
                "description": "Value encoded in the barcode, such as your verification URL or opaque member identifier.",
                "minLength": 1
              },
              "messageEncoding": {
                "type": "string",
                "maxLength": 40,
                "description": "Encoding used for the message. Use characters compatible with the selected encoding.",
                "default": "iso-8859-1"
              },
              "label": {
                "type": "string",
                "maxLength": 60,
                "description": "Optional text displayed with the barcode."
              }
            }
          },
          "qrCodeText": {
            "type": "string",
            "maxLength": 120,
            "description": "Shortcut for a QR barcode. Takes precedence over barcode when supplied; use only one form."
          },
          "location": {
            "type": "object",
            "description": "Optional location relevance hint. Does not implement access control or guarantee a notification.",
            "required": [
              "lat",
              "long"
            ],
            "properties": {
              "lat": {
                "type": "number",
                "minimum": -90,
                "maximum": 90,
                "description": "Latitude."
              },
              "long": {
                "type": "number",
                "minimum": -180,
                "maximum": 180,
                "description": "Longitude."
              }
            }
          },
          "relevantDate": {
            "type": "string",
            "maxLength": 64,
            "description": "Optional relevance date. Send an ISO 8601 timestamp with a timezone. This is not a revocation mechanism.",
            "format": "date-time"
          },
          "serialNumber": {
            "type": "string",
            "maxLength": 80,
            "description": "Pass serial number. Omit for a generated unique value. Reusing one does not provide an idempotency guarantee or a push-update API."
          }
        }
      },
      "PassLink": {
        "type": "object",
        "required": [
          "passId",
          "passUrl",
          "expiresAt",
          "message"
        ],
        "properties": {
          "passId": {
            "type": "string",
            "description": "Generated file identifier."
          },
          "passUrl": {
            "type": "string",
            "format": "uri",
            "description": "Sensitive signed download URL, valid approximately 15 minutes. Not a permanent URL."
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "Stored file retention deadline, approximately seven days. NOT the signed URL expiry or installed-pass expiry."
          },
          "message": {
            "type": "string",
            "description": "Delivery guidance."
          }
        }
      },
      "ApiError": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          }
        }
      }
    }
  }
}
