{
  "openapi": "3.1.0",
  "info": {
    "title": "biotshirt Agent API",
    "version": "1.0.0",
    "summary": "Catalog, prices, public merch shops and shop requests for biotshirt.de, built for AI agents and developers.",
    "description": "biotshirt builds free, branded merch shops for schools, clubs, companies, craft businesses and restaurants in Germany and Austria (sustainable textiles, no minimum order quantity, shop live within 48 hours).\n\nUse this API to read the printable base-product catalog with from-prices, list public shops, and submit a shop request on behalf of an organization. A sandbox server mirrors every endpoint without sending anything. Credentials are free and self-serve (POST /v1/keys); OAuth 2.0 client credentials are supported. All errors are JSON objects with `error.code`, `error.message` and `error.hint`. Documentation URLs also answer `Accept: text/markdown`.\n\nMCP server for the same capabilities: POST https://api.biotshirt.workers.dev/mcp\n\n**Versioning and deprecation:** the version lives in the URL path (/v1) and in the response header API-Version. v1 is additive-only. Deprecations are announced at least 180 days ahead with Deprecation: true and Sunset headers (RFC 8594) and documented at https://api.biotshirt.workers.dev/v1/versioning.",
    "x-versioning-policy": "https://api.biotshirt.workers.dev/v1/versioning",
    "x-deprecation-notice-days": 180,
    "termsOfService": "https://biotshirt.de/policies/terms-of-service",
    "contact": {
      "name": "biotshirt (YTWOO International GmbH)",
      "email": "info@biotshirt.de",
      "url": "https://api.biotshirt.workers.dev/developers"
    },
    "x-logo": {
      "url": "https://cdn.shopify.com/s/files/1/0907/1296/5503/files/BioTshirt_Logo.png?v=1780666177&width=800"
    }
  },
  "externalDocs": {
    "description": "Developer portal, quickstart, keys, sandbox",
    "url": "https://api.biotshirt.workers.dev/developers"
  },
  "servers": [
    {
      "url": "https://api.biotshirt.workers.dev",
      "description": "Production"
    },
    {
      "url": "https://api.biotshirt.workers.dev/sandbox",
      "description": "Sandbox: identical endpoints, shop requests are validated and stored but never sent"
    }
  ],
  "tags": [
    {
      "name": "Catalog",
      "description": "Printable base products with from-prices, colors, sizes and decoration options."
    },
    {
      "name": "Pricing",
      "description": "What the service costs: shop setup is free, product from-prices and volume tiers."
    },
    {
      "name": "Shops",
      "description": "Public merch shops of schools, clubs and companies hosted on biotshirt.de."
    },
    {
      "name": "Shop requests",
      "description": "Create a shop request for an organization. biotshirt builds the shop and replies by email."
    },
    {
      "name": "Auth",
      "description": "Self-serve credentials, OAuth 2.0 client credentials, scopes."
    },
    {
      "name": "Meta",
      "description": "Service document, OpenAPI, agent instructions, MCP."
    }
  ],
  "paths": {
    "/v1/products": {
      "get": {
        "operationId": "listProducts",
        "tags": [
          "Catalog"
        ],
        "summary": "List printable base products",
        "description": "Returns all base products that can be printed with an organization logo, including gross from-prices in EUR, colors, sizes and available decoration techniques. Public, no credentials required (higher rate limits with a key).",
        "security": [
          {},
          {
            "apiKey": []
          },
          {
            "oauth2": [
              "catalog:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "Free-text filter on title, type, tags and description.",
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          },
          {
            "name": "type",
            "in": "query",
            "description": "Filter by product type or tag, e.g. \"T-Shirt\", \"Hoodie\", \"Arbeitskleidung\".",
            "schema": {
              "type": "string",
              "maxLength": 60
            }
          },
          {
            "name": "decoration",
            "in": "query",
            "description": "Only products that support this technique.",
            "schema": {
              "type": "string",
              "enum": [
                "dtf",
                "stick"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 250,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductList"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Catalog source unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/products/{handle}": {
      "get": {
        "operationId": "getProduct",
        "tags": [
          "Catalog"
        ],
        "summary": "Get one base product",
        "description": "Returns a single base product by its handle (slug from listProducts), including from-price and volume tiers.",
        "security": [
          {},
          {
            "apiKey": []
          },
          {
            "oauth2": [
              "catalog:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "handle",
            "in": "path",
            "required": true,
            "description": "Product handle, e.g. \"mediumweight-t-shirt-unisex\".",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            }
          },
          "400": {
            "description": "Invalid handle",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Product not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/pricing": {
      "get": {
        "operationId": "getPricing",
        "tags": [
          "Pricing"
        ],
        "summary": "Pricing overview",
        "description": "Explains the cost model (shop setup free, pay only ordered products, minimum order quantity 1) and lists from-prices and volume tiers for every base product.",
        "security": [
          {},
          {
            "apiKey": []
          },
          {
            "oauth2": [
              "catalog:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Pricing"
                }
              }
            }
          }
        }
      }
    },
    "/v1/shops": {
      "get": {
        "operationId": "listPublicShops",
        "tags": [
          "Shops"
        ],
        "summary": "List public merch shops",
        "description": "Returns the public shops of schools, clubs and companies hosted on biotshirt.de, with category, URL and password flag. Agents can shop there through the store UCP/MCP endpoint (see agents.md).",
        "security": [
          {},
          {
            "apiKey": []
          },
          {
            "oauth2": [
              "shops:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "description": "Filter by category.",
            "schema": {
              "type": "string",
              "enum": [
                "schule",
                "verein",
                "unternehmen",
                "handwerker",
                "gastro"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShopList"
                }
              }
            }
          }
        }
      }
    },
    "/v1/shop-requests": {
      "post": {
        "operationId": "createShopRequest",
        "tags": [
          "Shop requests"
        ],
        "summary": "Submit a shop request for an organization",
        "description": "Creates a request for a free merch shop. On the production server the request is delivered to the biotshirt team (email to the organization and the team, entry in the internal request list); biotshirt builds the shop and replies within 48 hours. On the sandbox server (or with a sandbox key bt_test_...) the request is validated and stored only. Requires scope requests:write. Live keys: 5 requests per day.",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth2": [
              "requests:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ShopRequest"
              },
              "examples": {
                "school": {
                  "summary": "School hoodie shop",
                  "value": {
                    "organization": {
                      "name": "Grundschule am Park",
                      "type": "schule",
                      "contact_name": "Anna Beispiel",
                      "email": "sekretariat@example.org"
                    },
                    "shop_name": "Grundschule am Park Merch",
                    "products": [
                      {
                        "product": "cruiser-2-0",
                        "colors": [
                          "Navy",
                          "Heather Grey"
                        ],
                        "decoration": "dtf",
                        "designs": [
                          {
                            "position": "Front mittig",
                            "width_cm": 25,
                            "logo_url": "https://example.org/logo.png"
                          }
                        ]
                      }
                    ],
                    "group_order_until": "2026-12-01",
                    "password_protection": true,
                    "notes": "Bitte mit Schullogo in Weiß."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Request received",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShopRequestResult"
                }
              }
            }
          },
          "401": {
            "description": "Missing credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Scope requests:write missing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Validation failed (error.fields lists each problem)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Daily limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Intake unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/shop-requests/{id}": {
      "get": {
        "operationId": "getShopRequest",
        "tags": [
          "Shop requests"
        ],
        "summary": "Status of a shop request",
        "description": "Returns the stored status of a request created with the same credentials.",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth2": [
              "requests:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^req_[0-9a-f]{16}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShopRequestResult"
                }
              }
            }
          },
          "403": {
            "description": "Different credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/versioning": {
      "get": {
        "operationId": "getVersioningPolicy",
        "tags": [
          "Meta"
        ],
        "summary": "Versioning and deprecation policy",
        "description": "How versions are signalled (URL path + API-Version header), what stays stable inside v1, and how deprecations are announced (Deprecation and Sunset headers, 180 days notice). Also as Markdown with Accept: text/markdown.",
        "security": [
          {}
        ],
        "responses": {
          "200": {
            "description": "Policy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/onboarding": {
      "get": {
        "operationId": "getOnboarding",
        "tags": [
          "Auth"
        ],
        "summary": "How to get started (free tier, self-serve keys, sandbox)",
        "description": "Machine-readable onboarding document: free tier limits, how to create credentials without approval, OAuth endpoints, sandbox base URL and zero-auth endpoints. GET /v1/keys returns the same document.",
        "security": [
          {}
        ],
        "responses": {
          "200": {
            "description": "Onboarding document",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          }
        }
      }
    },
    "/v1/keys": {
      "get": {
        "operationId": "getKeyOnboarding",
        "tags": [
          "Auth"
        ],
        "summary": "Onboarding document (same as /v1/onboarding)",
        "security": [
          {}
        ],
        "responses": {
          "200": {
            "description": "Onboarding document",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createApiKey",
        "tags": [
          "Auth"
        ],
        "summary": "Create free API credentials (self-serve)",
        "description": "Creates an API key with the requested scopes. mode \"sandbox\" (default) yields a bt_test_ key whose shop requests are never sent; mode \"live\" yields a bt_live_ key. The key and client_secret are shown once. Free tier, no approval needed.",
        "security": [
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApiKeyRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Credentials created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKey"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many keys from this address today",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/me": {
      "get": {
        "operationId": "getCurrentPrincipal",
        "tags": [
          "Auth"
        ],
        "summary": "Inspect the current credentials",
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth2": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Principal"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/token": {
      "post": {
        "operationId": "issueAccessToken",
        "tags": [
          "Auth"
        ],
        "summary": "OAuth 2.0 token endpoint (client_credentials)",
        "description": "Exchanges client_id and client_secret (from createApiKey or registerClient) for a one-hour Bearer access token. Scopes can be narrowed with the scope parameter. Accepts application/json or application/x-www-form-urlencoded, and HTTP Basic client authentication.",
        "security": [
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request or invalid_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_client",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/register": {
      "post": {
        "operationId": "registerClient",
        "tags": [
          "Auth"
        ],
        "summary": "Dynamic client registration (RFC 7591)",
        "description": "Registers an OAuth client for the client_credentials grant. Equivalent to createApiKey; returns client_id and client_secret.",
        "security": [
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientRegistrationRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Client registered",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientRegistration"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "operationId": "mcpEndpoint",
        "tags": [
          "Meta"
        ],
        "summary": "MCP server (Streamable HTTP, JSON-RPC 2.0)",
        "description": "Model Context Protocol endpoint. Supports initialize, ping, tools/list, tools/call, resources/list, resources/read. Tools: search_products, get_product, get_pricing, list_public_shops, create_shop_request. Optional Bearer credentials enable live shop requests.",
        "security": [
          {},
          {
            "apiKey": []
          },
          {
            "oauth2": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "JSON-RPC 2.0 request or batch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC 2.0 response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "202": {
            "description": "Notification accepted"
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "getOpenApi",
        "tags": [
          "Meta"
        ],
        "summary": "This specification",
        "security": [
          {}
        ],
        "responses": {
          "200": {
            "description": "OpenAPI 3.1 document",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "getAgentInstructions",
        "tags": [
          "Meta"
        ],
        "summary": "Agent instructions (llms.txt)",
        "security": [
          {}
        ],
        "responses": {
          "200": {
            "description": "Markdown",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/": {
      "get": {
        "operationId": "getServiceDocument",
        "tags": [
          "Meta"
        ],
        "summary": "Service document with links to every resource",
        "security": [
          {}
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceDocument"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "bt_live_… or bt_test_… API key",
        "description": "Self-serve API key from POST /v1/keys, sent as Authorization: Bearer <key> (X-API-Key is also accepted). Scopes are fixed at key creation."
      },
      "oauth2": {
        "type": "oauth2",
        "description": "OAuth 2.0 client credentials. Register at POST /oauth/register (RFC 7591) or POST /v1/keys; metadata at /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource (RFC 9728).",
        "flows": {
          "clientCredentials": {
            "tokenUrl": "https://api.biotshirt.workers.dev/oauth/token",
            "scopes": {
              "catalog:read": "Read base products, printed from-prices, colors, sizes and availability.",
              "shops:read": "List the public merch shops hosted on biotshirt.de.",
              "requests:write": "Create a shop request (Anfrage) on behalf of an organization.",
              "requests:read": "Read the status of shop requests created with the same credentials."
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "status"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Machine-readable error code, e.g. validation_failed, unauthorized, insufficient_scope, rate_limited, product_not_found."
              },
              "message": {
                "type": "string"
              },
              "hint": {
                "type": "string",
                "description": "How to resolve the error."
              },
              "status": {
                "type": "integer"
              },
              "docs": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "fields": {
                "type": "array",
                "description": "Present on validation_failed.",
                "items": {
                  "type": "object",
                  "properties": {
                    "field": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "QuantityTier": {
        "type": "object",
        "properties": {
          "ab": {
            "type": "integer",
            "description": "From this quantity"
          },
          "brutto": {
            "type": "number",
            "description": "Gross price per piece incl. VAT"
          },
          "netto": {
            "type": "number",
            "description": "Net price per piece"
          }
        },
        "additionalProperties": true
      },
      "Product": {
        "type": "object",
        "required": [
          "handle",
          "title",
          "url",
          "currency"
        ],
        "properties": {
          "handle": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "type": {
            "type": "string",
            "description": "Product type, e.g. \"Unisex T-Shirt\"."
          },
          "available": {
            "type": "boolean"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "currency": {
            "type": "string",
            "example": "EUR"
          },
          "blank_price_from": {
            "type": [
              "number",
              "null"
            ],
            "description": "Unprinted sample price, gross."
          },
          "printed_price_from": {
            "type": [
              "number",
              "null"
            ],
            "description": "From-price for one printed piece, gross incl. 19 % VAT."
          },
          "quantity_prices_dtf": {
            "type": [
              "object",
              "null"
            ],
            "description": "Volume tiers for printed pieces (object with \"tiers\" array).",
            "properties": {
              "tiers": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/QuantityTier"
                }
              }
            },
            "additionalProperties": true
          },
          "decoration": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "dtf",
                "stick",
                "dtg"
              ]
            },
            "description": "Supported techniques. dtf = print (always), stick = embroidery (paused)."
          },
          "colors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sizes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "image": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "description": {
            "type": "string"
          }
        }
      },
      "ProductList": {
        "type": "object",
        "required": [
          "products",
          "total"
        ],
        "properties": {
          "generated_at": {
            "type": "string",
            "format": "date-time"
          },
          "total": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "products": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Product"
            }
          }
        }
      },
      "Pricing": {
        "type": "object",
        "properties": {
          "currency": {
            "type": "string"
          },
          "shop_setup": {
            "type": "object",
            "properties": {
              "price": {
                "type": "number"
              },
              "currency": {
                "type": "string"
              },
              "description": {
                "type": "string"
              }
            }
          },
          "minimum_order_quantity": {
            "type": "integer"
          },
          "price_basis": {
            "type": "string"
          },
          "delivery_area": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "decoration": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "products": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "handle": {
                  "type": "string"
                },
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "currency": {
                  "type": "string"
                },
                "printed_price_from": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "blank_price_from": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "quantity_prices_dtf": {
                  "type": [
                    "object",
                    "null"
                  ]
                },
                "decoration": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      },
      "Shop": {
        "type": "object",
        "required": [
          "name",
          "url"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "category": {
            "type": "string",
            "enum": [
              "schule",
              "verein",
              "unternehmen",
              "handwerker",
              "gastro",
              ""
            ]
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "password_protected": {
            "type": "boolean"
          },
          "image": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          }
        }
      },
      "ShopList": {
        "type": "object",
        "properties": {
          "generated_at": {
            "type": "string",
            "format": "date-time"
          },
          "count": {
            "type": "integer"
          },
          "shops": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Shop"
            }
          }
        }
      },
      "Design": {
        "type": "object",
        "properties": {
          "position": {
            "type": "string",
            "description": "Placement, e.g. \"Front mittig\", \"Brust links\", \"Rücken\".",
            "default": "Front mittig"
          },
          "width_cm": {
            "type": "number",
            "minimum": 1,
            "maximum": 40,
            "description": "Print width in centimeters."
          },
          "logo_url": {
            "type": "string",
            "format": "uri",
            "description": "Public http(s) URL of the logo (PNG/SVG/JPG, max 4 MB). Downloaded and attached to the request."
          },
          "comment": {
            "type": "string",
            "maxLength": 500
          }
        }
      },
      "RequestedProduct": {
        "type": "object",
        "required": [
          "product"
        ],
        "properties": {
          "product": {
            "type": "string",
            "description": "Product handle (preferred) or title from listProducts."
          },
          "colors": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Color names as listed in the product."
          },
          "decoration": {
            "type": "string",
            "enum": [
              "dtf",
              "stick"
            ],
            "default": "dtf",
            "description": "dtf = print (recommended, always available), stick = embroidery (paused)."
          },
          "designs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Design"
            }
          },
          "comment": {
            "type": "string",
            "maxLength": 500
          }
        }
      },
      "ShopRequest": {
        "type": "object",
        "required": [
          "organization",
          "shop_name"
        ],
        "properties": {
          "organization": {
            "type": "object",
            "required": [
              "name",
              "type",
              "contact_name",
              "email"
            ],
            "properties": {
              "name": {
                "type": "string"
              },
              "type": {
                "type": "string",
                "enum": [
                  "schule",
                  "verein",
                  "unternehmen",
                  "handwerk",
                  "gastro"
                ],
                "description": "schule/verein = consumer shop (members pay individually, gross prices); unternehmen/handwerk/gastro = company shop (net prices, volume tiers)."
              },
              "contact_name": {
                "type": "string"
              },
              "email": {
                "type": "string",
                "format": "email",
                "description": "biotshirt replies to this address."
              },
              "phone": {
                "type": "string"
              }
            }
          },
          "shop_name": {
            "type": "string",
            "maxLength": 80,
            "description": "Name of the shop as members will see it."
          },
          "products": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/RequestedProduct"
            },
            "description": "Required unless design_service is true."
          },
          "design_service": {
            "type": "boolean",
            "default": false,
            "description": "Let biotshirt design the collection (done-for-you). Products become optional."
          },
          "group_order_until": {
            "type": "string",
            "format": "date",
            "description": "Collect all member orders until this date and ship together (Sammelbestellung)."
          },
          "password_protection": {
            "type": "boolean",
            "default": false
          },
          "private_shop": {
            "type": "boolean",
            "default": false,
            "description": "Company shops only: shop visible in the customer account instead of publicly."
          },
          "customer_account": {
            "type": "boolean",
            "default": false,
            "description": "Create a biotshirt customer account for the contact person."
          },
          "occasion": {
            "type": "string",
            "maxLength": 120
          },
          "team_size": {
            "type": "integer",
            "minimum": 1
          },
          "notes": {
            "type": "string",
            "maxLength": 2000
          }
        }
      },
      "ShopRequestResult": {
        "type": "object",
        "required": [
          "id",
          "status",
          "sandbox"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "received"
            ]
          },
          "sandbox": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "organization": {
            "type": "string"
          },
          "shop_name": {
            "type": "string"
          },
          "shop_type": {
            "type": "string",
            "enum": [
              "b2c",
              "b2b"
            ]
          },
          "products": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "product": {
                  "type": "string"
                },
                "handle": {
                  "type": "string"
                },
                "colors": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "decoration": {
                  "type": "string"
                },
                "designs": {
                  "type": "integer"
                },
                "estimated_price_from": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              }
            }
          },
          "next_steps": {
            "type": "string"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "upstream": {
            "type": [
              "object",
              "null"
            ]
          }
        }
      },
      "ApiKeyRequest": {
        "type": "object",
        "required": [
          "name",
          "email"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 80,
            "description": "Name of your agent or application."
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "catalog:read",
                "shops:read",
                "requests:write",
                "requests:read"
              ]
            },
            "description": "Defaults to all scopes. Request only what you need."
          },
          "mode": {
            "type": "string",
            "enum": [
              "sandbox",
              "live"
            ],
            "default": "sandbox"
          }
        }
      },
      "ApiKey": {
        "type": "object",
        "properties": {
          "api_key": {
            "type": "string",
            "description": "Shown once. Use as Authorization: Bearer <api_key>."
          },
          "client_id": {
            "type": "string"
          },
          "client_secret": {
            "type": "string",
            "description": "Shown once. For POST /oauth/token."
          },
          "mode": {
            "type": "string",
            "enum": [
              "sandbox",
              "live"
            ]
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "tier": {
            "type": "string"
          },
          "limits": {
            "type": "object",
            "additionalProperties": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Principal": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "mode": {
            "type": "string"
          },
          "tier": {
            "type": "string"
          },
          "via": {
            "type": "string",
            "enum": [
              "api_key",
              "oauth"
            ]
          }
        }
      },
      "TokenRequest": {
        "type": "object",
        "required": [
          "grant_type"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "enum": [
              "client_credentials"
            ]
          },
          "client_id": {
            "type": "string"
          },
          "client_secret": {
            "type": "string"
          },
          "scope": {
            "type": "string",
            "description": "Space-separated subset of the granted scopes."
          }
        }
      },
      "TokenResponse": {
        "type": "object",
        "required": [
          "access_token",
          "token_type",
          "expires_in"
        ],
        "properties": {
          "access_token": {
            "type": "string"
          },
          "token_type": {
            "type": "string",
            "enum": [
              "Bearer"
            ]
          },
          "expires_in": {
            "type": "integer"
          },
          "scope": {
            "type": "string"
          }
        }
      },
      "ClientRegistrationRequest": {
        "type": "object",
        "required": [
          "client_name"
        ],
        "properties": {
          "client_name": {
            "type": "string"
          },
          "contacts": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            },
            "description": "At least one email address."
          },
          "scope": {
            "type": "string"
          },
          "grant_types": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "client_credentials"
              ]
            }
          },
          "mode": {
            "type": "string",
            "enum": [
              "sandbox",
              "live"
            ],
            "default": "sandbox"
          }
        }
      },
      "ClientRegistration": {
        "type": "object",
        "properties": {
          "client_id": {
            "type": "string"
          },
          "client_secret": {
            "type": "string"
          },
          "client_id_issued_at": {
            "type": "integer"
          },
          "client_secret_expires_at": {
            "type": "integer"
          },
          "client_name": {
            "type": "string"
          },
          "grant_types": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "token_endpoint_auth_method": {
            "type": "string"
          },
          "scope": {
            "type": "string"
          },
          "api_key": {
            "type": "string"
          }
        }
      },
      "ServiceDocument": {
        "type": "object",
        "additionalProperties": true
      }
    }
  }
}