{
  "openapi": "3.1.0",
  "info": {
    "version": "1.0.0",
    "title": "Squad API",
    "description": "Internal API for The Squad operations"
  },
  "servers": [
    {
      "url": "https://api.thesqd.com",
      "description": "Production"
    }
  ],
  "paths": {
    "/v1/image-gen/references/variants": {
      "get": {
        "operationId": "list_image_gen_variants",
        "summary": "List Variant Clusters",
        "description": "Clusters of near-duplicate/variant references (the current dedup view that replaced phash 'duplicates'). Returns groups with the suggested keep.",
        "tags": [
          "Variants"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "list_image_gen_variants",
            "description": "Clusters of near-duplicate/variant references (the current dedup view that replaced phash 'duplicates'). Returns groups with the suggested keep.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "account_number",
            "in": "query",
            "required": false,
            "description": "Scope to one church.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "groups": [
                    {
                      "keep": {
                        "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                        "ref_id": "REF-12345",
                        "url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/original",
                        "preview_url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/preview",
                        "colors": [
                          "#0a7d3b",
                          "#f4ede1",
                          "#1a1a1a"
                        ],
                        "color_names": [
                          "Forest",
                          "Cream",
                          "Charcoal"
                        ],
                        "palette_tag": "warm-sage",
                        "type": "sermon",
                        "season": "summer",
                        "design_styles": [
                          "editorial"
                        ],
                        "aspect": "social",
                        "text_coverage": "minimal",
                        "account_number": "2049",
                        "active": true
                      },
                      "members": [
                        {
                          "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                          "ref_id": "REF-12345",
                          "url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/original",
                          "preview_url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/preview",
                          "colors": [
                            "#0a7d3b",
                            "#f4ede1",
                            "#1a1a1a"
                          ],
                          "color_names": [
                            "Forest",
                            "Cream",
                            "Charcoal"
                          ],
                          "palette_tag": "warm-sage",
                          "type": "sermon",
                          "season": "summer",
                          "design_styles": [
                            "editorial"
                          ],
                          "aspect": "social",
                          "text_coverage": "minimal",
                          "account_number": "2049",
                          "active": true
                        }
                      ],
                      "distance": 0.04
                    }
                  ],
                  "scanned_at": "2026-06-22T12:00:00Z",
                  "never_scanned": false
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/references/variants/scan": {
      "post": {
        "operationId": "scan_image_gen_variants",
        "summary": "Scan For Variant Clusters",
        "description": "Recompute variant clusters across active references. ?threshold tunes the similarity cut.",
        "tags": [
          "Variants"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "scan_image_gen_variants",
            "description": "Recompute variant clusters across active references. ?threshold tunes the similarity cut.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "threshold",
            "in": "query",
            "required": false,
            "description": "Similarity threshold (looser/tighter clustering).",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "tag": "variants-scan:2026-06-22",
                  "publicAccessToken": "pk_tr_xxx"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/references/variants/cull": {
      "post": {
        "operationId": "cull_image_gen_variants",
        "summary": "Cull Variants To Best",
        "description": "Retire all but the best reference in each variant group (craft-aware keep). Soft-retires the rest.",
        "tags": [
          "Variants"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "cull_image_gen_variants",
            "description": "Retire all but the best reference in each variant group (craft-aware keep). Soft-retires the rest.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": true,
              "idempotentHint": false
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "group_id": "..."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "families": 7,
                  "retired": 11,
                  "failed": 0
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/references/variants/merge": {
      "post": {
        "operationId": "merge_image_gen_variants",
        "summary": "Merge Two Variants",
        "description": "Keep one reference and retire the other as its variant. Body: { keep_id, retire_id }.",
        "tags": [
          "Variants"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "merge_image_gen_variants",
            "description": "Keep one reference and retire the other as its variant. Body: { keep_id, retire_id }.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": true,
              "idempotentHint": false
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "keep_id": "...",
                "retire_id": "..."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "ok": true,
                  "keep_ref_id": "REF-12345",
                  "filled": 2
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/references/variants/ignore": {
      "post": {
        "operationId": "ignore_image_gen_variants",
        "summary": "Ignore A Variant Group",
        "description": "Mark a variant group as intentionally distinct so it stops being suggested. Body: { group_id, ids }.",
        "tags": [
          "Variants"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "ignore_image_gen_variants",
            "description": "Mark a variant group as intentionally distinct so it stops being suggested. Body: { group_id, ids }.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "group_id": "...",
                "ids": [
                  "..."
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "ok": true,
                  "pairs": 3
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/references/bulk-retire": {
      "post": {
        "operationId": "bulk_retire_image_gen_references",
        "summary": "Bulk Retire/Restore References",
        "description": "Soft-retire (or restore) many references at once. Body: { ids: [...], active: false }.",
        "tags": [
          "Library"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "bulk_retire_image_gen_references",
            "description": "Soft-retire (or restore) many references at once. Body: { ids: [...], active: false }.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": true,
              "idempotentHint": false
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "ids": [
                  "..."
                ],
                "active": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "ok": true,
                  "active": false,
                  "changed": 4
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/references/style-similar": {
      "get": {
        "operationId": "search_image_gen_style_similar",
        "summary": "Style-Similar To A Reference",
        "description": "References nearest a given reference in style-embedding space. ?id is the seed reference, ?k the count.",
        "tags": [
          "Sampling"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "search_image_gen_style_similar",
            "description": "References nearest a given reference in style-embedding space. ?id is the seed reference, ?k the count.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": false,
            "description": "Seed reference id (UUID).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "k",
            "in": "query",
            "required": false,
            "description": "How many to return.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                  "weighted": false,
                  "neighbors": [
                    {
                      "id": "c1d2e3f4-a5b6-4789-90ab-cdef01234567",
                      "ref_id": "REF-67890",
                      "sim": 0.913
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/references/{id}/similar": {
      "get": {
        "operationId": "similar_image_gen_reference",
        "summary": "Image-Similar References",
        "description": "References whose IMAGE content is nearest this reference (image->image similarity).",
        "tags": [
          "Sampling"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "similar_image_gen_reference",
            "description": "References whose IMAGE content is nearest this reference (image->image similarity).",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Reference id (UUID).",
            "schema": {
              "type": "string"
            },
            "example": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                  "neighbors": [
                    {
                      "id": "c1d2e3f4-a5b6-4789-90ab-cdef01234567",
                      "ref_id": "REF-67890",
                      "sim": 0.918
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/references/{id}/proximity-roll": {
      "get": {
        "operationId": "proximity_roll_image_gen_reference",
        "summary": "Proximity Roll",
        "description": "Library references CLOSEST to this seed reference in embedding space, run through the same craft / quality / roll-eligible gates as every roll, each with a cosine score. Built for seeding a generation from an uploaded inspiration's style-neighbors.",
        "tags": [
          "Sampling"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "proximity_roll_image_gen_reference",
            "description": "Library references closest to a seed reference in embedding space, with craft/quality gates and a cosine score.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Seed reference id (UUID).",
            "schema": {
              "type": "string"
            },
            "example": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "How many neighbors to return.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "min_score",
            "in": "query",
            "required": false,
            "description": "Drop neighbors below this cosine similarity (0..1).",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "space",
            "in": "query",
            "required": false,
            "description": "image (visual-content match, default) or style.",
            "schema": {
              "type": "string",
              "enum": [
                "image",
                "style"
              ]
            }
          },
          {
            "name": "account_number",
            "in": "query",
            "required": false,
            "description": "Unlock that church's own church-specific work in the neighbor pool.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "season",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "design_style",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                  "space": "image",
                  "references": [
                    {
                      "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                      "ref_id": "REF-12345",
                      "url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/original",
                      "preview_url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/preview",
                      "colors": [
                        "#0a7d3b",
                        "#f4ede1",
                        "#1a1a1a"
                      ],
                      "color_names": [
                        "Forest",
                        "Cream",
                        "Charcoal"
                      ],
                      "palette_tag": "warm-sage",
                      "type": "sermon",
                      "season": "summer",
                      "design_styles": [
                        "editorial"
                      ],
                      "aspect": "social",
                      "text_coverage": "minimal",
                      "account_number": "2049",
                      "active": true,
                      "score": 0.88
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Reference not found"
          },
          "409": {
            "description": "Seed has no embedding yet"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/references/{id}/style-similar": {
      "get": {
        "operationId": "style_similar_image_gen_reference",
        "summary": "Style-Similar References",
        "description": "References nearest this one in style space. ?limit caps the count.",
        "tags": [
          "Sampling"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "style_similar_image_gen_reference",
            "description": "References nearest this one in style space. ?limit caps the count.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Reference id (UUID).",
            "schema": {
              "type": "string"
            },
            "example": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "How many to return.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "references": [
                    {
                      "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                      "ref_id": "REF-12345",
                      "url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/original",
                      "preview_url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/preview",
                      "colors": [
                        "#0a7d3b",
                        "#f4ede1",
                        "#1a1a1a"
                      ],
                      "color_names": [
                        "Forest",
                        "Cream",
                        "Charcoal"
                      ],
                      "palette_tag": "warm-sage",
                      "type": "sermon",
                      "season": "summer",
                      "design_styles": [
                        "editorial"
                      ],
                      "aspect": "social",
                      "text_coverage": "minimal",
                      "account_number": "2049",
                      "active": true,
                      "score": 0.9
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/branding": {
      "get": {
        "operationId": "list_image_gen_brand_profiles",
        "summary": "List Brand Profiles",
        "description": "Extracted church brand profiles (colors, fonts, logos, guides, card). Newest first.",
        "tags": [
          "Branding"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "list_image_gen_brand_profiles",
            "description": "Extracted church brand profiles (colors, fonts, logos, guides, card). Newest first.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "profiles": [
                    {
                      "account_number": "2049",
                      "church_name": "Grace Community Church",
                      "status": "ready",
                      "source": "dropbox",
                      "has_card": true
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/branding/accounts": {
      "get": {
        "operationId": "list_image_gen_brand_accounts",
        "summary": "List Brandable Accounts",
        "description": "Churches that have a Client Assets folder (the universe of accounts a brand profile can be extracted for).",
        "tags": [
          "Branding"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "list_image_gen_brand_accounts",
            "description": "Churches that have a Client Assets folder (the universe of accounts a brand profile can be extracted for).",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "accounts": [
                    {
                      "account": "2049",
                      "name": "Grace Community Church",
                      "scanned": true,
                      "active": true
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/branding/extract": {
      "post": {
        "operationId": "extract_image_gen_brand",
        "summary": "Extract A Brand Profile",
        "description": "Build/refresh one church's brand profile from the best source (strategy guide -> Dropbox -> website). Body: { account_number }. ?stream=1 for live NDJSON progress.",
        "tags": [
          "Branding"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "extract_image_gen_brand",
            "description": "Build/refresh one church's brand profile from the best source (strategy guide -> Dropbox -> website). Body: { account_number }. ?stream=1 for live NDJSON progress.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "stream",
            "in": "query",
            "required": false,
            "description": "stream=1 streams live NDJSON progress.",
            "schema": {
              "type": "integer",
              "enum": [
                1
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "account_number": "2049"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "runId": "run_abc123",
                  "publicAccessToken": "pk_tr_xxx"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/branding/extract/bulk": {
      "post": {
        "operationId": "extract_image_gen_brands_bulk",
        "summary": "Bulk Extract Brand Profiles",
        "description": "Extract brand profiles for many churches. Body: { account_numbers: [...] }.",
        "tags": [
          "Branding"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "extract_image_gen_brands_bulk",
            "description": "Extract brand profiles for many churches. Body: { account_numbers: [...] }.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "account_numbers": [
                  "2049",
                  "1048"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "tag": "brand-extract:2026-06-22",
                  "publicAccessToken": "pk_tr_xxx",
                  "count": 8
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/remix/import": {
      "get": {
        "operationId": "list_image_gen_remix_imports",
        "summary": "Remix Import Status",
        "description": "Recent Remix import activity / status.",
        "tags": [
          "Import"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "list_image_gen_remix_imports",
            "description": "Recent Remix import activity / status.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "remaining": 240
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      },
      "post": {
        "operationId": "import_image_gen_remix",
        "summary": "Import From Remix",
        "description": "Pull graphics from the Remix source into the reference library (ingested via the shared pipeline). ?stream=1 for live NDJSON progress.",
        "tags": [
          "Import"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "import_image_gen_remix",
            "description": "Pull graphics from the Remix source into the reference library (ingested via the shared pipeline). ?stream=1 for live NDJSON progress.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "stream",
            "in": "query",
            "required": false,
            "description": "stream=1 streams live NDJSON progress.",
            "schema": {
              "type": "integer",
              "enum": [
                1
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Cap the import batch.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "imported": 18,
                  "skipped": 2,
                  "failed": 0,
                  "rejected": 1
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/qc": {
      "get": {
        "operationId": "list_qc_checks",
        "summary": "List QC Checks",
        "description": "Recent QC checks (design/video quality-control results), newest first — the QC dashboard history. Optional ?task_id scopes to one ClickUp task; ?limit caps the count (default 50, max 200).",
        "tags": [
          "QC"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "list_qc_checks",
            "description": "Recent QC checks (design/video quality-control results), newest first — the QC dashboard history. Optional ?task_id scopes to one ClickUp task; ?limit caps the count (default 50, max 200).",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "task_id",
            "in": "query",
            "required": false,
            "description": "Scope to one ClickUp task id.",
            "schema": {
              "type": "string",
              "example": "86e1rrnjf"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows (1-200, default 50).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "items": [
                    {
                      "id": "q1a2b3c4-d5e6-4789-a0b1-c2d3e4f5a6b7",
                      "task_id": "86abc12",
                      "status": "pass",
                      "file_name": "concept-1.png",
                      "created_at": "2026-06-22T12:00:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/qc/scan": {
      "post": {
        "operationId": "run_qc_scan",
        "summary": "Run A QC Scan",
        "description": "Run a QC scan on a ClickUp task's design concepts or final files (up to 25, judged separately). Modes: initial (first concepts — brief compliance + spell + craft), revision (also verifies requested changes from task comments), deliverables (checks finals against the task's ClickUp 'Deliverables' checklist — coverage + dimensions + format). Files come from uploads (multipart) OR selected Dropbox paths (concept_paths) OR are auto-pulled (newest round, or finals in deliverables mode). Runs on Trigger.dev; returns { runId, publicAccessToken } to follow live. Send JSON, or multipart/form-data with image file field(s) for manual uploads.",
        "tags": [
          "QC"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "run_qc_scan",
            "description": "Run a QC scan on a ClickUp task's design concepts or final files (up to 25, judged separately). Modes: initial (first concepts — brief compliance + spell + craft), revision (also verifies requested changes from task comments), deliverables (checks finals against the task's ClickUp 'Deliverables' checklist — coverage + dimensions + format). Files come from uploads (multipart) OR selected Dropbox paths (concept_paths) OR are auto-pulled (newest round, or finals in deliverables mode). Runs on Trigger.dev; returns { runId, publicAccessToken } to follow live. Send JSON, or multipart/form-data with image file field(s) for manual uploads.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "stream",
            "in": "query",
            "required": false,
            "description": "stream=1 streams live NDJSON progress instead of returning the run handle.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "task_id": "86e1rrnjf",
                "mode": "deliverables"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "task_id": {
                    "type": "string"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "initial",
                      "revision",
                      "deliverables"
                    ]
                  },
                  "image": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "runId": "run_qc_abc",
                  "publicAccessToken": "pk_tr_xxx"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/qc/concepts": {
      "get": {
        "operationId": "list_qc_concepts",
        "summary": "List A Task's Concepts & Finals",
        "description": "List a ClickUp task's Dropbox files grouped into concept rounds plus the finals set, so a caller can pick which to QC. Returns { church_name, account, rounds:[{name,path,image_count,images}], finals }.",
        "tags": [
          "QC"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "list_qc_concepts",
            "description": "List a ClickUp task's Dropbox files grouped into concept rounds plus the finals set, so a caller can pick which to QC. Returns { church_name, account, rounds:[{name,path,image_count,images}], finals }.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "task_id",
            "in": "query",
            "required": true,
            "description": "ClickUp task id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "task_id": "86abc12",
                  "church_name": "Grace Community Church",
                  "account": "2049",
                  "rounds": [
                    {
                      "round": 1,
                      "files": [
                        {
                          "name": "concept-1.png",
                          "url": "https://…"
                        }
                      ]
                    }
                  ],
                  "finals": []
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/qc/brief-extract": {
      "post": {
        "operationId": "extract_qc_brief",
        "summary": "Extract Brief Fields",
        "description": "Read a task's brief (description + project-request) into the canonical checkable fields (partner name, dates, times, locations, scripture, key copy, required elements, requested deliverables) plus the revision requests parsed from comments — for a reviewer to confirm before scanning. Returns { fields, revision_requests }.",
        "tags": [
          "QC"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "extract_qc_brief",
            "description": "Read a task's brief (description + project-request) into the canonical checkable fields (partner name, dates, times, locations, scripture, key copy, required elements, requested deliverables) plus the revision requests parsed from comments — for a reviewer to confirm before scanning. Returns { fields, revision_requests }.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "task_id": "86e1rrnjf"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "task_id": "86abc12",
                  "brief": {
                    "title": "Easter 2026",
                    "required_elements": [
                      "date",
                      "service times"
                    ],
                    "deliverables": [
                      "1080x1080",
                      "1920x1080"
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/qc/dismissals": {
      "get": {
        "operationId": "list_qc_dismissals",
        "summary": "List QC Dismissals",
        "description": "Designer-set suppressions of QC findings (a false positive or an intentional choice), scoped to a task or its church. ?task_id=&account= returns the dismissals applying to a task; ?report=1 returns the over-zealousness aggregate (which findings get dismissed most).",
        "tags": [
          "QC"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "list_qc_dismissals",
            "description": "Designer-set suppressions of QC findings (a false positive or an intentional choice), scoped to a task or its church. ?task_id=&account= returns the dismissals applying to a task; ?report=1 returns the over-zealousness aggregate (which findings get dismissed most).",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "task_id",
            "in": "query",
            "required": false,
            "description": "Task to list applicable dismissals for.",
            "schema": {
              "type": "string",
              "example": "86e1rrnjf"
            }
          },
          {
            "name": "account",
            "in": "query",
            "required": false,
            "description": "Account number (church) for account-scoped dismissals.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "report",
            "in": "query",
            "required": false,
            "description": "report=1 returns the dismissal-frequency aggregate.",
            "schema": {
              "type": "integer",
              "enum": [
                1
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "items": [
                    {
                      "id": "d1a2b3c4-d5e6-4789-a0b1-c2d3e4f5a6b7",
                      "scope": "task",
                      "finding_kind": "brand",
                      "finding_value": null,
                      "created_at": "2026-06-22T12:00:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      },
      "post": {
        "operationId": "create_qc_dismissal",
        "summary": "Dismiss A QC Finding",
        "description": "Suppress a QC finding for this task or the whole church. Applied on the next scan and logged for the over-zealousness report. Body: { scope: 'task'|'account', finding_kind, finding_value, clickup_task_id, account_number, reason }.",
        "tags": [
          "QC"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "create_qc_dismissal",
            "description": "Suppress a QC finding for this task or the whole church. Applied on the next scan and logged for the over-zealousness report. Body: { scope: 'task'|'account', finding_kind, finding_value, clickup_task_id, account_number, reason }.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "scope": "task",
                "finding_kind": "catch",
                "finding_value": "internal_contradiction",
                "clickup_task_id": "86e1rrnjf",
                "reason": "false positive"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "dismissal": {
                    "id": "d1a2b3c4-d5e6-4789-a0b1-c2d3e4f5a6b7",
                    "scope": "task",
                    "finding_kind": "spelling",
                    "finding_value": "Funday"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/qc/dismissals/{id}": {
      "delete": {
        "operationId": "delete_qc_dismissal",
        "summary": "Remove A QC Dismissal",
        "description": "Delete a dismissal by id (re-enables the finding on the next scan).",
        "tags": [
          "QC"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "delete_qc_dismissal",
            "description": "Delete a dismissal by id (re-enables the finding on the next scan).",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": true,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Dismissal id (UUID).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "ok": true
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/qc/{id}": {
      "get": {
        "operationId": "get_qc_check",
        "summary": "Get A QC Check",
        "description": "A QC check by its id (UUID), or the LATEST check for a ClickUp task id. Returns { check } with the verdict, per-field brief compliance, spelling, links, craft, and (for video) timestamped findings.",
        "tags": [
          "QC"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_qc_check",
            "description": "A QC check by its id (UUID), or the LATEST check for a ClickUp task id. Returns { check } with the verdict, per-field brief compliance, spelling, links, craft, and (for video) timestamped findings.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "QC check id (UUID) or a ClickUp task id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "check": {
                    "id": "q1a2b3c4-d5e6-4789-a0b1-c2d3e4f5a6b7",
                    "task_id": "86abc12",
                    "status": "fail",
                    "findings": [
                      {
                        "kind": "spelling",
                        "severity": "high",
                        "message": "“Fridya” should be “Friday”"
                      }
                    ],
                    "file_name": "concept-1.png"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/qc/{id}/asset": {
      "get": {
        "operationId": "get_qc_asset",
        "summary": "Get A QC Concept Preview",
        "description": "The checked concept's stored preview image (or original video) for display. Binary image/video; streamed from storage.",
        "tags": [
          "QC"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "QC check id (UUID).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "No preview available"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/qc/{id}/video-url": {
      "get": {
        "operationId": "get_qc_video_url",
        "summary": "Get A QC Video Stream URL",
        "description": "A directly-streamable, range-seekable URL for the checked video (presigned storage URL, or a Dropbox temp link for large clips). Returns { url } (or { url: null } when unavailable).",
        "tags": [
          "QC"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "QC check id (UUID).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "url": "https://ai-image-gen-reference.vercel.app/api/r/…/original"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/tasks/list": {
      "get": {
        "operationId": "get_tasks",
        "summary": "Get Many Tasks",
        "description": "Returns a paginated list of tasks with assignees, tags, status, time tracking, and more. Only returns tasks associated with an account. Deleted and archived tasks are always excluded. All filters are optional except include_closed.",
        "tags": [
          "Tasks"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/tasks/get-tasks)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_tasks",
            "description": "Returns a paginated list of tasks with assignees, tags, status, time tracking, and more. Only returns tasks associated with an account. Deleted and archived tasks are always excluded. All filters are optional except include_closed.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "include_closed",
            "in": "query",
            "required": true,
            "description": "Whether to include tasks with status 'closed' or 'complete'. Required.",
            "schema": {
              "type": "boolean"
            },
            "example": false
          },
          {
            "name": "account",
            "in": "query",
            "required": false,
            "description": "Filter by account number(s). Comma-separated for multiple (e.g., 1738,1963).",
            "schema": {
              "type": "string"
            },
            "example": "1738,1963"
          },
          {
            "name": "church_name",
            "in": "query",
            "required": false,
            "description": "Filter by church name(s), case-insensitive partial match. Comma-separated for multiple.",
            "schema": {
              "type": "string"
            },
            "example": "Resurrection,Bethany"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by task status(es). Comma-separated for multiple.",
            "schema": {
              "type": "string"
            },
            "example": "received,in progress"
          },
          {
            "name": "tag",
            "in": "query",
            "required": false,
            "description": "Filter by tag name(s). Task must have at least one matching tag. Comma-separated for multiple.",
            "schema": {
              "type": "string"
            },
            "example": "sermonseries,minevent"
          },
          {
            "name": "department",
            "in": "query",
            "required": false,
            "description": "Filter by responsible department(s). Case-insensitive partial match. Comma-separated for multiple.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "brand",
                  "creative direction",
                  "creative division",
                  "c-suite",
                  "customer experience",
                  "design",
                  "exec",
                  "marketing",
                  "remix",
                  "sales",
                  "social media",
                  "strategy division",
                  "systems integration",
                  "video",
                  "web"
                ]
              }
            },
            "example": [
              "design",
              "video"
            ]
          },
          {
            "name": "assignee_email",
            "in": "query",
            "required": false,
            "description": "Filter by assignee email(s). Matches internal or external. Comma-separated for multiple.",
            "schema": {
              "type": "string"
            },
            "example": "jane.doe@churchmediasquad.com"
          },
          {
            "name": "is_designer_assigned",
            "in": "query",
            "required": false,
            "description": "Filter by whether a designer is assigned",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "is_employee_assigned",
            "in": "query",
            "required": false,
            "description": "Filter by whether an internal employee is assigned",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "due_date_from",
            "in": "query",
            "required": false,
            "description": "Filter tasks with due date on or after this date (YYYY-MM-DD)",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-03-01"
          },
          {
            "name": "due_date_to",
            "in": "query",
            "required": false,
            "description": "Filter tasks with due date on or before this date (YYYY-MM-DD)",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-04-30"
          },
          {
            "name": "created_from",
            "in": "query",
            "required": false,
            "description": "Filter tasks created on or after this timestamp",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "created_to",
            "in": "query",
            "required": false,
            "description": "Filter tasks created on or before this timestamp",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "min_time_estimate",
            "in": "query",
            "required": false,
            "description": "Minimum time estimate in minutes",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "max_time_estimate",
            "in": "query",
            "required": false,
            "description": "Maximum time estimate in minutes",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "min_time_tracked",
            "in": "query",
            "required": false,
            "description": "Minimum time tracked in minutes",
            "schema": {
              "type": "number",
              "minimum": 0
            }
          },
          {
            "name": "max_time_tracked",
            "in": "query",
            "required": false,
            "description": "Maximum time tracked in minutes",
            "schema": {
              "type": "number",
              "minimum": 0
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1)",
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Results per page (default 50, max 250)",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 250
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated task list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "has_more": {
                      "type": "boolean",
                      "description": "Whether more results exist beyond this page"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "per_page": {
                      "type": "integer"
                    },
                    "filters": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing include_closed or invalid parameters"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/tasks/queued": {
      "get": {
        "operationId": "get_queued_tasks",
        "summary": "Get Queued Tasks",
        "description": "Returns currently queued tasks from both auto-scheduling and auto-assign logs. Includes queue position, current status, assignees, and department. Ordered by queue number.",
        "tags": [
          "Tasks"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/tasks/get-queued-tasks)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_queued_tasks",
            "description": "Returns currently queued tasks from both auto-scheduling and auto-assign logs. Includes queue position, current status, assignees, and department. Ordered by queue number.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "department",
            "in": "query",
            "required": false,
            "description": "Filter by department(s). Case-insensitive partial match. Comma-separated for multiple.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "brand",
                  "creative direction",
                  "creative division",
                  "c-suite",
                  "customer experience",
                  "design",
                  "exec",
                  "marketing",
                  "remix",
                  "sales",
                  "social media",
                  "strategy division",
                  "systems integration",
                  "video",
                  "web"
                ]
              }
            },
            "example": [
              "design"
            ]
          },
          {
            "name": "account",
            "in": "query",
            "required": false,
            "description": "Filter by account number(s). Comma-separated for multiple.",
            "schema": {
              "type": "string"
            },
            "example": "2220"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by current task status",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1)",
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Results per page (default 50, max 250)",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 250
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated queued task list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/tasks/{task_id}": {
      "get": {
        "operationId": "get_task_details",
        "summary": "Get Task",
        "description": "Returns detailed information about a ClickUp task including assignees, status, tags, time tracking, due date, department, and designer assignment info.",
        "tags": [
          "Tasks"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/tasks/get-task-details)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_task_details",
            "description": "Returns detailed information about a ClickUp task including assignees, status, tags, time tracking, due date, department, and designer assignment info.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "description": "The ClickUp task ID",
            "schema": {
              "type": "string"
            },
            "example": "86dxgmhh2"
          },
          {
            "name": "include_markdown_description",
            "in": "query",
            "required": false,
            "description": "When true, fetches the task description from the ClickUp API in parallel and returns it under `markdown_description`.",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "example": false
          }
        ],
        "responses": {
          "200": {
            "description": "Task details from get_task_details_v3 RPC. Includes the current project folder path under `path_display` and `folder_id` from view_latest_folder_paths. Includes the mapped project type under `project_type_id`, `project_type_name`, and `srp` (supports both the new forms-based process and the legacy project-submission process). Includes `markdown_description` when `include_markdown_description=true`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing task_id"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/tasks/{task_id}/assignee-history": {
      "get": {
        "operationId": "get_task_assignee_history",
        "summary": "Get Task Assignee History",
        "description": "Returns current assignees and full assignee change history for a task. Unions data from public and clickup schemas. Current assignees are determined by add events with no subsequent remove.",
        "tags": [
          "Tasks"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/tasks/get-assignee-history)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_task_assignee_history",
            "description": "Returns current assignees and full assignee change history for a task. Unions data from public and clickup schemas. Current assignees are determined by add events with no subsequent remove.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "description": "The ClickUp task ID",
            "schema": {
              "type": "string"
            },
            "example": "86dxgmhh2"
          }
        ],
        "responses": {
          "200": {
            "description": "Current assignees and change history",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing task_id"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/tasks/{task_id}/status-history": {
      "get": {
        "operationId": "get_task_status_history",
        "summary": "Get Task Status History",
        "description": "Returns current status (with color and active flag) and full status change history for a task. Unions data from public and clickup schemas.",
        "tags": [
          "Tasks"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/tasks/get-status-history)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_task_status_history",
            "description": "Returns current status (with color and active flag) and full status change history for a task. Unions data from public and clickup schemas.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "description": "The ClickUp task ID",
            "schema": {
              "type": "string"
            },
            "example": "86dxgmhh2"
          }
        ],
        "responses": {
          "200": {
            "description": "Current status and change history",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing task_id"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/tasks/{task_id}/due-date-history": {
      "get": {
        "operationId": "get_task_due_date_history",
        "summary": "Get Task Due Date History",
        "description": "Returns current due date and full due date change history for a task. Unions data from public and clickup schemas.",
        "tags": [
          "Tasks"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/tasks/get-due-date-history)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_task_due_date_history",
            "description": "Returns current due date and full due date change history for a task. Unions data from public and clickup schemas.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "description": "The ClickUp task ID",
            "schema": {
              "type": "string"
            },
            "example": "86dxgmhh2"
          }
        ],
        "responses": {
          "200": {
            "description": "Current due date and change history",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing task_id"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/tasks/{task_id}/tag-history": {
      "get": {
        "operationId": "get_task_tag_history",
        "summary": "Get Task Tag History",
        "description": "Returns current active tags and full tag change history for a task. Current tags are determined by excluding tags that were later removed. Unions data from public and clickup schemas.",
        "tags": [
          "Tasks"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/tasks/get-tag-history)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_task_tag_history",
            "description": "Returns current active tags and full tag change history for a task. Current tags are determined by excluding tags that were later removed. Unions data from public and clickup schemas.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "description": "The ClickUp task ID",
            "schema": {
              "type": "string"
            },
            "example": "86dxgmhh2"
          }
        ],
        "responses": {
          "200": {
            "description": "Current tags and change history",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing task_id"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/tasks/{task_id}/time-estimate-history": {
      "get": {
        "operationId": "get_task_time_estimate_history",
        "summary": "Get Task Time Estimate History",
        "description": "Returns current time estimate (in minutes) and full time estimate change history for a task. Unions data from public and clickup schemas.",
        "tags": [
          "Tasks"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/tasks/get-time-estimate-history)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_task_time_estimate_history",
            "description": "Returns current time estimate (in minutes) and full time estimate change history for a task. Unions data from public and clickup schemas.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "description": "The ClickUp task ID",
            "schema": {
              "type": "string"
            },
            "example": "86dxgmhh2"
          }
        ],
        "responses": {
          "200": {
            "description": "Current estimate and change history",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing task_id"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/tasks/{task_id}/time-tracking-history": {
      "get": {
        "operationId": "get_task_time_tracking_history",
        "summary": "Get Task Time Tracking History",
        "description": "Returns total tracked minutes and full time tracking entry history for a task. Deduplicates by tracking_id across public and clickup schemas.",
        "tags": [
          "Tasks"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/tasks/get-time-tracking-history)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_task_time_tracking_history",
            "description": "Returns total tracked minutes and full time tracking entry history for a task. Deduplicates by tracking_id across public and clickup schemas.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "description": "The ClickUp task ID",
            "schema": {
              "type": "string"
            },
            "example": "86dxgmhh2"
          }
        ],
        "responses": {
          "200": {
            "description": "Total tracked time and entry history",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing task_id"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/accounts/list": {
      "get": {
        "operationId": "get_accounts",
        "summary": "Get Many Accounts",
        "description": "Returns a paginated list of accounts with active products. Supports filtering by account ID, church name, status, product, usage, timezone, monthly rate range, subscription start date range, and airtable field values. Does not include acc_airtable_data — use the details endpoint for that.",
        "tags": [
          "Accounts"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/accounts/get-accounts)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_accounts",
            "description": "Returns a paginated list of accounts with active products. Supports filtering by account ID, church name, status, product, usage, timezone, monthly rate range, subscription start date range, and airtable field values. Does not include acc_airtable_data — use the details endpoint for that.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "account_id",
            "in": "query",
            "required": false,
            "description": "Filter by account number(s). Comma-separated for multiple.",
            "schema": {
              "type": "string"
            },
            "example": "2220,2945"
          },
          {
            "name": "church_name",
            "in": "query",
            "required": false,
            "description": "Filter by church name(s), case-insensitive partial match. Comma-separated for multiple.",
            "schema": {
              "type": "string"
            },
            "example": "Lake Sawyer"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by account status(es). Comma-separated for multiple (e.g., Active, Cancelled, Non-Renewing).",
            "schema": {
              "type": "string"
            },
            "example": "Active"
          },
          {
            "name": "product",
            "in": "query",
            "required": false,
            "description": "Filter by active product name(s). Returns accounts that have at least one matching current product. Comma-separated for multiple.",
            "schema": {
              "type": "string"
            },
            "example": "Unlimited"
          },
          {
            "name": "high_usage",
            "in": "query",
            "required": false,
            "description": "Filter by high usage flag",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "timezone",
            "in": "query",
            "required": false,
            "description": "Filter by timezone (case-insensitive match)",
            "schema": {
              "type": "string"
            },
            "example": "Pacific"
          },
          {
            "name": "comms_rep",
            "in": "query",
            "required": false,
            "description": "Filter by communications rep (case-insensitive match)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "min_monthly_rate",
            "in": "query",
            "required": false,
            "description": "Minimum monthly rate filter",
            "schema": {
              "type": "number",
              "minimum": 0
            }
          },
          {
            "name": "max_monthly_rate",
            "in": "query",
            "required": false,
            "description": "Maximum monthly rate filter",
            "schema": {
              "type": "number",
              "minimum": 0
            }
          },
          {
            "name": "original_sub_start_from",
            "in": "query",
            "required": false,
            "description": "Filter accounts with original subscription start date on or after (YYYY-MM-DD)",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "original_sub_start_to",
            "in": "query",
            "required": false,
            "description": "Filter accounts with original subscription start date on or before (YYYY-MM-DD)",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "airtable_field",
            "in": "query",
            "required": false,
            "description": "Name of a field inside acc_airtable_data.fields to filter by. Must be used together with airtable_value.",
            "schema": {
              "type": "string"
            },
            "example": "Design Profiles"
          },
          {
            "name": "airtable_value",
            "in": "query",
            "required": false,
            "description": "Value to match against the airtable_field (case-insensitive partial match). Must be used together with airtable_field.",
            "schema": {
              "type": "string"
            },
            "example": "Minimal"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1)",
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Results per page (default 50, max 250)",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 250
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated account list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "has_more": {
                      "type": "boolean",
                      "description": "Whether more results exist beyond this page"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "per_page": {
                      "type": "integer"
                    },
                    "filters": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/accounts/{account_id}": {
      "get": {
        "operationId": "get_account_details",
        "summary": "Get Account",
        "description": "Returns detailed information about a single account including church info, subscription details, airtable data, and current active products.",
        "tags": [
          "Accounts"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/accounts/get-account-details)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_account_details",
            "description": "Returns detailed information about a single account including church info, subscription details, airtable data, and current active products.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "account_id",
            "in": "path",
            "required": true,
            "description": "The account/member number",
            "schema": {
              "type": "integer"
            },
            "example": 2220
          },
          {
            "name": "includewebsites",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "When true, attach a 'websites' array to the response listing the account's websites (from strategy_account_websites, matched on the account number). Defaults to false."
          },
          {
            "name": "include_brand_profile",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": true
            },
            "description": "When true (the default), attach a 'brand_profile' object to the response containing the account's extracted brand profile (same data as GET /v1/image-gen/branding/{account}). Set to false to skip the brand-gen lookup. If no profile has been extracted yet, or the brand-gen service errors, 'brand_profile' is null."
          }
        ],
        "responses": {
          "200": {
            "description": "Account details including active products and airtable data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing or invalid account_id"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Account not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/users/list": {
      "get": {
        "operationId": "get_users",
        "summary": "Get Many Users",
        "description": "Returns a paginated list of ClickUp users with optional filters for account, email domain, employee status, active status, and department.",
        "tags": [
          "Users"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/users/get-users)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_users",
            "description": "Returns a paginated list of ClickUp users with optional filters for account, email domain, employee status, active status, and department.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "account",
            "in": "query",
            "required": false,
            "description": "Filter by account ID(s). Comma-separated for multiple.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email_domain",
            "in": "query",
            "required": false,
            "description": "Filter by email domain (e.g., churchmediasquad.com)",
            "schema": {
              "type": "string"
            },
            "example": "churchmediasquad.com"
          },
          {
            "name": "is_employee",
            "in": "query",
            "required": false,
            "description": "Filter for internal employees (true) or non-employees (false)",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "active",
            "in": "query",
            "required": false,
            "description": "Filter by active status in ClickUp",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "department",
            "in": "query",
            "required": false,
            "description": "Filter by employee department(s). Case-insensitive partial match. Comma-separated for multiple.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "brand",
                  "creative direction",
                  "creative division",
                  "c-suite",
                  "customer experience",
                  "design",
                  "exec",
                  "marketing",
                  "remix",
                  "sales",
                  "social media",
                  "strategy division",
                  "systems integration",
                  "video",
                  "web"
                ]
              }
            },
            "example": [
              "design"
            ]
          },
          {
            "name": "name",
            "in": "query",
            "required": false,
            "description": "Search by first name. Requires 'name_match' to be set. Results are ordered by match relevance.",
            "schema": {
              "type": "string"
            },
            "example": "alex"
          },
          {
            "name": "name_match",
            "in": "query",
            "required": false,
            "description": "Required when 'name' is provided. 'fuzzy' returns close matches (e.g. 'alex' finds Alexa, Alexander). 'exact' returns only exact first name matches.",
            "schema": {
              "type": "string",
              "enum": [
                "fuzzy",
                "exact"
              ]
            },
            "example": "fuzzy"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1)",
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Results per page (default 50, max 250)",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 250
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated user list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/users/{identifier}": {
      "get": {
        "operationId": "get_user",
        "summary": "Get User",
        "description": "Returns a single ClickUp user by email address or ClickUp ID. If the identifier contains @, it's treated as email; otherwise as ClickUp ID. Includes employee details when the user is internal staff.",
        "tags": [
          "Users"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/users/get-user-details)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_user",
            "description": "Returns a single ClickUp user by email address or ClickUp ID. If the identifier contains @, it's treated as email; otherwise as ClickUp ID. Includes employee details when the user is internal staff.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "identifier",
            "in": "path",
            "required": true,
            "description": "Email address or ClickUp ID",
            "schema": {
              "type": "string"
            },
            "example": "jose@churchmediasquad.com"
          }
        ],
        "responses": {
          "200": {
            "description": "User details with employee info when applicable",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing identifier"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "User not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/prf/project-submissions": {
      "get": {
        "operationId": "list_prf_project_submissions",
        "summary": "List Project Submissions",
        "description": "Lists PRF project submissions with optional filters. Supports filtering by general submission UUID (giid), ClickUp task id (task_id), task presence (has_task_id), member account number (account), project types, and submission end_time range. Airtable record ids are never exposed — `member_number` is returned in place of internal account ids. The renamed `task_id` field replaces the legacy `clickup_id`.",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/list-project-submissions)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "list_prf_project_submissions",
            "description": "List PRF project submissions filtered by giid, task_id, has_task_id, account (member number), project_types, and submission end_time range. Returns member_number instead of internal Airtable account ids and renames clickup_id to task_id.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "giid",
            "in": "query",
            "required": false,
            "description": "Filter by general submission UUID.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "task_id",
            "in": "query",
            "required": false,
            "description": "Filter by exact ClickUp task id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "has_task_id",
            "in": "query",
            "required": false,
            "description": "If true, only return submissions that have a task_id set; if false, only those without one.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "account",
            "in": "query",
            "required": false,
            "description": "Filter by member account number (NOT an Airtable record id).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "project_types",
            "in": "query",
            "required": false,
            "description": "Comma-separated list of project_type values (e.g. `60,64,75`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "end_time_from",
            "in": "query",
            "required": false,
            "description": "Inclusive lower bound on submission end_time (ISO 8601).",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "end_time_to",
            "in": "query",
            "required": false,
            "description": "Inclusive upper bound on submission end_time (ISO 8601).",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows to return (1-500, default 50).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Row offset for pagination.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Project submissions matching the filters",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total": {
                      "type": "integer"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    },
                    "submissions": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameter"
          },
          "401": {
            "description": "Unauthorized"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/prf/project-submissions/{identifier}": {
      "get": {
        "operationId": "get_prf_project_submission",
        "summary": "Get Project Submission",
        "description": "Returns a single PRF project submission by its UUID or by ClickUp task ID. UUID format is auto-detected.",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/get-project-submission)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_prf_project_submission",
            "description": "Returns a single PRF project submission by its UUID or by ClickUp task ID. UUID format is auto-detected.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "identifier",
            "in": "path",
            "required": true,
            "description": "Project submission UUID or ClickUp task ID",
            "schema": {
              "type": "string"
            },
            "example": "86dz6a6ye"
          }
        ],
        "responses": {
          "200": {
            "description": "Project submission details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing identifier"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Project submission not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/prf/project-submissions/{identifier}/duplicates": {
      "get": {
        "operationId": "get_prf_submission_duplicates",
        "summary": "Find Duplicate Submissions",
        "description": "Returns PRF project submissions that look like duplicates of the given submission, using the same answer-value comparison the Submission Viewer uses. The identifier may be a project submission UUID or a ClickUp task ID (auto-detected). Each candidate is returned with its own submission id and ClickUp task id.",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/get-submission-duplicates)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_prf_submission_duplicates",
            "description": "Finds potential duplicate PRF project submissions for a given submission UUID or ClickUp task ID, returning each candidate's submission id and ClickUp task id with a similarity score.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "identifier",
            "in": "path",
            "required": true,
            "description": "Project submission UUID or ClickUp task ID",
            "schema": {
              "type": "string"
            },
            "example": "86dz6a6ye"
          },
          {
            "name": "similarity_threshold",
            "in": "query",
            "required": false,
            "description": "Minimum answer-overlap score (0-1) for a submission to count as a possible duplicate. Defaults to 0.7 (matches the Submission Viewer).",
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "default": 0.7
            },
            "example": 0.7
          },
          {
            "name": "max_results",
            "in": "query",
            "required": false,
            "description": "Maximum number of candidate duplicates to return (1-200).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "example": 50
          }
        ],
        "responses": {
          "200": {
            "description": "Source submission plus its possible duplicates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "source": {
                      "type": "object",
                      "properties": {
                        "identifier": {
                          "type": "string"
                        },
                        "submission_id": {
                          "type": "string"
                        },
                        "clickup_id": {
                          "type": "string",
                          "nullable": true
                        },
                        "project_name": {
                          "type": "string",
                          "nullable": true
                        }
                      }
                    },
                    "similarity_threshold": {
                      "type": "number"
                    },
                    "max_results": {
                      "type": "integer"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "duplicates": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "submission_id": {
                            "type": "string"
                          },
                          "clickup_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "task_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "project_name": {
                            "type": "string",
                            "nullable": true
                          },
                          "user_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "account_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "created_at": {
                            "type": "string",
                            "nullable": true
                          },
                          "similarity_percent": {
                            "type": "number",
                            "nullable": true
                          },
                          "time_difference": {
                            "type": "string",
                            "nullable": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing identifier"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Project submission not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/prf/project-selection-page": {
      "get": {
        "operationId": "get_prf_project_selection_page",
        "summary": "Get Project Selection Page Data",
        "description": "Bundles every read the PRF4 project selection page needs into a single response. Each slice (account, projects, projectPermissions, ministryDepartments, queueStats, mostUsedTags, generalSubmissions, inProgressSubmissions, helpContent, scheduleDrafts) returns its own {data, error} envelope so a partial failure does not blank the page. Replaces ~10 separate round trips with one edge-side Promise.all fan-out.",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/get-project-selection-page)"
          },
          "policies": {
            "inbound": [
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "parameters": [
          {
            "name": "memberId",
            "in": "query",
            "required": true,
            "description": "Numeric Supabase account id (accounts.account).",
            "schema": {
              "type": "integer"
            },
            "example": 306
          },
          {
            "name": "userId",
            "in": "query",
            "required": true,
            "description": "ClickUp user id of the caller. Required because get_project_permissions_v4 takes a user id alongside the member id.",
            "schema": {
              "type": "string"
            },
            "example": "1381408"
          }
        ],
        "responses": {
          "200": {
            "description": "Bundled selection-page data. Every slice is shaped as {data, error}; a non-null error on a slice means that slice failed while the others may still be usable.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing or invalid memberId/userId"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/v1/prf/submission-context": {
      "get": {
        "operationId": "get_prf_submission_context",
        "summary": "Get Submission Context",
        "description": "Bundles the gisId-keyed reads the PRF4 project-details page needs into a single response. Each slice (projectSubmissions, primarySubmissions) returns its own {data, error} envelope so a partial failure does not blank the page.",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/get-submission-context)"
          },
          "policies": {
            "inbound": [
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_prf_submission_context",
            "description": "Returns gisId-scoped project-details bundle: list of project submissions under a general submission, plus the primary-submission detection slice.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "memberId",
            "in": "query",
            "required": true,
            "description": "Numeric Supabase account id (accounts.account).",
            "schema": {
              "type": "integer"
            },
            "example": 306
          },
          {
            "name": "gisId",
            "in": "query",
            "required": true,
            "description": "General submission UUID (prf_general_submissions.gis_id / submission_id).",
            "schema": {
              "type": "string"
            },
            "example": "c2ef7109-4e2f-468d-aa9a-25335f72fdaf"
          }
        ],
        "responses": {
          "200": {
            "description": "Bundled submission-context data. Every slice is shaped as {data, error}.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing or invalid memberId/gisId"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "x-internal": true
      }
    },
    "/v1/prf/general-submissions/{identifier}": {
      "get": {
        "operationId": "get_prf_general_submission",
        "summary": "Get General Submission",
        "description": "Returns a single PRF general submission by its UUID or by a ClickUp task ID. When a task ID is provided, resolves to the parent general submission via the project submission.",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/get-general-submission)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_prf_general_submission",
            "description": "Returns a single PRF general submission by its UUID or by a ClickUp task ID. When a task ID is provided, resolves to the parent general submission via the project submission.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "identifier",
            "in": "path",
            "required": true,
            "description": "General submission UUID or ClickUp task ID",
            "schema": {
              "type": "string"
            },
            "example": "86dz6a6ye"
          },
          {
            "name": "include_project_submissions",
            "in": "query",
            "required": false,
            "description": "When true, includes full joined row data for associated project submissions; when false (default) only their IDs are returned.",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "example": false
          }
        ],
        "responses": {
          "200": {
            "description": "General submission details including raw_data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing identifier"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "General submission not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/prf/general-submissions": {
      "post": {
        "operationId": "create_prf_general_submission_internal",
        "summary": "Create or Update PRF General Submission (Internal)",
        "description": "Progressive upsert endpoint for the PRF4 wizard. Every keystroke / auto-save sends the full canonical envelope; the server upserts the row, syncing raw_data + data + project_form_submission_ids.\n\n- Body MUST contain `submitter.memberid` and `submitter.clickupUserId` (both JSON numbers).\n- `giid` is optional. Provide to upsert; omit on first call to mint.\n- `response` is an array of `{id, value}` entries; only well-formedness is validated — there are no required-field, primary-count, or URL reachability checks on this endpoint.\n- Caller-provided `submissionId` values in projectDetailsSubmissions are honored. Server INSERTs a stub into forms.form_submissions for any submissionId not yet there. Ids removed from the envelope are dropped from project_form_submission_ids (the orphan stub is left for the 12h `abandon-stale-submissions` cron).\n\nReject with `cannot_update_legacy_submission` if the giid points at a legacy wizard row.\n\nWrites prf_general_submissions with submission_version='new_endpoint' and submission_method='direct'.",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/create-general-submission)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "submitter",
                  "response"
                ],
                "properties": {
                  "giid": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Optional. Provide to upsert an existing submission. Omit to mint a new one."
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "in_progress",
                      "completed",
                      "abandoned"
                    ],
                    "description": "Ignored on create — server sets in_progress."
                  },
                  "submitter": {
                    "type": "object",
                    "required": [
                      "memberid",
                      "clickupUserId"
                    ],
                    "properties": {
                      "memberid": {
                        "type": "integer",
                        "description": "Squad account/member id (resolved against public.accounts to derive accid + church_name)."
                      },
                      "clickupUserId": {
                        "type": "integer",
                        "description": "ClickUp user id of the submitter."
                      }
                    },
                    "additionalProperties": false
                  },
                  "response": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "id",
                        "value"
                      ],
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "value": {}
                      }
                    }
                  }
                },
                "additionalProperties": true
              },
              "examples": {
                "minimal": {
                  "summary": "Create — minimum required fields",
                  "value": {
                    "submitter": {
                      "memberid": 301,
                      "clickupUserId": 89139343
                    },
                    "response": [
                      {
                        "id": "selectedProjects",
                        "value": [
                          2,
                          60
                        ]
                      },
                      {
                        "id": "projectTitle",
                        "value": "Easter Sermon Series"
                      },
                      {
                        "id": "description",
                        "value": "Five week sermon series for Easter."
                      },
                      {
                        "id": "ministry",
                        "value": "All Church"
                      },
                      {
                        "id": "vision",
                        "value": "Bright, hopeful, photo-driven."
                      },
                      {
                        "id": "projectDetailsSubmissions",
                        "value": [
                          {
                            "projectType": 2,
                            "submissions": [
                              {
                                "isPrimary": true
                              }
                            ]
                          },
                          {
                            "projectType": 60,
                            "submissions": [
                              {
                                "isPrimary": false
                              }
                            ]
                          }
                        ]
                      }
                    ]
                  }
                },
                "update": {
                  "summary": "Update — providing giid upserts the existing row",
                  "value": {
                    "giid": "3e6031c0-2edd-4129-85c5-9a279cf1231f",
                    "submitter": {
                      "memberid": 301,
                      "clickupUserId": 89139343
                    },
                    "response": [
                      {
                        "id": "projectTitle",
                        "value": "Easter Sermon Series — Revised"
                      },
                      {
                        "id": "description",
                        "value": "Five week sermon series for Easter (updated description)."
                      },
                      {
                        "id": "ministry",
                        "value": "All Church"
                      },
                      {
                        "id": "vision",
                        "value": "Bright, hopeful, photo-driven."
                      },
                      {
                        "id": "selectedProjects",
                        "value": [
                          2,
                          60
                        ]
                      },
                      {
                        "id": "projectDetailsSubmissions",
                        "value": [
                          {
                            "projectType": 2,
                            "submissions": [
                              {
                                "isPrimary": true
                              }
                            ]
                          },
                          {
                            "projectType": 60,
                            "submissions": [
                              {
                                "isPrimary": false
                              }
                            ]
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "General submission updated (giid pointed at an existing row)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PrfGeneralSubmissionCreated"
                }
              }
            }
          },
          "201": {
            "description": "General submission created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PrfGeneralSubmissionCreated"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PrfCreateError"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/forms/combine": {
      "post": {
        "operationId": "combine_forms",
        "summary": "Combine Forms",
        "description": "Combines two or more form schemas from forms.forms into a single form schema, returned in the same format as a stored form. Duplicate questions are removed by entity ID: the first occurrence (earliest in the input order) wins, and later forms only fill in attributes the earlier one left undefined. Pages are NOT merged across forms: each form contributes its own pages (namespaced to keep IDs unique) so a form's pages stay grouped together and in order, with no interleaving. All prefetch mappings are combined. The trailing fields are each collapsed to a single instance and placed together on a dedicated final page, in this order: the 'How would you like to provide your assets?' field, its 'Heads up! Upload Portal' alert banner, the asset/logo file uploader, the 'Anything else we need to know?' field, and the disclaimer/sign-off field. This holds regardless of which form(s) contained them or what IDs they use. Their existing visibility rules are preserved (an individual field may still be hidden by its own rule). Finally, each page is laid out at most 5 fields per page (overflow split into additional pages, preserving order and inheriting the original page's attributes such as label and visibility rules), and repeatable group and panel editor fields are always placed alone on their own page. Accepts exactly one of 'form_ids' or 'project_type_ids' (project types are resolved to forms via forms.forms_project_types). Order determines priority.",
        "tags": [
          "Forms"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/forms/combine-forms)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "combine_forms",
            "description": "Combine multiple form schemas into one, deduping questions by entity ID (earlier wins). Accepts exactly one of form_ids or project_type_ids; input order sets priority.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "form_ids": {
                    "type": "array",
                    "description": "Ordered list of form IDs (forms.forms.id). Order determines merge priority (earlier wins). Provide this OR project_type_ids, not both.",
                    "items": {
                      "type": "string"
                    },
                    "example": [
                      "8ag1AzJD",
                      "ZvccHxzV"
                    ]
                  },
                  "project_type_ids": {
                    "type": "array",
                    "description": "Ordered list of prf project type IDs, resolved to forms via forms.forms_project_types. Order determines merge priority (earlier wins). Provide this OR form_ids, not both.",
                    "items": {
                      "type": "integer"
                    },
                    "example": [
                      65,
                      73
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Combined form schema with the list of source form IDs and any merge warnings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "schema": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "sources": {
                      "type": "object",
                      "description": "Maps each entity ID in the combined schema to the form it originated from (the highest-priority form that contributed it). Kept separate from schema so the schema stays in the renderer's expected shape.",
                      "additionalProperties": {
                        "type": "object",
                        "properties": {
                          "form_id": {
                            "type": "string"
                          },
                          "form_name": {
                            "type": "string",
                            "nullable": true
                          },
                          "kind": {
                            "type": "string",
                            "enum": [
                              "page",
                              "field"
                            ]
                          }
                        }
                      }
                    },
                    "page_groups": {
                      "type": "array",
                      "description": "Ordered grouping of the final pages by the form they belong to. Consecutive pages from the same form are grouped; the synthesized final assets page is reported under a 'Combined' group (form_id '__combined__').",
                      "items": {
                        "type": "object",
                        "properties": {
                          "form_id": {
                            "type": "string"
                          },
                          "form_name": {
                            "type": "string",
                            "nullable": true
                          },
                          "pages": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    },
                    "source_form_ids": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "warnings": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request - must provide exactly one of form_ids or project_type_ids"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "One or more requested forms not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/forms/{form_id}": {
      "get": {
        "operationId": "get_form_metadata",
        "summary": "Get Form Metadata",
        "description": "Returns metadata for a form from forms.forms joined with its associated project types (from forms.forms_project_types and public.prf_selection_types), plus a nested global_config object built from the active rows of forms.global_config (keyed by key).",
        "tags": [
          "Forms"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/forms/get-form-metadata)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_form_metadata",
            "description": "Returns metadata for a form joined with its project type(s) from prf_selection_types, plus a nested global_config object (active forms.global_config rows keyed by key).",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "form_id",
            "in": "path",
            "required": true,
            "description": "Form ID (forms.forms.id)",
            "schema": {
              "type": "string"
            },
            "example": "wpnA50mJ"
          }
        ],
        "responses": {
          "200": {
            "description": "Form metadata with joined project_types array",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing form_id"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Form not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/prf/form-submissions": {
      "post": {
        "operationId": "create_prf_form_submission",
        "summary": "Create Form Submission",
        "description": "Inserts a row into forms.form_submissions. Required: form_id (string), data (object). Optional: submitted_by (string), status (in_progress|completed|abandoned, default in_progress).",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/form-submissions/create)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "create_prf_form_submission",
            "description": "Create a row in forms.form_submissions. Required: form_id, data. Optional: submitted_by, status (in_progress|completed|abandoned).",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "form_id",
                  "data"
                ],
                "properties": {
                  "form_id": {
                    "type": "string"
                  },
                  "data": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "submitted_by": {
                    "type": "string",
                    "nullable": true
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "in_progress",
                      "completed",
                      "abandoned"
                    ],
                    "default": "in_progress"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created"
          },
          "400": {
            "description": "Missing or invalid fields"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/prf/form-submissions/{id}": {
      "get": {
        "operationId": "get_prf_form_submission",
        "summary": "Get Form Submission",
        "description": "Returns a single row from forms.form_submissions by id.",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "anything-goes",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/form-submissions/get)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_prf_form_submission",
            "description": "Get a single forms.form_submissions row by id.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Form submission UUID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Form submission"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      },
      "patch": {
        "operationId": "update_prf_form_submission",
        "summary": "Update Form Submission",
        "description": "Partially updates a forms.form_submissions row. Allowed fields: form_id, data, submitted_by, status.",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "anything-goes",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/form-submissions/update)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "update_prf_form_submission",
            "description": "Partially update a forms.form_submissions row. Allowed: form_id, data, submitted_by, status.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Form submission UUID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "form_id": {
                    "type": "string"
                  },
                  "data": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "submitted_by": {
                    "type": "string",
                    "nullable": true
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "in_progress",
                      "completed",
                      "abandoned"
                    ]
                  }
                },
                "additionalProperties": false
              },
              "examples": {
                "markAbandoned": {
                  "summary": "Mark as abandoned",
                  "value": {
                    "status": "abandoned"
                  }
                },
                "markCompleted": {
                  "summary": "Mark as completed",
                  "value": {
                    "status": "completed"
                  }
                },
                "markInProgress": {
                  "summary": "Mark as in progress",
                  "value": {
                    "status": "in_progress"
                  }
                },
                "setSubmittedBy": {
                  "summary": "Set submitted_by",
                  "value": {
                    "submitted_by": "user@example.com"
                  }
                },
                "updateData": {
                  "summary": "Replace the data payload",
                  "value": {
                    "data": {
                      "field-series-title": "Summer Series"
                    }
                  }
                },
                "fullUpdate": {
                  "summary": "Update several fields at once",
                  "value": {
                    "status": "completed",
                    "submitted_by": "user@example.com",
                    "data": {
                      "field-series-title": "Summer Series"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated"
          },
          "400": {
            "description": "Missing or invalid fields"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      },
      "delete": {
        "operationId": "delete_prf_form_submission",
        "summary": "Delete Form Submission",
        "description": "Deletes a forms.form_submissions row by id. Returns the deleted row.",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "anything-goes",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/form-submissions/delete)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "delete_prf_form_submission",
            "description": "Delete a forms.form_submissions row by id.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": true,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Form submission UUID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/prf/sermon-speakers": {
      "get": {
        "operationId": "list_prf_sermon_speakers",
        "summary": "List Sermon Speakers",
        "description": "Lists rows from prf.sermon_speakers. Filter by any column: id, speaker_name (partial), account, submission_id, created_at/updated_at (exact or *_from/*_to range). Supports limit and offset.",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "anything-goes",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/sermon-speakers/list)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "list_prf_sermon_speakers",
            "description": "List prf.sermon_speakers rows. Filter by id, speaker_name (partial), account, submission_id, created_at/updated_at (exact or range). Supports limit/offset.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": false,
            "description": "Filter by exact id (uuid)",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "speaker_name",
            "in": "query",
            "required": false,
            "description": "Case-insensitive partial match on speaker_name",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "account",
            "in": "query",
            "required": false,
            "description": "Filter by exact account number",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "submission_id",
            "in": "query",
            "required": false,
            "description": "Filter by exact submission_id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "created_at",
            "in": "query",
            "required": false,
            "description": "Filter by exact created_at timestamp",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "created_from",
            "in": "query",
            "required": false,
            "description": "Return rows created at or after this timestamp",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "created_to",
            "in": "query",
            "required": false,
            "description": "Return rows created at or before this timestamp",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "updated_at",
            "in": "query",
            "required": false,
            "description": "Filter by exact updated_at timestamp",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "updated_from",
            "in": "query",
            "required": false,
            "description": "Return rows updated at or after this timestamp",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "updated_to",
            "in": "query",
            "required": false,
            "description": "Return rows updated at or before this timestamp",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows to return (default 100, max 1000)",
            "schema": {
              "type": "integer",
              "default": 100,
              "minimum": 1,
              "maximum": 1000
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Rows to skip for pagination",
            "schema": {
              "type": "integer",
              "default": 0,
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of sermon speakers"
          },
          "400": {
            "description": "Invalid query parameters"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      },
      "post": {
        "operationId": "create_prf_sermon_speaker",
        "summary": "Create Sermon Speaker",
        "description": "Inserts a row into prf.sermon_speakers. Required: speaker_name (string). Optional: account (integer), submission_id (string).",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "anything-goes",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/sermon-speakers/create)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "create_prf_sermon_speaker",
            "description": "Create a prf.sermon_speakers row. Required: speaker_name. Optional: account, submission_id.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "speaker_name"
                ],
                "properties": {
                  "speaker_name": {
                    "type": "string"
                  },
                  "account": {
                    "type": "integer",
                    "nullable": true
                  },
                  "submission_id": {
                    "type": "string",
                    "nullable": true
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created"
          },
          "400": {
            "description": "Missing or invalid fields"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/prf/sermon-speakers/{id}": {
      "get": {
        "operationId": "get_prf_sermon_speaker",
        "summary": "Get Sermon Speaker",
        "description": "Returns a single row from prf.sermon_speakers by id.",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "anything-goes",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/sermon-speakers/get)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_prf_sermon_speaker",
            "description": "Get a single prf.sermon_speakers row by id.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Sermon speaker UUID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sermon speaker"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      },
      "patch": {
        "operationId": "update_prf_sermon_speaker",
        "summary": "Update Sermon Speaker",
        "description": "Partially updates a prf.sermon_speakers row. Allowed fields: speaker_name, account, submission_id.",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "anything-goes",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/sermon-speakers/update)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "update_prf_sermon_speaker",
            "description": "Partially update a prf.sermon_speakers row. Allowed: speaker_name, account, submission_id.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Sermon speaker UUID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "speaker_name": {
                    "type": "string"
                  },
                  "account": {
                    "type": "integer",
                    "nullable": true
                  },
                  "submission_id": {
                    "type": "string",
                    "nullable": true
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated"
          },
          "400": {
            "description": "Missing or invalid fields"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      },
      "delete": {
        "operationId": "delete_prf_sermon_speaker",
        "summary": "Delete Sermon Speaker",
        "description": "Deletes a prf.sermon_speakers row by id. Returns the deleted row.",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "anything-goes",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/sermon-speakers/delete)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "delete_prf_sermon_speaker",
            "description": "Delete a prf.sermon_speakers row by id.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": true,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Sermon speaker UUID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/prf/file-sizes": {
      "get": {
        "operationId": "list_prf_file_sizes",
        "summary": "List File Sizes",
        "description": "Lists rows from prf.file_sizes. Pass `account` to scope to that account; by default (`list_defaults=true`) the response includes both that account's rows AND the global default rows (account IS NULL). Set `list_defaults=false` to return only the account's own rows. When `account` is omitted, all rows are returned. When `account` is provided, hidden rows (those with a matching prf.file_sizes_visibility row for that account) are excluded by default — pass `show_hidden=true` to include them.",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/file-sizes/list)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "list_prf_file_sizes",
            "description": "List PRF file sizes. When account is provided, list_defaults=true (default) returns both account-specific rows and global defaults (account IS NULL); list_defaults=false returns only the account's rows. show_hidden=false (default) excludes rows hidden for that account via the visibility table. Read-only. Set only_return_unique=true to collapse rows sharing identical geometry to one result (default false).",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "account",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Account/member number. By default (with list_defaults=true) the response includes both this account's rows and the global defaults (account IS NULL)."
          },
          {
            "name": "list_defaults",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": true
            },
            "description": "Required when `account` is provided. When true (default), the response includes BOTH the account's own rows and the global default rows (account IS NULL). When false, only the account's own rows are returned. Ignored when `account` is omitted."
          },
          {
            "name": "show_hidden",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "When false (default) and `account` is provided, rows with a matching prf.file_sizes_visibility row for that account are excluded. When true, no visibility filtering is applied."
          },
          {
            "name": "print_digital",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "print",
                "digital"
              ]
            },
            "description": "Filter by print/digital."
          },
          {
            "name": "recommended",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Filter by recommended flag."
          },
          {
            "name": "project_type",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "integer"
              }
            },
            "description": "Filter to file sizes linked to any of the given project type IDs via prf.file_sizes_project_types. Repeat the query param (?project_type=1&project_type=2) or pass a comma-separated list."
          },
          {
            "name": "department",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "design",
                "video",
                "social",
                "web",
                "brand"
              ]
            },
            "description": "Filter to file sizes linked to project types whose department matches. Joins prf.file_sizes_project_types -> public.prf_selection_types(department)."
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Fuzzy search (case-insensitive substring match) across label, description, print_digital, width, height, dpi, and keywords."
          },
          {
            "name": "only_return_unique",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "When true, collapse rows sharing the same geometry (width, height, unit, bleed_size, active, account, print_digital, dpi) to a single result. Defaults to false (return all matching rows)."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 5000
            },
            "description": "Optional. If omitted, all matching rows are returned. Hard cap of 5000 if specified."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 0,
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of file sizes"
          },
          "400": {
            "description": "Invalid query parameters"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      },
      "post": {
        "operationId": "create_prf_file_size",
        "summary": "Create File Size",
        "description": "Creates a row in prf.file_sizes. Required: label, width, height, unit, print_digital.",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/file-sizes/create)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "create_prf_file_size",
            "description": "Create a PRF file size. Required: label, width, height, unit (inches|feet|mm|cm|meter|px), print_digital (print|digital).",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "label",
                  "width",
                  "height",
                  "unit",
                  "print_digital",
                  "project_types"
                ],
                "properties": {
                  "label": {
                    "type": "string"
                  },
                  "project_types": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    },
                    "minItems": 1,
                    "description": "Project type id(s) to map this file size to (creates rows in prf.file_sizes_project_types). Required. A single 'project_type' integer is also accepted as an alias."
                  },
                  "width": {
                    "type": "number"
                  },
                  "height": {
                    "type": "number"
                  },
                  "unit": {
                    "type": "string",
                    "enum": [
                      "inches",
                      "feet",
                      "mm",
                      "cm",
                      "meter",
                      "px"
                    ]
                  },
                  "print_digital": {
                    "type": "string",
                    "enum": [
                      "print",
                      "digital"
                    ]
                  },
                  "bleed_size": {
                    "type": "string",
                    "enum": [
                      "0.125",
                      "0.25"
                    ],
                    "nullable": true
                  },
                  "account": {
                    "type": "integer",
                    "nullable": true
                  },
                  "description": {
                    "type": "string",
                    "nullable": true
                  },
                  "recommended": {
                    "type": "boolean",
                    "default": false
                  },
                  "template_url": {
                    "type": "string",
                    "nullable": true
                  },
                  "dpi": {
                    "type": "integer",
                    "nullable": true
                  },
                  "keywords": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "nullable": true
                  },
                  "plan_exclude": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created"
          },
          "400": {
            "description": "Missing required fields"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/prf/file-sizes/{id}": {
      "get": {
        "operationId": "get_prf_file_size",
        "summary": "Get File Size",
        "description": "Returns a single row from prf.file_sizes by id.",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/file-sizes/get)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_prf_file_size",
            "description": "Get a single PRF file size by id. Read-only.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "File size id."
          },
          {
            "name": "account",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Account/member number. When provided with show_hidden=false, returns 404 if this file size is hidden for that account via prf.file_sizes_visibility."
          },
          {
            "name": "show_hidden",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "When false (default) and `account` is provided, a hidden row returns 404. When true, visibility is ignored."
          }
        ],
        "responses": {
          "200": {
            "description": "File size row"
          },
          "404": {
            "description": "Not found (or hidden for the given account)"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      },
      "patch": {
        "operationId": "update_prf_file_size",
        "summary": "Update File Size",
        "description": "Partially updates a row in prf.file_sizes. Send only the fields you want to change. To toggle hidden state for an account, use PATCH /v1/prf/file-sizes/{id}/visibility instead.",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/file-sizes/update)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "update_prf_file_size",
            "description": "Partially update a PRF file size. Send only fields to change.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "minProperties": 1,
                "properties": {
                  "label": {
                    "type": "string"
                  },
                  "width": {
                    "type": "number"
                  },
                  "height": {
                    "type": "number"
                  },
                  "unit": {
                    "type": "string",
                    "enum": [
                      "inches",
                      "feet",
                      "mm",
                      "cm",
                      "meter",
                      "px"
                    ]
                  },
                  "print_digital": {
                    "type": "string",
                    "enum": [
                      "print",
                      "digital"
                    ]
                  },
                  "bleed_size": {
                    "type": "string",
                    "enum": [
                      "0.125",
                      "0.25"
                    ],
                    "nullable": true
                  },
                  "account": {
                    "type": "integer",
                    "nullable": true
                  },
                  "description": {
                    "type": "string",
                    "nullable": true
                  },
                  "recommended": {
                    "type": "boolean"
                  },
                  "template_url": {
                    "type": "string",
                    "nullable": true
                  },
                  "dpi": {
                    "type": "integer",
                    "nullable": true
                  },
                  "keywords": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "nullable": true
                  },
                  "plan_exclude": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated row"
          },
          "400": {
            "description": "Empty body"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/prf/file-sizes/{id}/visibility": {
      "patch": {
        "operationId": "set_prf_file_size_visibility",
        "summary": "Set File Size Visibility",
        "description": "Toggles per-account visibility for a file size. Send `{\"account\": <int>, \"hidden\": true}` to hide (upserts a row in prf.file_sizes_visibility) or `{\"hidden\": false}` to unhide (deletes the row). Idempotent.",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/file-sizes/visibility)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "set_prf_file_size_visibility",
            "description": "Hide or unhide a PRF file size for a specific account. Body: { account: int, hidden: bool }. Idempotent.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "File size id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "account",
                  "hidden"
                ],
                "properties": {
                  "account": {
                    "type": "integer",
                    "description": "Account/member number."
                  },
                  "hidden": {
                    "type": "boolean",
                    "description": "true → hide (upsert visibility row); false → unhide (delete visibility row)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Visibility updated"
          },
          "400": {
            "description": "Invalid body"
          },
          "404": {
            "description": "File size not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/design/scheduling/availabilities": {
      "get": {
        "operationId": "get_designer_availabilities",
        "summary": "Get Designer Availabilities",
        "description": "Returns designer availability slots with preference + previous-designer scoring for an account. Provide either task_id (auto-resolves account, department, and time estimate from the task) or account (min_time_estimate optional). Each result includes preference_score (-3..4 scale: 4=preferred+recommended, 3=recommended, 2=preferred, 1=none, -1=not preferred, -2=not recommended, -3=both negative; rows below 1 are excluded), is_previous_designer (1 when the designer worked a same-tag/similar-name task on this account in the last 30 days), previous_task_ids, and combined_score = preference_score + is_previous_designer. Results are ordered by combined_score, then date, then available time. By default, results are clipped to the soonest 5 distinct working days that have any viable designer (override with lowest_n_days).",
        "tags": [
          "Design"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/design/scheduling/availabilities)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_designer_availabilities",
            "description": "Returns designer availability slots ranked by preference + previous-designer affinity for an account. Provide task_id or account. Each row includes preference_score (-3..4), is_previous_designer (0/1), previous_task_ids, and combined_score. Default output is clipped to the soonest 5 working days; pass lowest_n_days=0 for the full window.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "task_id",
            "in": "query",
            "required": false,
            "description": "ClickUp task ID. If provided, account, department, and time estimate are auto-resolved from the task. Either task_id or account is required.",
            "schema": {
              "type": "string"
            },
            "example": "86dz7xeed"
          },
          {
            "name": "account",
            "in": "query",
            "required": false,
            "description": "Account/member number. Required if task_id is not provided. Used for preference scoring (preferred/recommended designers for this account).",
            "schema": {
              "type": "integer"
            },
            "example": 2220
          },
          {
            "name": "department",
            "in": "query",
            "required": false,
            "description": "Department to query. 'design' includes Design Squad and Exec Squad. If task_id is provided, defaults to the task's department.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "design",
                  "video"
                ]
              }
            },
            "example": [
              "design"
            ]
          },
          {
            "name": "min_time_estimate",
            "in": "query",
            "required": false,
            "description": "Minimum available minutes a designer must have on a given day to be included. Optional; when omitted and no task_id is provided, no minimum is applied. Use 0 to include all.",
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "example": 30
          },
          {
            "name": "min_date",
            "in": "query",
            "required": false,
            "description": "Earliest date to include (YYYY-MM-DD). Defaults to tomorrow.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-03-25"
          },
          {
            "name": "max_date",
            "in": "query",
            "required": false,
            "description": "Latest date to include (YYYY-MM-DD). Defaults to no limit (up to 100 working days out).",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-04-30"
          },
          {
            "name": "max_results",
            "in": "query",
            "required": false,
            "description": "Maximum number of availability slots to return. Default 50, max 250.",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 250
            }
          },
          {
            "name": "assignee_filter",
            "in": "query",
            "required": false,
            "description": "Look up a specific designer's availability by email. Mutually exclusive with department — provide one or the other.",
            "schema": {
              "type": "string",
              "format": "email"
            },
            "example": "jane.doe@churchmediasquad.com"
          },
          {
            "name": "lowest_n_days",
            "in": "query",
            "required": false,
            "description": "Restrict results to the N smallest distinct days_out values (i.e. the soonest N working days that have at least one viable designer). Default 5 mirrors the auto-scheduling pipeline. Pass 0 to disable the filter and return the full date window.",
            "schema": {
              "type": "integer",
              "default": 5,
              "minimum": 0
            },
            "example": 5
          },
          {
            "name": "include_overloaded",
            "in": "query",
            "required": false,
            "description": "When true, include designers whose total_avail is below the time estimate or whose preference_score is below 1 (over-allocated, not_preferred, or not_recommended). Useful for capacity-dashboard / overcommit-audit queries. Note: this filter is auto-bypassed internally whenever assignee_filter resolves to a single designer, so a targeted lookup of one designer always returns their row regardless of this flag. Default false.",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "example": false
          }
        ],
        "responses": {
          "200": {
            "description": "Designer availability results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer",
                      "description": "Number of availability slots returned"
                    },
                    "department": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Department queried, or null if assignee_filter was used"
                    },
                    "filters": {
                      "type": "object",
                      "description": "Echo of the filters applied",
                      "additionalProperties": true
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing or invalid parameters, or both department and assignee_filter provided"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/design/generate-time-estimate": {
      "post": {
        "operationId": "generate_time_estimate",
        "summary": "Generate Time Estimate (v4.1)",
        "description": "Computes a v4.1 time estimate for a task or for an (account, tag) pair, and writes an audit row to auto_scheduling.as_time_estimate_log. The v4.1 engine combines a tag baseline (last 180 days, IQR-cleaned, p10–p90 trimmed) with three multipliers: account history (Bayesian linear ramp from 0 → full influence at 3 closed tasks for the same tag), form complexity (department-aware bands of text length + content count vs. typical), and project role (lead / sibling, with a no-discount rule when siblings are in a different department). When task_id is used, the engine resolves the task's account and currently-applied estimable tags and produces one estimate per tag. Optionally writes the sum back to the ClickUp task's time_estimate. For read-only Chrome-extension lookups, prefer GET /v1/tasks/{task_id}/estimate-breakdown.",
        "tags": [
          "Design"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/design/generate-time-estimate)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "generate_time_estimate",
            "description": "Computes a v4.1 time estimate. Provide either task_id (auto-resolves account + estimable tags) or account+tag directly. Writes an audit row to as_time_estimate_log per evaluated tag. Optionally pushes the summed estimate to the ClickUp task. Use the read-only /v1/tasks/{task_id}/estimate-breakdown for Chrome-extension panel lookups.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "task_id": {
                    "type": "string",
                    "description": "ClickUp task ID. When provided, the engine resolves the task's account and all currently-applied estimable tags (Design Squad / Video Squad, active, with min/max configured). Mutually exclusive with 'account' and 'tag'."
                  },
                  "account": {
                    "type": "integer",
                    "description": "Account number. Required (with 'tag') when task_id is not provided. Account+tag mode produces an estimate without per-task complexity signals."
                  },
                  "tag": {
                    "type": "string",
                    "description": "Tag name. Required (with 'account') when task_id is not provided."
                  },
                  "update_task": {
                    "type": "boolean",
                    "default": false,
                    "description": "If true, sets the ClickUp task's time_estimate to the SUM of all per-tag estimates returned. Requires task_id."
                  },
                  "ml_beta": {
                    "type": "boolean",
                    "default": false,
                    "description": "If true, proxies to the Windmill ML predictor instead of the v4.1 engine. Requires task_id, cannot be used with account/tag."
                  }
                }
              },
              "examples": {
                "by_task_id": {
                  "summary": "Resolve from task",
                  "value": {
                    "task_id": "86dxgmhh2"
                  }
                },
                "by_task_id_with_update": {
                  "summary": "Resolve and update ClickUp",
                  "value": {
                    "task_id": "86dxgmhh2",
                    "update_task": true
                  }
                },
                "by_account_tag": {
                  "summary": "Direct account + tag",
                  "value": {
                    "account": 1738,
                    "tag": "sermonseries"
                  }
                },
                "ml_beta": {
                  "summary": "ML-based prediction (beta)",
                  "value": {
                    "task_id": "86dxgmhh2",
                    "ml_beta": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "v4.1 time estimate result(s). One per evaluated tag.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "engine_version": {
                      "type": "string",
                      "example": "v4.1"
                    },
                    "count": {
                      "type": "integer",
                      "description": "Number of per-tag rows returned"
                    },
                    "filters": {
                      "type": "object",
                      "description": "Echo of the filters applied",
                      "additionalProperties": true
                    },
                    "task_updated": {
                      "type": "boolean",
                      "description": "Whether the ClickUp task was successfully updated (only present when update_task is true)"
                    },
                    "estimates": {
                      "type": "array",
                      "description": "One entry per evaluated tag, with the four-factor breakdown",
                      "items": {
                        "type": "object",
                        "properties": {
                          "tag_name": {
                            "type": "string"
                          },
                          "final_estimate": {
                            "type": "integer",
                            "description": "Minutes (rounded to 5, floored at 30)"
                          },
                          "baseline_estimate": {
                            "type": "integer",
                            "description": "Minutes — tag baseline before multipliers"
                          },
                          "account_multiplier": {
                            "type": "number"
                          },
                          "complexity_multiplier": {
                            "type": "number"
                          },
                          "role_multiplier": {
                            "type": "number"
                          },
                          "high_variance_flag": {
                            "type": "boolean"
                          },
                          "log_id": {
                            "type": "integer",
                            "description": "as_time_estimate_log row id for this estimate"
                          },
                          "data_source": {
                            "type": "string",
                            "example": "v4.1"
                          }
                        }
                      }
                    },
                    "results": {
                      "type": "array",
                      "description": "Full row payload (includes applied_signals)",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request - invalid parameter combination or missing required params"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Task not found, has no eligible tags, or no resolvable account"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/tasks/{task_id}/estimate-breakdown": {
      "get": {
        "operationId": "get_v4_estimate_breakdown",
        "summary": "Get Estimate Breakdown (v4.1)",
        "description": "Returns the most recent v4 estimate breakdown(s) for a task — one row per tag — read straight from auto_scheduling.as_time_estimate_log. Read-only: no engine recompute, no new audit row. This is the endpoint the MySquad Chrome extension uses to render its 'Time Estimate' panel on each task. To produce a new estimate, use POST /v1/design/generate-time-estimate.",
        "tags": [
          "Design"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/tasks/get-estimate-breakdown)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_v4_estimate_breakdown",
            "description": "Returns the latest v4.1 estimate breakdown(s) for a task — baseline, account_multiplier, complexity_multiplier, role_multiplier, final_estimate, applied_signals — one row per tag. Read-only.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ClickUp task ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Latest breakdown rows for the task. Empty `breakdowns` array when no estimate has been logged yet.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "task_id": {
                      "type": "string"
                    },
                    "engine_version": {
                      "type": "string",
                      "example": "v4.1"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "breakdowns": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "log_id": {
                            "type": "integer"
                          },
                          "computed_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "engine_version": {
                            "type": "string"
                          },
                          "task_id": {
                            "type": "string"
                          },
                          "account_number": {
                            "type": "integer"
                          },
                          "account_name": {
                            "type": "string"
                          },
                          "tag_name": {
                            "type": "string"
                          },
                          "baseline_estimate": {
                            "type": "integer"
                          },
                          "account_multiplier": {
                            "type": "number"
                          },
                          "complexity_multiplier": {
                            "type": "number"
                          },
                          "role_multiplier": {
                            "type": "number"
                          },
                          "final_estimate": {
                            "type": "integer"
                          },
                          "high_variance_flag": {
                            "type": "boolean"
                          },
                          "applied_signals": {
                            "type": "object",
                            "additionalProperties": true,
                            "description": "Full debug payload — task_department, rel_text, rel_content, rel_avg, gis_sibling_count, design_siblings, video_siblings, is_cross_dept, tag_min, tag_max, weight_acct, raw_acct_tag_ratio, etc."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing required task_id path parameter"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/clickup/load-custom-fields": {
      "post": {
        "operationId": "load_clickup_custom_fields",
        "summary": "Load ClickUp Custom Fields",
        "description": "Loads account/Airtable-derived custom fields onto a ClickUp task. If 'fields_to_update' is provided, those fields are forwarded directly to the Windmill custom-field-loader (empties dropped) and no derivation occurs. If omitted, the task's department is resolved (the department custom field, else the first tag with a department), the matching Design/Video/Social field set is built from the account's Airtable data, empty values are dropped, and the result is applied via the loader. Video and Social Media tasks also post the upload-portal comment; Design and Video log to task_update_log.",
        "tags": [
          "ClickUp"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/clickup/load-custom-fields)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "load_clickup_custom_fields",
            "description": "Loads account/Airtable-derived custom fields onto a ClickUp task. If 'fields_to_update' is provided, those fields are forwarded directly to the Windmill custom-field-loader (empties dropped) and no derivation occurs. If omitted, the task's department is resolved (the department custom field, else the first tag with a department), the matching Design/Video/Social field set is built from the account's Airtable data, empty values are dropped, and the result is applied via the loader. Video and Social Media tasks also post the upload-portal comment; Design and Video log to task_update_log.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "task_id"
                ],
                "properties": {
                  "task_id": {
                    "type": "string",
                    "description": "The ClickUp task ID to load custom fields onto"
                  },
                  "fields_to_update": {
                    "type": "array",
                    "description": "Optional. Pre-built custom fields to apply. When provided, the endpoint forwards them straight to the loader and skips all derivation. When omitted, fields are derived from the task's department and the account's Airtable data.",
                    "items": {
                      "type": "object",
                      "required": [
                        "fieldId"
                      ],
                      "properties": {
                        "fieldId": {
                          "type": "string",
                          "description": "ClickUp custom field ID"
                        },
                        "value": {
                          "description": "Value to set (string, number, boolean, array, or null)"
                        },
                        "name": {
                          "type": "string",
                          "description": "Optional human-readable field name, used for logging"
                        }
                      }
                    }
                  }
                }
              },
              "examples": {
                "derive": {
                  "summary": "Derive fields from the task (omit fields_to_update)",
                  "value": {
                    "task_id": "86e1m845k"
                  }
                },
                "forward": {
                  "summary": "Forward explicit fields",
                  "value": {
                    "task_id": "86e1m845k",
                    "fields_to_update": [
                      {
                        "name": "Squad Notes",
                        "fieldId": "ced77f8c-fa7a-429b-96a2-c57892fdfb42",
                        "value": "Prefers minimal style"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Fields applied (or comment-only for Social)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "mode": {
                      "type": "string",
                      "enum": [
                        "derive",
                        "forward"
                      ]
                    },
                    "department": {
                      "type": "string",
                      "nullable": true
                    },
                    "sent": {
                      "type": "integer",
                      "description": "Number of non-empty fields sent to the loader"
                    },
                    "comment": {
                      "type": "boolean",
                      "description": "Whether the upload-portal comment was posted"
                    },
                    "result": {
                      "type": "object",
                      "description": "Raw response from the custom-field-loader"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing 'task_id'"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "ClickUp task not found (recorded in task_deletions)"
          },
          "502": {
            "description": "Upstream ClickUp, Windmill, or Supabase error"
          }
        }
      }
    },
    "/v1/clickup/send-comment": {
      "post": {
        "operationId": "send_clickup_comment",
        "summary": "Send ClickUp Comment",
        "description": "Sends a formatted comment to a ClickUp task. Supports markdown with bold, italic, links (as inline or buttons), ordered/unordered lists, @user mentions via [ClickUpUserID], and @group mentions via [ClickUpGroupID]. Use either a saved template from the clickup_comment_templates table or pass markdown directly. Optionally checks for duplicate comments before sending. Logs every comment to task_update_log.",
        "tags": [
          "ClickUp"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/clickup/send-comment)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "send_clickup_comment",
            "description": "Sends a formatted comment to a ClickUp task. Supports markdown with bold, italic, links (as inline or buttons), ordered/unordered lists, @user mentions via [ClickUpUserID], and @group mentions via [ClickUpGroupID]. Use either a saved template from the clickup_comment_templates table or pass markdown directly. Optionally checks for duplicate comments before sending. Logs every comment to task_update_log.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "task_id"
                ],
                "properties": {
                  "task_id": {
                    "type": "string",
                    "description": "The ClickUp task ID to comment on"
                  },
                  "comment_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "UUID of a saved comment template from clickup_comment_templates table. Mutually exclusive with 'markdown'."
                  },
                  "markdown": {
                    "type": "string",
                    "description": "Inline markdown text for the comment. Supports **bold**, *italic*, [links](url), ordered lists (1.), bullet lists (-), [ClickUpUserID] for @mentions, and [ClickUpGroupID] for group mentions. Mutually exclusive with 'comment_id'."
                  },
                  "user_to_tag": {
                    "type": "string",
                    "description": "Email address or numeric ClickUp user ID. Resolves to the user's ClickUp ID and replaces [ClickUpUserID] placeholders with an @mention. If not provided, [ClickUpUserID] placeholders are removed."
                  },
                  "links_as_buttons": {
                    "type": "boolean",
                    "default": false,
                    "description": "If true, markdown links are rendered as ClickUp button elements instead of inline links."
                  },
                  "replace_url": {
                    "type": "string",
                    "description": "If provided, replaces all URLs in markdown links and plain ClickUp task URLs with this URL."
                  },
                  "check_duplicates": {
                    "type": "boolean",
                    "default": false,
                    "description": "If true, checks for duplicate comments before sending. Requires 'search_text' and 'days_back' > 0."
                  },
                  "search_text": {
                    "type": "string",
                    "description": "Text to search for in existing comments when check_duplicates is true. Case-insensitive match."
                  },
                  "days_back": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Number of days to look back when checking for duplicate comments."
                  }
                }
              },
              "examples": {
                "from_template": {
                  "summary": "Send from saved template",
                  "value": {
                    "task_id": "86dz7xeed",
                    "comment_id": "b78d1c1b-26f1-4b88-9440-4fe46cf9eeea",
                    "user_to_tag": "jacob@churchmediasquad.com",
                    "links_as_buttons": true
                  }
                },
                "inline_markdown": {
                  "summary": "Send inline markdown",
                  "value": {
                    "task_id": "86dz7xeed",
                    "markdown": "👋 Hey [ClickUpUserID],\n\n\nYour project is **ready for review**!\n\n\n- Design files are uploaded\n- All revisions have been applied\n\n\n[View Files](https://dropbox.com/example)",
                    "user_to_tag": "jacob@churchmediasquad.com",
                    "links_as_buttons": true
                  }
                },
                "with_duplicate_check": {
                  "summary": "With duplicate check",
                  "value": {
                    "task_id": "86dz7xeed",
                    "markdown": "⏰ Reminder: this project is due soon!",
                    "check_duplicates": true,
                    "search_text": "Reminder: this project is due soon",
                    "days_back": 3
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Comment sent (or duplicate found)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "task_id": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing or conflicting parameters"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Comment template not found"
          },
          "502": {
            "description": "ClickUp API error"
          }
        }
      }
    },
    "/v1/clickup/update-task-description": {
      "post": {
        "operationId": "update_clickup_task_description",
        "summary": "Update ClickUp Task Description",
        "description": "Converts markdown into ClickUp's rich-text description format (Quill ops JSON). If task_id is provided, updates that task's description via the ClickUp API; otherwise just returns the converted content. Supports headers, ordered/unordered/checklist lists, bold, italic, inline code, code blocks, dividers, banners (==text==, multiline), grey ([text]) and grey-bold ([**text**]) coloring, links (rendered as inline link, button, or embed), and >>-prefixed indentation.",
        "tags": [
          "ClickUp"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/clickup/update-task-description)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "update_clickup_task_description",
            "description": "Converts markdown into ClickUp's rich-text description format and optionally updates a ClickUp task's description. If task_id is omitted, returns the converted content only. Supports headers, lists, checklists, bold, italic, inline code, code blocks, dividers, banners, grey/grey-bold coloring, links (link/button/embed), and indentation.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "content"
                ],
                "properties": {
                  "content": {
                    "type": "string",
                    "description": "Markdown content to convert. Supports # headers, -/1. lists, - [ ]/- [x] checklists, **bold**, *italic*, `code`, ```code blocks```, ***divider, ==banner==, [grey], [**grey bold**], [link](url), and >>-prefixed indentation."
                  },
                  "task_id": {
                    "type": "string",
                    "description": "Optional ClickUp task ID. If provided, the task's description will be updated with the converted content. If omitted, only the converted content is returned."
                  },
                  "links_setting": {
                    "type": "string",
                    "enum": [
                      "link",
                      "button",
                      "embed"
                    ],
                    "default": "button",
                    "description": "How markdown links should be rendered: 'link' for inline hyperlink, 'button' for ClickUp button block, 'embed' for an embedded frame."
                  },
                  "color_setting": {
                    "type": "string",
                    "default": "cu-purple",
                    "description": "Button color used when links_setting is 'button' (e.g. cu-purple, cu-blue, cu-red)."
                  }
                }
              },
              "examples": {
                "convert_only": {
                  "summary": "Convert markdown without updating a task",
                  "value": {
                    "content": "# Project Update\n\n- All files **uploaded**\n- [View Files](https://dropbox.com/example)",
                    "links_setting": "button"
                  }
                },
                "update_task": {
                  "summary": "Convert and update a task description",
                  "value": {
                    "content": "# Project Update\n\n- All files **uploaded**\n- [View Files](https://dropbox.com/example)",
                    "task_id": "86dz7xeed",
                    "links_setting": "button",
                    "color_setting": "cu-purple"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result of the task update (or converted content if task_id was omitted).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "task_id": {
                      "type": "string",
                      "description": "Echo of the task that was updated. Omitted when no task_id was provided."
                    },
                    "message": {
                      "type": "string"
                    },
                    "content": {
                      "type": "string",
                      "description": "Stringified Quill ops JSON. Only included when task_id was omitted."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing or invalid parameters"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "ClickUp API error"
          }
        }
      }
    },
    "/v1/slack/send-message": {
      "post": {
        "operationId": "send_slack_message",
        "summary": "Send Slack Message",
        "description": "Sends a message to a Slack channel as a bot. Supports full markdown formatting, custom bot name and icon, and native Slack URL buttons. The channel is specified by name (e.g. 'general' or '#general'). Use [Markdown Guide](https://www.markdownguide.org/cheat-sheet/) for formatting reference.",
        "tags": [
          "Slack"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/slack/send-message)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "send_slack_message",
            "description": "Sends a message to a Slack channel as a bot. Supports full markdown formatting, custom bot name and icon, and native Slack URL buttons. The channel is specified by name (e.g. 'general' or '#general'). Use [Markdown Guide](https://www.markdownguide.org/cheat-sheet/) for formatting reference.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "channel"
                ],
                "properties": {
                  "channel": {
                    "type": "string",
                    "description": "Slack channel name (e.g. 'general' or '#general')"
                  },
                  "message": {
                    "type": "string",
                    "description": "Simple message text in markdown. Use this for single-section messages. For multi-section messages with different formatting, use 'blocks' instead."
                  },
                  "blocks": {
                    "type": "array",
                    "description": "Array of markdown blocks for rich multi-section messages. Each block is rendered as a separate section. Supports **bold**, _italic_, ~~strikethrough~~, `inline code`, ```code blocks```, [links](url), > blockquotes, # headings (h1-h6), --- horizontal rules, and | tables |. Use this OR 'message', not both.",
                    "items": {
                      "type": "object",
                      "required": [
                        "type",
                        "text"
                      ],
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "markdown"
                          ],
                          "description": "Block type — always 'markdown'"
                        },
                        "text": {
                          "type": "string",
                          "description": "Markdown-formatted text content for this block"
                        }
                      }
                    }
                  },
                  "bot_name": {
                    "type": "string",
                    "description": "Custom display name for the bot (e.g. 'Deploy Bot')"
                  },
                  "bot_icon": {
                    "type": "string",
                    "description": "Bot icon — either a Slack emoji code (e.g. ':rocket:') or an image URL"
                  },
                  "buttons": {
                    "type": "array",
                    "description": "URL link buttons rendered as native Slack buttons",
                    "items": {
                      "type": "object",
                      "required": [
                        "text",
                        "url"
                      ],
                      "properties": {
                        "text": {
                          "type": "string",
                          "description": "Button label"
                        },
                        "url": {
                          "type": "string",
                          "description": "URL the button links to"
                        },
                        "style": {
                          "type": "string",
                          "enum": [
                            "primary",
                            "danger"
                          ],
                          "description": "Button color style. 'primary' = green, 'danger' = red. Omit for default gray."
                        }
                      }
                    }
                  }
                }
              },
              "examples": {
                "simple": {
                  "summary": "Simple message",
                  "value": {
                    "channel": "general",
                    "message": "Hello from the Squad API! :wave:"
                  }
                },
                "rich_markdown": {
                  "summary": "Rich markdown with blocks",
                  "value": {
                    "channel": "general",
                    "bot_name": "Squad Bot",
                    "bot_icon": ":sparkles:",
                    "blocks": [
                      {
                        "type": "markdown",
                        "text": "Text can be **bold**, _italic_, ~~strikethrough~~, or `inline code`.\n\nCombine them: **_bold italic_** and [links](https://api.slack.com).\n\n> Blockquotes work too, with **formatting** inside."
                      },
                      {
                        "type": "markdown",
                        "text": "# Heading 1\n\nIntroduction paragraph.\n\n---\n\n## Heading 2\n\nMore details here.\n\n---\n\n### Heading 3\n\nEven more details.\n\n#### Heading 4\n\n##### Heading 5\n\n###### Heading 6"
                      },
                      {
                        "type": "markdown",
                        "text": "Here is a JavaScript function:\n\n```javascript\nfunction greet(name) {\n  return \"Hello, \" + name + \"!\";\n}\n\nconsole.log(greet(\"world\"));\n```"
                      },
                      {
                        "type": "markdown",
                        "text": "## Sprint Status\n\n| Task | Owner | Status |\n|---|---|---|\n| **Authentication** | Alice | ~~Done~~ _Shipped_ |\n| `Dashboard` | Bob | **In Progress** |\n| [API Docs](https://api.slack.com) | Carol | _Not Started_ |"
                      }
                    ],
                    "buttons": [
                      {
                        "text": "View Dashboard",
                        "url": "https://dashboard.example.com",
                        "style": "primary"
                      },
                      {
                        "text": "View Logs",
                        "url": "https://logs.example.com"
                      },
                      {
                        "text": "Rollback",
                        "url": "https://rollback.example.com",
                        "style": "danger"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Message sent successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "channel": {
                      "type": "string",
                      "description": "Channel name"
                    },
                    "channel_id": {
                      "type": "string",
                      "description": "Resolved Slack channel ID"
                    },
                    "timestamp": {
                      "type": "string",
                      "description": "Slack message timestamp (ts)"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing channel or message"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Channel not found"
          },
          "502": {
            "description": "Slack API error"
          }
        }
      }
    },
    "/v1/employees/verify-status": {
      "get": {
        "operationId": "verify_employee_status",
        "summary": "Verify Employee Status",
        "description": "Verifies whether an email belongs to an active Squad employee by checking against Rippling worker records. Returns employee details if verified.",
        "tags": [
          "Employees"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/employees/verify-employee-status)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "verify_employee_status",
            "description": "Verifies whether an email belongs to an active Squad employee by checking against Rippling worker records. Returns employee details if verified.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "email",
            "in": "query",
            "required": true,
            "description": "The work email address to verify",
            "schema": {
              "type": "string",
              "format": "email"
            },
            "example": "jane.doe@churchmediasquad.com"
          }
        ],
        "responses": {
          "200": {
            "description": "Verification result",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "description": "Employee verified",
                      "properties": {
                        "verified": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        },
                        "given_name": {
                          "type": "string"
                        },
                        "family_name": {
                          "type": "string"
                        },
                        "display_name": {
                          "type": "string"
                        },
                        "title": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "ACTIVE"
                          ]
                        }
                      }
                    },
                    {
                      "type": "object",
                      "description": "Employee not found or not active",
                      "properties": {
                        "verified": {
                          "type": "boolean",
                          "enum": [
                            false
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing or invalid email"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/airtable/search": {
      "get": {
        "operationId": "search_airtable_records",
        "summary": "Search Airtable Records",
        "description": "Search for records in any Airtable base/table using a filterByFormula expression. Returns all matching records with automatic pagination, Redis caching (60s TTL), rate limiting (4 req/s per base), and retry with exponential backoff.",
        "tags": [
          "Airtable"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/airtable/search-records)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "search_airtable_records",
            "description": "Search for records in any Airtable base/table using a filterByFormula expression. Returns all matching records with automatic pagination.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "base_id",
            "in": "query",
            "required": true,
            "description": "Airtable base ID",
            "schema": {
              "type": "string"
            },
            "example": "appXCvcntPYSBZey2"
          },
          {
            "name": "table_id",
            "in": "query",
            "required": true,
            "description": "Airtable table ID",
            "schema": {
              "type": "string"
            },
            "example": "tblSpD6Sw8VVnr0Wh"
          },
          {
            "name": "formula",
            "in": "query",
            "required": true,
            "description": "Airtable filterByFormula expression",
            "schema": {
              "type": "string"
            },
            "example": "{Clickup User ID} = \"1381408\""
          },
          {
            "name": "fields",
            "in": "query",
            "required": false,
            "description": "Field name(s) to return. Repeat for multiple fields (e.g., fields=Name&fields=Email). Omit to return all fields.",
            "schema": {
              "type": "string"
            },
            "example": "Name"
          }
        ],
        "responses": {
          "200": {
            "description": "Matching Airtable records",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "description": "Record with id, createdTime, and all Airtable field values at the top level",
                    "properties": {
                      "id": {
                        "type": "string",
                        "description": "Airtable record ID"
                      },
                      "createdTime": {
                        "type": "string",
                        "format": "date-time"
                      }
                    },
                    "additionalProperties": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "No records found or missing required parameters"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Airtable API failed after retries"
          },
          "503": {
            "description": "Rate limit queue timed out (30s)"
          }
        }
      }
    },
    "/favicon.ico": {
      "get": {
        "operationId": "favicon",
        "summary": "Favicon",
        "tags": [
          "Other"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/auth/favicon)"
          },
          "policies": {
            "inbound": [],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "responses": {
          "200": {
            "description": "Favicon"
          }
        }
      }
    },
    "/.well-known/oauth-protected-resource/mcp": {
      "x-zuplo-path": {
        "pathMode": "url-pattern"
      },
      "get": {
        "operationId": "well_known_oauth_protected_resource_mcp",
        "summary": "OAuth Protected Resource Metadata (MCP)",
        "description": "RFC 9728 metadata for the MCP resource server. Advertises Auth0 as the authorization server and https://api.thesqd.com as the audience Claude should request (matching Auth0's default_audience).",
        "tags": [
          "Other"
        ],
        "x-zuplo-route": {
          "corsPolicy": "anything-goes",
          "handler": {
            "export": "default",
            "module": "$import(./modules/well-known/oauth-protected-resource-mcp)"
          }
        },
        "responses": {
          "200": {
            "description": "Protected resource metadata"
          }
        }
      }
    },
    "/mcp": {
      "x-zuplo-path": {
        "pathMode": "open-api"
      },
      "post": {
        "operationId": "mcp_server",
        "summary": "MCP Server",
        "description": "Model Context Protocol server endpoint. Exposes all Squad API routes as MCP tools for AI agents and assistants.",
        "tags": [
          "Other"
        ],
        "x-zuplo-route": {
          "corsPolicy": "anything-goes",
          "handler": {
            "export": "mcpServerHandler",
            "module": "$import(@zuplo/runtime)",
            "options": {
              "name": "squad-api-mcp",
              "version": "1.0.0",
              "operations": [
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_tasks"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_queued_tasks"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_task_details"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_task_assignee_history"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_task_status_history"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_task_due_date_history"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_task_tag_history"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_task_time_estimate_history"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_task_time_tracking_history"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_accounts"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_account_details"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_users"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_user"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_prf_project_submission"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_prf_general_submission"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "list_prf_project_submissions"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "create_prf_general_submission_client"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "create_prf_form_submission"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_prf_form_submission"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "update_prf_form_submission"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "delete_prf_form_submission"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_designer_availabilities"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "generate_time_estimate"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "send_clickup_comment"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "update_clickup_task_description"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "send_slack_message"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "verify_employee_status"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "upload_dropbox_files"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "generic_file_upload"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_presigned_s3_upload_url"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "update_clickup_users"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "list_squad_sdk_components"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_squad_sdk_component"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_squad_wrapped"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "list_clickup_statuses"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "list_remix_products"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_remix_product"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "list_remix_taxonomies"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_production_queue"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "claim_production_task"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "extend_production_checkout"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "release_production_checkout"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_designer_completed_tasks"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_form_metadata"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "list_prf_sermon_speakers"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "create_prf_sermon_speaker"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "get_prf_sermon_speaker"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "update_prf_sermon_speaker"
                },
                {
                  "file": "./config/routes.oas.json",
                  "id": "delete_prf_sermon_speaker"
                }
              ]
            }
          },
          "policies": {
            "inbound": [
              "mcp-auth0-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "responses": {
          "200": {
            "description": "MCP JSON-RPC response"
          }
        }
      }
    },
    "/v1/dropbox/upload-files": {
      "post": {
        "operationId": "upload_dropbox_files",
        "summary": "Upload Files to Dropbox",
        "description": "Uploads files to Dropbox. Accepts JSON with file URLs (batch, max 100) or multipart/form-data with a raw file (single). Files that already exist at the destination are skipped.",
        "tags": [
          "Files"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/dropbox/upload-files)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "upload_dropbox_files",
            "description": "Upload files to Dropbox. Send JSON with file URLs (batch) or multipart/form-data with a raw file (single).",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "files": {
                    "type": "array",
                    "description": "Array of files to upload from URLs (max 100). Required if 'file' is not provided.",
                    "items": {
                      "type": "object",
                      "required": [
                        "file_url",
                        "destination_path"
                      ],
                      "properties": {
                        "file_url": {
                          "type": "string",
                          "description": "URL of the file to download and upload to Dropbox"
                        },
                        "destination_path": {
                          "type": "string",
                          "description": "Destination path in Dropbox (e.g. /Client Assets/123 - Church Name/project-files/design.png)"
                        }
                      }
                    }
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Raw file to upload directly to Dropbox (single file). Required if 'files' is not provided. Use the Multipart body type in the playground."
                  },
                  "destination_path": {
                    "type": "string",
                    "description": "Destination path in Dropbox. Required when uploading a raw file."
                  },
                  "wait_for_result": {
                    "type": "boolean",
                    "description": "If true (default), waits for the upload to complete and returns the result. If false, queues the job and returns immediately with a job ID.",
                    "default": true
                  }
                }
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file",
                  "destination_path"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The raw file to upload to Dropbox"
                  },
                  "destination_path": {
                    "type": "string",
                    "description": "Destination path in Dropbox (e.g. /Client Assets/123 - Church Name/project-files/design.png)"
                  },
                  "wait_for_result": {
                    "type": "string",
                    "enum": [
                      "true",
                      "false"
                    ],
                    "description": "If 'true' (default), waits for the upload to complete. If 'false', queues the job and returns a job ID.",
                    "default": "true"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Upload results with per-file status (when wait_for_result is true)"
          },
          "202": {
            "description": "Job queued (when wait_for_result is false). Returns { queued: true, job_id: string }"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/s3/get-presigned-upload-url": {
      "post": {
        "operationId": "get_presigned_s3_upload_url",
        "summary": "Get Presigned S3 Upload URL",
        "description": "Returns a presigned PUT URL the client can use to upload a file directly to Wasabi S3, bypassing the gateway's ~10 MB request body limit. Use this for files larger than ~5 MB.\n\n## How to use the presigned URL\n\n1. Call this endpoint with the destination `path` (and optionally `content_type`, `expires_in`, `metadata`).\n2. The response contains `upload_url` (the presigned PUT URL), `required_headers` (every header that was baked into the signature), and `public_url` (where the file will live after upload).\n3. **PUT the raw file body** to `upload_url`. You MUST send every header listed in `required_headers` exactly as returned — the `Content-Type` and any `x-amz-meta-*` keys are part of the signature. Sending a different value (or omitting one) produces a `SignatureDoesNotMatch` error.\n4. Do NOT add extra signed headers (e.g., `x-amz-acl`). Anything you didn't sign at presign time will also break the signature.\n5. On success, S3 returns `200 OK` with an empty body and an `ETag` header. The file is now publicly available at `public_url`.\n\n## Example: simple upload (curl)\n\n```bash\n# 1) Get the presigned URL\nRESP=$(curl -s -X POST https://api.thesqd.com/v1/s3/get-presigned-upload-url \\\n  -H \"Authorization: Bearer $SQUAD_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"path\": \"uploads/2026/large-video.mp4\",\n    \"content_type\": \"video/mp4\",\n    \"expires_in\": 900\n  }')\n\nUPLOAD_URL=$(echo \"$RESP\" | jq -r '.upload_url')\nPUBLIC_URL=$(echo \"$RESP\" | jq -r '.public_url')\n\n# 2) PUT the file directly to S3 — note the exact same Content-Type\ncurl -X PUT \"$UPLOAD_URL\" \\\n  -H \"Content-Type: video/mp4\" \\\n  --data-binary @/path/to/large-video.mp4\n\necho \"File available at: $PUBLIC_URL\"\n```\n\n## Example: upload with metadata (curl)\n\nWhen you sign metadata, the PUT must include matching `x-amz-meta-*` headers.\n\n```bash\n# 1) Presign with metadata\nRESP=$(curl -s -X POST https://api.thesqd.com/v1/s3/get-presigned-upload-url \\\n  -H \"Authorization: Bearer $SQUAD_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"path\": \"brand-assets/hero.png\",\n    \"content_type\": \"image/png\",\n    \"expires_in\": 1800,\n    \"metadata\": {\n      \"uploaded_by\": \"jacob\",\n      \"project\": \"rebrand-2026\"\n    }\n  }')\n\nUPLOAD_URL=$(echo \"$RESP\" | jq -r '.upload_url')\n\n# 2) PUT with EVERY required header\ncurl -X PUT \"$UPLOAD_URL\" \\\n  -H \"Content-Type: image/png\" \\\n  -H \"x-amz-meta-uploaded_by: jacob\" \\\n  -H \"x-amz-meta-project: rebrand-2026\" \\\n  --data-binary @/path/to/hero.png\n```\n\nTip: `required_headers` in the response contains every header you must echo back — it's the source of truth. Iterate over it instead of hardcoding header names.\n\n## Example: browser upload (fetch)\n\n```javascript\n// 1) Ask the API for a presigned URL\nconst presignRes = await fetch('/v1/s3/get-presigned-upload-url', {\n  method: 'POST',\n  headers: {\n    'Authorization': `Bearer ${apiKey}`,\n    'Content-Type': 'application/json',\n  },\n  body: JSON.stringify({\n    path: `uploads/${Date.now()}-${file.name}`,\n    content_type: file.type || 'application/octet-stream',\n    metadata: { uploaded_by: currentUser.email },\n  }),\n});\nconst { upload_url, required_headers, public_url } = await presignRes.json();\n\n// 2) PUT the file directly to S3 using the headers the server signed\nconst uploadRes = await fetch(upload_url, {\n  method: 'PUT',\n  headers: required_headers, // contains Content-Type + any x-amz-meta-*\n  body: file,                // a File or Blob\n});\n\nif (!uploadRes.ok) throw new Error(`Upload failed: ${uploadRes.status}`);\nconsole.log('Uploaded to', public_url);\n```\n\n## Example: Node.js upload (fetch + fs)\n\n```javascript\nimport fs from 'node:fs';\n\nconst presignRes = await fetch('https://api.thesqd.com/v1/s3/get-presigned-upload-url', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.SQUAD_API_KEY}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({ path: 'uploads/big.zip', content_type: 'application/zip' }),\n});\nconst { upload_url, required_headers, public_url } = await presignRes.json();\n\nconst body = fs.readFileSync('/path/to/big.zip');\nconst uploadRes = await fetch(upload_url, { method: 'PUT', headers: required_headers, body });\nif (!uploadRes.ok) throw new Error(await uploadRes.text());\nconsole.log('Uploaded to', public_url);\n```\n\n## Batch mode — multiple files in one request\n\nPass a `files` array (up to 100 entries) instead of a top-level `path`. Each entry can override `content_type`, `expires_in`, and `metadata`. The response is `{ count, files: [...] }` with one signed result per input, in the same order. Top-level `expires_in` becomes the default for entries that don't set their own.\n\n```bash\ncurl -s -X POST https://api.thesqd.com/v1/s3/get-presigned-upload-url \\\n  -H \"Authorization: Bearer $SQUAD_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"expires_in\": 1800,\n    \"files\": [\n      { \"path\": \"uploads/a.jpg\", \"content_type\": \"image/jpeg\" },\n      { \"path\": \"uploads/b.zip\", \"content_type\": \"application/zip\", \"metadata\": {\"project\": \"x\"} }\n    ]\n  }'\n```\n\n```javascript\n// Browser/Node: presign many, upload in parallel\nconst { files } = await fetch('/v1/s3/get-presigned-upload-url', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    expires_in: 1800,\n    files: filesToUpload.map(f => ({\n      path: `uploads/${Date.now()}-${f.name}`,\n      content_type: f.type || 'application/octet-stream',\n    })),\n  }),\n}).then(r => r.json());\n\nawait Promise.all(\n  files.map((presigned, i) =>\n    fetch(presigned.upload_url, {\n      method: 'PUT',\n      headers: presigned.required_headers,\n      body: filesToUpload[i],\n    }).then(r => { if (!r.ok) throw new Error(`Upload ${i} failed: ${r.status}`); })\n  )\n);\nconsole.log('All uploaded:', files.map(f => f.public_url));\n```\n\n## Common errors\n\n- **`SignatureDoesNotMatch`**: a header you sent doesn't match what was signed. Most often caused by a different `Content-Type`, a missing/extra `x-amz-meta-*`, or letting your HTTP client add a header (e.g., `Transfer-Encoding`) that wasn't signed. Echo `required_headers` verbatim and don't add extras.\n- **`AccessDenied` / expired URL**: the URL is older than `expires_in` seconds. Request a new one.\n- **413 from this endpoint**: this only returns JSON, so 413 here means your *request* to the gateway was too large — not a presigned-URL issue.",
        "tags": [
          "Files"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/s3/get-presigned-upload-url)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_presigned_s3_upload_url",
            "description": "Returns a presigned PUT URL so the client can upload a file directly to Wasabi S3, bypassing the gateway's ~10 MB body limit. Use for files >5 MB. Client PUTs the raw file body to upload_url with the listed required_headers; the file is then reachable at public_url.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Either provide top-level 'path' (single-file mode) OR a 'files' array (batch mode, up to 100 entries). When 'files' is present, top-level 'path'/'content_type'/'metadata' are ignored, and top-level 'expires_in' becomes the default for any file entry that doesn't override it.",
                "properties": {
                  "path": {
                    "type": "string",
                    "description": "Single-file mode: destination path in the S3 bucket (e.g. uploads/2026/my-file.png). No leading slash. Ignored when 'files' is provided."
                  },
                  "content_type": {
                    "type": "string",
                    "description": "Single-file mode: Content-Type the client will send when PUTting. Defaults to application/octet-stream. Must match the Content-Type header used in the actual upload. Ignored when 'files' is provided."
                  },
                  "expires_in": {
                    "type": "integer",
                    "minimum": 60,
                    "maximum": 604800,
                    "default": 900,
                    "description": "Lifetime of the presigned URL in seconds (60 to 604800; default 900 = 15 minutes). In batch mode, used as the default for any file entry that doesn't set its own."
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "description": "Single-file mode: custom metadata to bind to the presigned URL. If provided, the client MUST send the matching x-amz-meta-* headers when uploading. Ignored when 'files' is provided."
                  },
                  "files": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 100,
                    "description": "Batch mode: array of file entries to presign in a single request. Each entry has its own 'path' and may override 'content_type', 'expires_in', and 'metadata'. Returns a parallel array of results.",
                    "items": {
                      "type": "object",
                      "required": [
                        "path"
                      ],
                      "properties": {
                        "path": {
                          "type": "string",
                          "description": "Destination path in the S3 bucket. No leading slash."
                        },
                        "content_type": {
                          "type": "string",
                          "description": "Content-Type for this file. Defaults to application/octet-stream."
                        },
                        "expires_in": {
                          "type": "integer",
                          "minimum": 60,
                          "maximum": 604800,
                          "description": "Override expiry for this file. Falls back to the top-level 'expires_in' (or 900s)."
                        },
                        "metadata": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "string"
                          },
                          "description": "Custom metadata for this file. Client must echo matching x-amz-meta-* headers when uploading."
                        }
                      }
                    }
                  }
                }
              },
              "examples": {
                "basic": {
                  "summary": "Basic presigned URL (single file)",
                  "value": {
                    "path": "uploads/2026/large-file.zip",
                    "content_type": "application/zip"
                  }
                },
                "with_metadata": {
                  "summary": "Presigned URL with metadata (single file)",
                  "value": {
                    "path": "brand-assets/hero.png",
                    "content_type": "image/png",
                    "expires_in": 1800,
                    "metadata": {
                      "uploaded_by": "jacob",
                      "project": "rebrand-2026"
                    }
                  }
                },
                "batch": {
                  "summary": "Batch — multiple files in one request",
                  "value": {
                    "expires_in": 1800,
                    "files": [
                      {
                        "path": "uploads/2026/photo-1.jpg",
                        "content_type": "image/jpeg"
                      },
                      {
                        "path": "uploads/2026/photo-2.jpg",
                        "content_type": "image/jpeg",
                        "metadata": {
                          "project": "rebrand-2026"
                        }
                      },
                      {
                        "path": "uploads/2026/raw.zip",
                        "content_type": "application/zip",
                        "expires_in": 3600
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Presigned upload URL details. Single-file mode returns one object; batch mode returns { count, files: [ ... ] } where each entry has the same shape.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "title": "Single",
                      "properties": {
                        "upload_url": {
                          "type": "string"
                        },
                        "method": {
                          "type": "string"
                        },
                        "required_headers": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "string"
                          }
                        },
                        "public_url": {
                          "type": "string"
                        },
                        "path": {
                          "type": "string"
                        },
                        "bucket": {
                          "type": "string"
                        },
                        "content_type": {
                          "type": "string"
                        },
                        "expires_in": {
                          "type": "integer"
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "title": "Batch",
                      "properties": {
                        "count": {
                          "type": "integer"
                        },
                        "files": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "upload_url": {
                                "type": "string"
                              },
                              "method": {
                                "type": "string"
                              },
                              "required_headers": {
                                "type": "object",
                                "additionalProperties": {
                                  "type": "string"
                                }
                              },
                              "public_url": {
                                "type": "string"
                              },
                              "path": {
                                "type": "string"
                              },
                              "bucket": {
                                "type": "string"
                              },
                              "content_type": {
                                "type": "string"
                              },
                              "expires_in": {
                                "type": "integer"
                              },
                              "expires_at": {
                                "type": "string",
                                "format": "date-time"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing or invalid parameters"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/v1/s3/upload-file": {
      "post": {
        "operationId": "generic_file_upload",
        "summary": "Generic File Upload",
        "description": "Uploads a file to Wasabi S3 storage. Accepts JSON with a file URL or multipart/form-data with a raw file. Returns the public URL, file metadata, and ETag. Best for small files (~5 MB or less) — the gateway has a ~10 MB request body limit, so for larger files use POST /v1/s3/get-presigned-upload-url and PUT directly to S3.",
        "tags": [
          "Files"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/s3/upload-file)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "generic_file_upload",
            "description": "Upload a file to Wasabi S3. Send JSON with a file URL or multipart/form-data with a raw file. Returns public URL and file metadata. Best for small files (~5 MB or less); for larger files use get_presigned_s3_upload_url and PUT directly to S3.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "path"
                ],
                "properties": {
                  "file_url": {
                    "type": "string",
                    "description": "URL of the file to download and upload to S3. Required if 'file' is not provided."
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Raw file to upload directly to S3. Required if 'file_url' is not provided. Use the Multipart body type in the playground."
                  },
                  "path": {
                    "type": "string",
                    "description": "Destination path in the S3 bucket (e.g. uploads/2026/my-file.png). No leading slash."
                  },
                  "metadata": {
                    "type": "object",
                    "description": "Custom metadata key-value pairs to attach to the file",
                    "additionalProperties": {
                      "type": "string"
                    }
                  }
                }
              },
              "examples": {
                "from_url": {
                  "summary": "Upload from URL",
                  "value": {
                    "file_url": "https://example.com/photo.jpg",
                    "path": "uploads/2026/03/photo.jpg"
                  }
                },
                "with_metadata": {
                  "summary": "Upload from URL with metadata",
                  "value": {
                    "file_url": "https://example.com/logo.png",
                    "path": "brand-assets/logo.png",
                    "metadata": {
                      "project": "rebrand-2026",
                      "uploaded_by": "jacob"
                    }
                  }
                }
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file",
                  "path"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The raw file to upload to S3"
                  },
                  "path": {
                    "type": "string",
                    "description": "Destination path in the S3 bucket (e.g. uploads/2026/my-file.png). No leading slash."
                  },
                  "metadata": {
                    "type": "string",
                    "description": "JSON string of custom metadata key-value pairs (e.g. {\"project\": \"rebrand-2026\"})"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Upload successful with file URL and metadata"
          },
          "400": {
            "description": "Bad request or file download failed"
          },
          "401": {
            "description": "Unauthorized"
          },
          "502": {
            "description": "S3 upload failed"
          }
        }
      }
    },
    "/v1/clickup/update-users": {
      "post": {
        "operationId": "update_clickup_users",
        "summary": "Add/Remove Users",
        "description": "Add or remove users from an account. When action is add, provide emails_to_add. When action is remove, provide users_to_remove with their email, user_id, and username.",
        "tags": [
          "PRF"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/clickup/update-users)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "update_clickup_users",
            "description": "Add or remove users from an account.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": true,
              "idempotentHint": false
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "accid",
                  "memberid",
                  "church_name",
                  "action",
                  "submitted_by"
                ],
                "properties": {
                  "accid": {
                    "type": "string",
                    "description": "Airtable record ID for the account"
                  },
                  "memberid": {
                    "type": "integer",
                    "description": "Account/member number"
                  },
                  "church_name": {
                    "type": "string",
                    "description": "Church name"
                  },
                  "action": {
                    "type": "string",
                    "enum": [
                      "add",
                      "remove"
                    ],
                    "description": "Whether to add or remove users"
                  },
                  "submitted_by": {
                    "type": "object",
                    "required": [
                      "id",
                      "email",
                      "username"
                    ],
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "email": {
                        "type": "string"
                      },
                      "username": {
                        "type": "string"
                      }
                    },
                    "description": "Who is performing this action"
                  },
                  "emails_to_add": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Email addresses to add (required when action is add)"
                  },
                  "users_to_remove": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "email",
                        "user_id",
                        "username"
                      ],
                      "properties": {
                        "email": {
                          "type": "string"
                        },
                        "user_id": {
                          "type": "string"
                        },
                        "username": {
                          "type": "string"
                        }
                      }
                    },
                    "description": "Users to remove (required when action is remove)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Users updated"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/design/squad-wrapped/{account}": {
      "get": {
        "operationId": "get_squad_wrapped",
        "summary": "Get Squad Wrapped",
        "description": "Returns the Squad Wrapped year-in-review stats for an account: project counts, top designers, ratings, busiest month, weekly rhythm, and more. Defaults to the current year; pass an earlier year via the `year` query parameter. Future years are not allowed.",
        "tags": [
          "Design"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/design/get-squad-wrapped)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_squad_wrapped",
            "description": "Returns the Squad Wrapped year-in-review stats for an account: project counts, top designers, ratings, busiest month, weekly rhythm, and more. Defaults to the current year; pass an earlier year via the `year` query parameter. Future years are not allowed.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "account",
            "in": "path",
            "required": true,
            "description": "Account/member number.",
            "schema": {
              "type": "integer"
            },
            "example": 2787
          },
          {
            "name": "year",
            "in": "query",
            "required": false,
            "description": "Year to compute stats for. Defaults to the current year. Must not be a future year.",
            "schema": {
              "type": "integer"
            },
            "example": 2025
          }
        ],
        "responses": {
          "200": {
            "description": "Squad Wrapped stats for the account and year",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing/invalid account or future year"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Account not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/clickup/statuses": {
      "get": {
        "operationId": "list_clickup_statuses",
        "summary": "List ClickUp Statuses",
        "description": "Returns the catalog of ClickUp statuses with their active flag and badge color. Only statuses with a color set are returned.",
        "tags": [
          "ClickUp"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/clickup/list-statuses)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "list_clickup_statuses",
            "description": "Returns the catalog of ClickUp statuses with their active flag and badge color. Only statuses with a color set are returned.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "responses": {
          "200": {
            "description": "List of statuses",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer"
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "status": {
                            "type": "string"
                          },
                          "status_is_active": {
                            "type": "boolean"
                          },
                          "status_badge_color": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/clickup/generate-task-description": {
      "get": {
        "operationId": "generate_task_description",
        "summary": "Generate ClickUp Task Descriptions",
        "description": "Renders ClickUp-ready markdown task descriptions for a submission. The `id` parameter accepts any of: a PRF general submission UUID (returns every child project), a legacy `prf_project_submissions.project_submission_id` (returns just that project), or a new-endpoint `forms.form_submissions.id` (returns just that form). For new-endpoint forms, the renderer prefers `questionShortLabel` over the long `label` when present. Legacy submissions render from the Fillout `0. Final Content` calculation template, mirroring the n8n workflow `aLvfX2XhNgBcZuhX` so labels match 1-for-1 (Number of Social Media Graphics, Image #N, Audience, DESCRIPTION, VISION, Design Style, Brand Guide, etc.). Brand Guide is resolved via `prf_airtable_account_records.member_number` -> `prf_brand_guides`. Holiday Type is resolved via `clickup_tags` (`grouping='Holiday'`). The `giid` query param is still accepted as an alias for `id`.",
        "tags": [
          "ClickUp"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/clickup/generate-task-description)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "generate_task_description",
            "description": "Renders ClickUp-ready markdown task descriptions. Accepts an `id` that can be a PRF general submission UUID, a legacy project_submission_id, or a new-endpoint form_submissions.id, and returns one markdown string per matching submission.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": false,
            "description": "Submission UUID. Accepts a general submission id, a legacy project_submission_id, or a new-endpoint form_submissions.id. Exactly one of `id` or `giid` must be supplied.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "0fe3390c-e804-408b-ae24-ca466580046d"
          },
          {
            "name": "giid",
            "in": "query",
            "required": false,
            "description": "Alias for `id`. PRF general submission UUID (or any other supported submission id).",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "4795f511-1c9c-4993-b561-beab9ca813ff"
          },
          {
            "name": "variant",
            "in": "query",
            "required": false,
            "description": "Social Media Promotion (SMP) render variant. When set, `id` must be an SMP form_submissions.id (form VpFFPutB). Returns one markdown tailored to the task: `smp_main` (post inventory), `smp_graphics` (per graphic/photo slot), or `smp_reel` (one slot — requires `slot`). Each variant also includes the shared Creative Direction / General Info sections and the Dropbox folder-code suffix.",
            "schema": {
              "type": "string",
              "enum": [
                "smp_main",
                "smp_graphics",
                "smp_reel"
              ]
            },
            "example": "smp_reel"
          },
          {
            "name": "slot",
            "in": "query",
            "required": false,
            "description": "SMP slot key for `variant=smp_reel` — one of `ann` (Event Announcement), `r1w` (Reminder 1 Week), `r3d` (Reminder 3 Days), `dbd` (Day-Before/Of). Required for `smp_reel`; ignored otherwise.",
            "schema": {
              "type": "string",
              "enum": [
                "ann",
                "r1w",
                "r3d",
                "dbd"
              ]
            },
            "example": "r3d"
          },
          {
            "name": "source_id",
            "in": "query",
            "required": false,
            "description": "SMP only. The source Event task's ClickUp id, used for the 'DEPENDENT TASK - Waiting on' prefix (all variants) and the EVENT QUICK-REFERENCE parent link (Graphics/Reel). When omitted, the endpoint falls back to resolving via `eventsid`, then the submission's `giid` → primary project sibling; if still unresolved, the link is omitted.",
            "schema": {
              "type": "string"
            },
            "example": "86e1ggrw2"
          },
          {
            "name": "eventsid",
            "in": "query",
            "required": false,
            "description": "SMP only. A legacy prf_project_submissions.project_submission_id pointing at the source Event task; resolved to its clickup_id for the dependent/parent link when `source_id` is absent.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Rendered markdowns",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "The id supplied by the caller (general, project, or form submission)."
                    },
                    "giid": {
                      "type": "string",
                      "nullable": true,
                      "description": "The parent PRF general submission id, when one was resolved."
                    },
                    "submission_version": {
                      "type": "string",
                      "enum": [
                        "new_endpoint",
                        "legacy"
                      ]
                    },
                    "count": {
                      "type": "integer"
                    },
                    "markdowns": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "submission_id": {
                            "type": "string"
                          },
                          "markdown": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid id"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Submission not found"
          },
          "422": {
            "description": "Unknown submission_version"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/squad-sdk/components": {
      "get": {
        "operationId": "list_squad_sdk_components",
        "summary": "List Squad SDK Components",
        "description": "Returns the full Squad SDK component registry — a list of every available component with its metadata.",
        "tags": [
          "Squad SDK"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/squad-sdk/list-components)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "list_squad_sdk_components",
            "description": "Returns the full Squad SDK component registry — a list of every available component with its metadata.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "responses": {
          "200": {
            "description": "Squad SDK component registry",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/squad-sdk/components/{component}": {
      "get": {
        "operationId": "get_squad_sdk_component",
        "summary": "Get Squad SDK Component",
        "description": "Returns the registry definition for a single Squad SDK component by name (e.g. 'button', 'data-grid').",
        "tags": [
          "Squad SDK"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/squad-sdk/get-component)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_squad_sdk_component",
            "description": "Returns the registry definition for a single Squad SDK component by name (e.g. 'button', 'data-grid').",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "component",
            "in": "path",
            "required": true,
            "description": "The component name as it appears in the Squad SDK registry (e.g. 'button', 'data-grid').",
            "schema": {
              "type": "string"
            },
            "example": "button"
          }
        ],
        "responses": {
          "200": {
            "description": "Squad SDK component definition",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing or invalid component name"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Component not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/remix/products": {
      "get": {
        "operationId": "list_remix_products",
        "summary": "List Remix Products",
        "description": "Returns a paginated list of rows from the remix.products table. Supports filtering by name (ilike), category, color, and active flag. Returns all columns for each row.",
        "tags": [
          "Remix"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/remix/list-products)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "list_remix_products",
            "description": "Returns a paginated list of rows from the remix.products table. Supports filtering by name (ilike), category, color, and active flag. Returns all columns for each row.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Row-wide case-insensitive substring search across name, description, and link. If the value is a positive integer, also matches id exactly.",
            "schema": {
              "type": "string"
            },
            "example": "easter"
          },
          {
            "name": "name",
            "in": "query",
            "required": false,
            "description": "Case-insensitive partial match on product name.",
            "schema": {
              "type": "string"
            },
            "example": "logo"
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Filter products whose `categories` array contains this value.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "color",
            "in": "query",
            "required": false,
            "description": "Filter products whose `colors` array contains this value.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "active",
            "in": "query",
            "required": false,
            "description": "Filter by active flag (true/false).",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "taxonomy_item_id",
            "in": "query",
            "required": false,
            "description": "Filter products linked to this remix.taxonomy_items.id via taxonomy_history. Uses an inner join so only matching products are returned.",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "example": 3404
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows to return (default 50, max 200).",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 200
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Row offset for pagination (default 0).",
            "schema": {
              "type": "integer",
              "default": 0,
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated remix product list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/remix/products/{id}": {
      "get": {
        "operationId": "get_remix_product",
        "summary": "Get Remix Product",
        "description": "Returns a single row from the remix.products table by id. Returns all columns.",
        "tags": [
          "Remix"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/remix/get-product)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_remix_product",
            "description": "Returns a single row from the remix.products table by id. Returns all columns.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The remix.products row id (bigint).",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Remix product row",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - invalid id"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Remix product not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/remix/taxonomies": {
      "get": {
        "operationId": "list_remix_taxonomies",
        "summary": "List Remix Taxonomies",
        "description": "Returns rows from remix.taxonomies with their nested taxonomy_items by default. Useful for populating filter dropdowns. Supports optional `slug` filter.",
        "tags": [
          "Remix"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/remix/list-taxonomies)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "list_remix_taxonomies",
            "description": "Returns rows from remix.taxonomies with their nested taxonomy_items by default. Useful for populating filter dropdowns.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "slug",
            "in": "query",
            "required": false,
            "description": "Return only the taxonomy with this slug (e.g. 'category', 'season', 'ministry').",
            "schema": {
              "type": "string"
            },
            "example": "season"
          },
          {
            "name": "include_items",
            "in": "query",
            "required": false,
            "description": "When true (default), embed the related taxonomy_items rows. Set to false for a lighter response.",
            "schema": {
              "type": "boolean",
              "default": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Remix taxonomies list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/design/production/queue": {
      "get": {
        "operationId": "get_production_queue",
        "summary": "Get Production Queue",
        "description": "Returns the production queue for a designer (the 'Up Next' list in the mySquad Chrome extension). Also expires stale checkouts and triggers production_release for any that just expired.",
        "tags": [
          "Design"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/design/production/get-queue)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_production_queue",
            "description": "Returns the production queue for a designer. Also expires stale checkouts.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "designer_id",
            "in": "query",
            "required": false,
            "description": "ClickUp user id of the designer requesting the queue. If omitted, returns the global queue.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows to return (default 50, max 500).",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 500
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Production queue rows",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/design/production/checkout": {
      "post": {
        "operationId": "claim_production_task",
        "summary": "Claim Production Task",
        "description": "Claims (checks out) a production task for a designer. Equivalent to the 'Claim This Task' button in the mySquad Chrome extension. Refuses if the task currently has active ClickUp time tracking by a non-production user. Fires ClickUp custom-field updates and orchestrator production_claim event.",
        "tags": [
          "Design"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/design/production/claim-checkout)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "claim_production_task",
            "description": "Claims a production task for a designer. Refuses if the task has active ClickUp time tracking.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "task_id",
                  "designer_id"
                ],
                "properties": {
                  "task_id": {
                    "type": "string",
                    "description": "ClickUp task id."
                  },
                  "designer_id": {
                    "type": "string",
                    "description": "ClickUp user id of the claiming designer."
                  },
                  "designer_name": {
                    "type": "string",
                    "description": "Display name for ClickUp custom field. Falls back to designer_id."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Checkout claimed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing task_id or designer_id"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "409": {
            "description": "Conflict - task has active time tracking or RPC rejected"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      },
      "patch": {
        "operationId": "extend_production_checkout",
        "summary": "Extend Production Checkout",
        "description": "Extends the active production checkout for a task. Maps to pd_extend_checkout RPC.",
        "tags": [
          "Design"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/design/production/extend-checkout)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "extend_production_checkout",
            "description": "Extends an active production checkout for a task.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "task_id",
                  "designer_id"
                ],
                "properties": {
                  "task_id": {
                    "type": "string"
                  },
                  "designer_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Checkout extended",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing task_id or designer_id"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      },
      "delete": {
        "operationId": "release_production_checkout",
        "summary": "Release Production Checkout",
        "description": "Releases (returns to queue) an active production checkout for a task. Clears ClickUp custom fields and triggers orchestrator production_release event.",
        "tags": [
          "Design"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/design/production/release-checkout)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "release_production_checkout",
            "description": "Releases an active production checkout. Clears ClickUp fields and triggers orchestrator production_release.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": true,
              "idempotentHint": false
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "task_id",
                  "designer_id"
                ],
                "properties": {
                  "task_id": {
                    "type": "string"
                  },
                  "designer_id": {
                    "type": "string"
                  },
                  "reason": {
                    "type": "string",
                    "description": "Optional release reason (defaults to 'manual')."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Checkout released",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing task_id or designer_id"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/design/designers/completed-tasks": {
      "get": {
        "operationId": "get_designer_completed_tasks",
        "summary": "Get Designer Completed Tasks",
        "description": "Returns tasks completed by a designer (statuses 'closed' or 'final files delivered') within the given date range, plus a comparison against the immediately preceding window of the same length. Wraps the public.get_designer_completed_tasks RPC.",
        "tags": [
          "Design"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/design/designers/get-completed-tasks)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_designer_completed_tasks",
            "description": "Returns tasks completed by a designer in the given date range, with a comparison to the immediately preceding window of equal length.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "email",
            "in": "query",
            "required": true,
            "description": "Designer email (case-insensitive). Resolved against clickup_users.email.",
            "schema": {
              "type": "string",
              "format": "email"
            },
            "example": "cina@churchmediasquad.com"
          },
          {
            "name": "start_date",
            "in": "query",
            "required": true,
            "description": "Window start date (inclusive), YYYY-MM-DD.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-05-05"
          },
          {
            "name": "end_date",
            "in": "query",
            "required": true,
            "description": "Window end date (inclusive), YYYY-MM-DD. Must be >= start_date.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-05-11"
          }
        ],
        "responses": {
          "200": {
            "description": "Designer completion summary with current and previous period task lists",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing or invalid parameters"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "No ClickUp user found for the given email"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/me": {
      "get": {
        "operationId": "get_me",
        "summary": "Get Caller Identity",
        "description": "Returns the consumer identity resolved from the API key, including any metadata attached to it (e.g. email, clickup_id, account). Useful for debugging key configuration and for clients to confirm how the gateway sees them.",
        "tags": [
          "Other"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/me)"
          },
          "policies": {
            "inbound": [
              "api-key-inbound"
            ]
          }
        },
        "responses": {
          "200": {
            "description": "Caller identity",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Metadata attached to the API key in the Zuplo API Key Service (account, clickup_id, email, name, role, etc.)",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          }
        }
      }
    },
    "/v1/prf/client/general-submissions": {
      "post": {
        "operationId": "create_prf_general_submission_client",
        "summary": "Create New Project Request",
        "description": "Client-facing endpoint for creating a PRF general submission from an external system or MCP agent. Accepts a bare response array of {id, value} entries. Identity (member id + submitting user) is resolved from the API key metadata — never from the body. Server mints the giid and returns it. For each projectType in projectDetailsSubmissions the server also stubs an in_progress forms.form_submissions row whose id is returned in the response; the agent then completes each project submission separately (see follow-up endpoints).",
        "tags": [
          "PRF Client API"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/prf/external/create-general-submission)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "create_prf_general_submission",
            "description": "Create a PRF general submission. Body is a response array of {id, value} entries. Server mints the giid and returns it plus an in_progress submissionId per project type. The LLM then completes each project submission separately. Required entries: projectTitle, description, ministry, selectedProjects, projectDetailsSubmissions, and (trustSquad===true OR vision non-empty). Exactly one submission across projectDetailsSubmissions must have isPrimary: true. All uploadedImages[].url values are HEAD-checked before commit.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "description": "Response array of {id, value} entries.",
                "items": {
                  "type": "object",
                  "required": [
                    "id",
                    "value"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "value": {}
                  }
                }
              },
              "examples": {
                "minimal": {
                  "summary": "Minimum required + projectDetailsSubmissions with isPrimary",
                  "value": [
                    {
                      "id": "selectedProjects",
                      "value": [
                        2,
                        60
                      ]
                    },
                    {
                      "id": "projectTitle",
                      "value": "Easter Sermon Series"
                    },
                    {
                      "id": "description",
                      "value": "Five week sermon series for Easter."
                    },
                    {
                      "id": "ministry",
                      "value": "All Church"
                    },
                    {
                      "id": "vision",
                      "value": "Bright, hopeful, photo-driven."
                    },
                    {
                      "id": "projectDetailsSubmissions",
                      "value": [
                        {
                          "projectType": 2,
                          "submissions": [
                            {
                              "isPrimary": true
                            }
                          ]
                        },
                        {
                          "projectType": 60,
                          "submissions": [
                            {
                              "isPrimary": false
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "General submission created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PrfGeneralSubmissionCreated"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PrfCreateError"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/references": {
      "get": {
        "operationId": "list_image_gen_references",
        "summary": "List Design References",
        "description": "List references (newest first), active only by default. Each has `url` (full-res) and `preview_url` (<5 MB JPEG for vision). Returns { references: [...] }.",
        "tags": [
          "Library"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "list_image_gen_references",
            "description": "List references (newest first), active only by default. Each has `url` (full-res) and `preview_url` (<5 MB JPEG for vision). Returns { references: [...] }.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "active",
            "in": "query",
            "required": false,
            "description": "Default active only; active=false → retired only; active=all → both.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false",
                "all"
              ]
            },
            "example": "true"
          },
          {
            "name": "preferred",
            "in": "query",
            "required": false,
            "description": "preferred=true (with account_number) → only that church's preferred/recommended designers.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "palette",
            "in": "query",
            "required": false,
            "description": "Palette family slug.",
            "schema": {
              "type": "string",
              "enum": [
                "deep-red-and-cream",
                "cool-blue-navy",
                "forest-green-emerald",
                "plum-violet-purple",
                "mustard-amber-gold",
                "monochrome-bw",
                "hot-pink-magenta",
                "electric-neon",
                "earth-clay-terracotta",
                "desaturated-grayscale",
                "pastel-soft",
                "orange-coral-sunset"
              ]
            },
            "example": "cool-blue-navy"
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Project type.",
            "schema": {
              "type": "string",
              "enum": [
                "sermon",
                "social",
                "event",
                "slides",
                "branding",
                "apparel",
                "mockup",
                "other"
              ]
            },
            "example": "sermon"
          },
          {
            "name": "design_style",
            "in": "query",
            "required": false,
            "description": "Design style (one of six).",
            "schema": {
              "type": "string",
              "enum": [
                "experimental",
                "grunge",
                "minimal",
                "photo-driven",
                "professional",
                "type-driven"
              ]
            },
            "example": "type-driven"
          },
          {
            "name": "season",
            "in": "query",
            "required": false,
            "description": "Season.",
            "schema": {
              "type": "string",
              "enum": [
                "christmas",
                "easter",
                "spring",
                "summer",
                "fall",
                "winter",
                "generic"
              ]
            },
            "example": "easter"
          },
          {
            "name": "account_number",
            "in": "query",
            "required": false,
            "description": "Exact match — church account number.",
            "schema": {
              "type": "string"
            },
            "example": "1048"
          },
          {
            "name": "designer_email",
            "in": "query",
            "required": false,
            "description": "Exact match — originating designer email.",
            "schema": {
              "type": "string"
            },
            "example": "david@churchmediasquad.com"
          },
          {
            "name": "task_id",
            "in": "query",
            "required": false,
            "description": "Exact match — ClickUp task id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Pagination limit (max 2000).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 2000
            },
            "example": 24
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Pagination offset.",
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "example": 0
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "description": "count=1 returns { total } instead of rows.",
            "schema": {
              "type": "integer",
              "enum": [
                1
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "references": [
                    {
                      "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                      "ref_id": "REF-12345",
                      "url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/original",
                      "preview_url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/preview",
                      "colors": [
                        "#0a7d3b",
                        "#f4ede1",
                        "#1a1a1a"
                      ],
                      "color_names": [
                        "Forest",
                        "Cream",
                        "Charcoal"
                      ],
                      "palette_tag": "warm-sage",
                      "type": "sermon",
                      "season": "summer",
                      "design_styles": [
                        "editorial"
                      ],
                      "aspect": "social",
                      "text_coverage": "minimal",
                      "account_number": "2049",
                      "active": true
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      },
      "post": {
        "operationId": "upload_image_gen_reference",
        "summary": "Upload One Reference",
        "description": "Upload one image (multipart/form-data). `image` is required; the rest override auto-tags. Runs the full pipeline (Wasabi → palette → phash → auto-tag → embedding; apparel auto-retires).",
        "tags": [
          "Ingest"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "upload_image_gen_reference",
            "description": "Upload one image (multipart/form-data). `image` is required; the rest override auto-tags. Runs the full pipeline (Wasabi → palette → phash → auto-tag → embedding; apparel auto-retires).",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "image"
                ],
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary",
                    "description": "The image file (jpeg / png / webp)."
                  },
                  "account_number": {
                    "type": "string",
                    "example": "1048"
                  },
                  "designer_email": {
                    "type": "string",
                    "example": "david@churchmediasquad.com"
                  },
                  "source": {
                    "type": "string",
                    "example": "pinterest"
                  },
                  "task_id": {
                    "type": "string"
                  },
                  "palette_tag": {
                    "type": "string",
                    "enum": [
                      "deep-red-and-cream",
                      "cool-blue-navy",
                      "forest-green-emerald",
                      "plum-violet-purple",
                      "mustard-amber-gold",
                      "monochrome-bw",
                      "hot-pink-magenta",
                      "electric-neon",
                      "earth-clay-terracotta",
                      "desaturated-grayscale",
                      "pastel-soft",
                      "orange-coral-sunset"
                    ]
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "sermon",
                      "social",
                      "event",
                      "slides",
                      "branding",
                      "apparel",
                      "mockup",
                      "other"
                    ]
                  },
                  "season": {
                    "type": "string",
                    "enum": [
                      "christmas",
                      "easter",
                      "spring",
                      "summer",
                      "fall",
                      "winter",
                      "generic"
                    ]
                  },
                  "imagery_mode": {
                    "type": "string",
                    "enum": [
                      "none",
                      "optional",
                      "required"
                    ]
                  },
                  "type_voices": {
                    "type": "string"
                  },
                  "style_note": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "reference": {
                    "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                    "ref_id": "REF-12345",
                    "url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/original",
                    "preview_url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/preview",
                    "colors": [
                      "#0a7d3b",
                      "#f4ede1",
                      "#1a1a1a"
                    ],
                    "color_names": [
                      "Forest",
                      "Cream",
                      "Charcoal"
                    ],
                    "palette_tag": "warm-sage",
                    "type": "sermon",
                    "season": "summer",
                    "design_styles": [
                      "editorial"
                    ],
                    "aspect": "social",
                    "text_coverage": "minimal",
                    "account_number": "2049",
                    "active": true
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/references/search": {
      "get": {
        "operationId": "search_image_gen_references",
        "summary": "Semantic Style Search",
        "description": "Semantic style search — embeds the query (OpenAI) and ranks by cosine similarity. Returns references with a `score`.",
        "tags": [
          "Library"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "search_image_gen_references",
            "description": "Semantic style search — embeds the query (OpenAI) and ranks by cosine similarity. Returns references with a `score`.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Natural-language style query.",
            "schema": {
              "type": "string"
            },
            "example": "moody blue serif"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max results (default 24).",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "example": 24
          },
          {
            "name": "active",
            "in": "query",
            "required": false,
            "description": "active=false to include retired.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false",
                "all"
              ]
            },
            "example": "true"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "query": "easter",
                  "space": "style",
                  "references": [
                    {
                      "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                      "ref_id": "REF-12345",
                      "url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/original",
                      "preview_url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/preview",
                      "colors": [
                        "#0a7d3b",
                        "#f4ede1",
                        "#1a1a1a"
                      ],
                      "color_names": [
                        "Forest",
                        "Cream",
                        "Charcoal"
                      ],
                      "palette_tag": "warm-sage",
                      "type": "sermon",
                      "season": "summer",
                      "design_styles": [
                        "editorial"
                      ],
                      "aspect": "social",
                      "text_coverage": "minimal",
                      "account_number": "2049",
                      "active": true,
                      "score": 0.82
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/references/roll": {
      "get": {
        "operationId": "roll_image_gen_references",
        "summary": "Roll Design References",
        "description": "Sample a variety-constrained set, or replay a saved roll by seed. With account_number, returns an in-church + outside-church split (outside slots prefer the church's preferred designers). Returns { seed, references, meta }.",
        "tags": [
          "Sampling"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "roll_image_gen_references",
            "description": "Sample a variety-constrained set, or replay a saved roll by seed. With account_number, returns an in-church + outside-church split (outside slots prefer the church's preferred designers). Returns { seed, references, meta }.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "account_number",
            "in": "query",
            "required": false,
            "description": "Scope to a church → enables the inside/outside split and unlocks that church's church-specific work.",
            "schema": {
              "type": "string"
            },
            "example": "1048"
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "description": "Single-pool size when no account_number is set (default 3).",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "example": 3
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Semantic-guided roll: sample from references nearest this query.",
            "schema": {
              "type": "string"
            },
            "example": "romans series"
          },
          {
            "name": "space",
            "in": "query",
            "required": false,
            "description": "Embedding space for a semantic roll.",
            "schema": {
              "type": "string",
              "enum": [
                "style",
                "image"
              ]
            },
            "example": "style"
          },
          {
            "name": "season",
            "in": "query",
            "required": false,
            "description": "Restrict the pool by season.",
            "schema": {
              "type": "string",
              "enum": [
                "christmas",
                "easter",
                "spring",
                "summer",
                "fall",
                "winter",
                "generic"
              ]
            },
            "example": "christmas"
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Restrict the pool by project type.",
            "schema": {
              "type": "string",
              "enum": [
                "sermon",
                "social",
                "event",
                "slides",
                "branding",
                "apparel",
                "mockup",
                "other"
              ]
            },
            "example": "sermon"
          },
          {
            "name": "design_style",
            "in": "query",
            "required": false,
            "description": "Restrict the pool by design style.",
            "schema": {
              "type": "string",
              "enum": [
                "experimental",
                "grunge",
                "minimal",
                "photo-driven",
                "professional",
                "type-driven"
              ]
            },
            "example": "minimal"
          },
          {
            "name": "designer_email",
            "in": "query",
            "required": false,
            "description": "Restrict the pool by designer email.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "minPalettes",
            "in": "query",
            "required": false,
            "description": "Minimum distinct palette families.",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "minTypes",
            "in": "query",
            "required": false,
            "description": "Minimum distinct type registers.",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "minRegisters",
            "in": "query",
            "required": false,
            "description": "Minimum distinct visual registers.",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "maxPhoto",
            "in": "query",
            "required": false,
            "description": "Maximum photo-led references.",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "split",
            "in": "query",
            "required": false,
            "description": "split=false disables the auto inside/outside split even with account_number.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "churchCount",
            "in": "query",
            "required": false,
            "description": "Style-matched roll: how many of the church's own graphics to include.",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "insideCount",
            "in": "query",
            "required": false,
            "description": "Legacy split: number of the church's own references.",
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "example": 4
          },
          {
            "name": "outsideCount",
            "in": "query",
            "required": false,
            "description": "Legacy split: number of outside references.",
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "example": 3
          },
          {
            "name": "preferred",
            "in": "query",
            "required": false,
            "description": "preferred=true → only the church's preferred/recommended designers.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "fill",
            "in": "query",
            "required": false,
            "description": "Legacy single-pool top-up to `target`.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "target",
            "in": "query",
            "required": false,
            "description": "Target roll size when fill=true.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000
            }
          },
          {
            "name": "seed",
            "in": "query",
            "required": false,
            "description": "Replay a previously returned roll exactly. Other params ignored when set.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "seed": "3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c",
                  "replayed": false,
                  "references": [
                    {
                      "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                      "ref_id": "REF-12345",
                      "url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/original",
                      "preview_url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/preview",
                      "colors": [
                        "#0a7d3b",
                        "#f4ede1",
                        "#1a1a1a"
                      ],
                      "color_names": [
                        "Forest",
                        "Cream",
                        "Charcoal"
                      ],
                      "palette_tag": "warm-sage",
                      "type": "sermon",
                      "season": "summer",
                      "design_styles": [
                        "editorial"
                      ],
                      "aspect": "social",
                      "text_coverage": "minimal",
                      "account_number": "2049",
                      "active": true
                    }
                  ],
                  "meta": {
                    "mode": "semantic",
                    "query": "easter",
                    "poolSize": 84,
                    "satisfied": true
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/references/bulk": {
      "post": {
        "operationId": "bulk_ingest_image_gen_references",
        "summary": "Bulk Ingest By URL",
        "description": "Bulk ingest by URL — fetches each image and ingests it. Idempotent per item when source_ref is set. Up to 100 items. Returns { requested, imported, skipped, failed, results }. ?stream=1 streams NDJSON progress.",
        "tags": [
          "Ingest"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "bulk_ingest_image_gen_references",
            "description": "Bulk ingest by URL — fetches each image and ingests it. Idempotent per item when source_ref is set. Up to 100 items. Returns { requested, imported, skipped, failed, results }. ?stream=1 streams NDJSON progress.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "stream",
            "in": "query",
            "required": false,
            "description": "stream=1 streams live NDJSON progress (meta → item* → done).",
            "schema": {
              "type": "integer",
              "enum": [
                1
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "items": [
                  {
                    "url": "https://example.com/a.jpg",
                    "account_number": "1048",
                    "source": "dropbox",
                    "source_ref": "dropbox:id:abc"
                  },
                  {
                    "url": "https://example.com/b.png",
                    "account_number": "1048",
                    "source": "dropbox",
                    "source_ref": "dropbox:id:def"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "imported": 12,
                  "skipped": 3,
                  "failed": 0,
                  "rejected": 1
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/dropbox/import": {
      "post": {
        "operationId": "import_image_gen_from_dropbox",
        "summary": "Import Finals From Dropbox",
        "description": "Import a church's recent finals from Dropbox by account #. One image per project, dedup by Dropbox file id. Returns { church_folder, tasks_scanned, imported, skipped, failed, items }.",
        "tags": [
          "Ingest"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "import_image_gen_from_dropbox",
            "description": "Import a church's recent finals from Dropbox by account #. One image per project, dedup by Dropbox file id. Returns { church_folder, tasks_scanned, imported, skipped, failed, items }.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "stream",
            "in": "query",
            "required": false,
            "description": "stream=1 streams live NDJSON progress.",
            "schema": {
              "type": "integer",
              "enum": [
                1
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "account_number": "1048",
                "limit": 50
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "account_number": "2049",
                  "imported": 34,
                  "skipped": 5,
                  "failed": 0
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/dropbox/import/log": {
      "get": {
        "operationId": "get_image_gen_dropbox_import_log",
        "summary": "Dropbox Import Log",
        "description": "Recent import runs (newest first), one row per church imported. Returns { runs: [...] }.",
        "tags": [
          "Ingest"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_image_gen_dropbox_import_log",
            "description": "Recent import runs (newest first), one row per church imported. Returns { runs: [...] }.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Rows to return (default 50, max 200).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "example": 50
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "runs": [
                    {
                      "task_id": "86abc12",
                      "account_number": "2049",
                      "imported": 34,
                      "finished_at": "2026-06-22T12:00:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/slack/import": {
      "post": {
        "operationId": "import_image_gen_from_slack",
        "summary": "Import From #squad-favs",
        "description": "Import image posts from #squad-favs. Dedup by Slack file id. Returns { found, imported, skipped, failed, items }.",
        "tags": [
          "Ingest"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "import_image_gen_from_slack",
            "description": "Import image posts from #squad-favs. Dedup by Slack file id. Returns { found, imported, skipped, failed, items }.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "stream",
            "in": "query",
            "required": false,
            "description": "stream=1 streams live NDJSON progress.",
            "schema": {
              "type": "integer",
              "enum": [
                1
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "days": 7
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "imported": 9,
                  "skipped": 1,
                  "failed": 0,
                  "rejected": 0
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/briefing": {
      "get": {
        "operationId": "list_image_gen_briefings",
        "summary": "List Partner Briefings",
        "description": "Recent briefings, newest first (one entry per generate run, so a task may appear multiple times). Each item: briefing_id, task_id, account_number, church_name, task_name, status, board_count, updated_at.",
        "tags": [
          "Moodboards"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "list_image_gen_briefings",
            "description": "Recent partner briefings, newest first (one entry per generate run, so a task may appear multiple times). Each item: briefing_id, task_id, account_number, church_name, task_name, status, board_count, updated_at. Use a briefing_id with get_image_gen_briefing to fetch an exact brief.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max briefings to return (default 50, max 200).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "items": [
                    {
                      "briefing_id": "b1a2b3c4-d5e6-4789-a0b1-c2d3e4f5a6b7",
                      "task_id": "86abc12",
                      "church_name": "Grace Community Church",
                      "created_at": "2026-06-22T12:00:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/briefing/generate": {
      "post": {
        "operationId": "generate_image_gen_briefing",
        "summary": "Generate Partner Briefing",
        "description": "Generate a new partner briefing for a ClickUp task: reads the task brief and comments plus the partner's project-request answers (selected style, vision, uploaded inspiration), the church's brand profile (auto-extracted from Dropbox when missing), and good-rated inspiration (squad favs, remixes, and other churches' library work, boosted toward the partner's chosen style), then returns two mood-board directions (each with a code-rendered board image) and a partner-facing message. Each call creates a NEW brief (a task can be re-briefed); the response includes its briefing_id. Results persist to Supabase. Long-running: typically 1-3 minutes.",
        "tags": [
          "Moodboards"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "generate_image_gen_briefing",
            "description": "Generate a new partner briefing for a ClickUp task: reads the task brief and comments plus the partner's project-request answers (selected style, vision, uploaded inspiration), the church's brand profile (auto-extracted from Dropbox when missing), and good-rated inspiration (squad favs, remixes, and other churches' library work, boosted toward the partner's chosen style), then returns two mood-board directions (each with a code-rendered board image) and a partner-facing message. Each call creates a NEW brief (a task can be re-briefed); the response includes its briefing_id. Results persist to Supabase. Long-running: typically 1-3 minutes.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "stream",
            "in": "query",
            "required": false,
            "description": "stream=1 streams live NDJSON progress.",
            "schema": {
              "type": "integer",
              "enum": [
                1
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "task_id": "86e1nunzd"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "briefing_id": "b1a2b3c4-d5e6-4789-a0b1-c2d3e4f5a6b7",
                  "task_id": "86abc12",
                  "boards": [
                    {
                      "index": 0,
                      "url": "https://ai-image-gen-reference.vercel.app/api/briefing/86abc12/board/0"
                    },
                    {
                      "index": 1,
                      "url": "https://ai-image-gen-reference.vercel.app/api/briefing/86abc12/board/1"
                    }
                  ],
                  "partner_message": "Here are two directions we explored…"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/briefing/{task}": {
      "get": {
        "operationId": "get_image_gen_briefing",
        "summary": "Get Partner Briefing",
        "description": "A stored briefing. The path value is either a briefing_id (UUID, an exact brief) or a ClickUp task id (returns that task's latest brief; generate one first). Returns the briefing_id, task summary, brand snapshot, partner direction (selected style, vision, uploaded inspiration), inspiration pools, both mood boards with board_image_path, and partner_message.",
        "tags": [
          "Moodboards"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_image_gen_briefing",
            "description": "A stored briefing. The path value is either a briefing_id (UUID, an exact brief) or a ClickUp task id (returns that task's latest brief; generate one first). Returns the briefing_id, task summary, brand snapshot, partner direction (selected style, vision, uploaded inspiration), inspiration pools, both mood boards with board_image_path, and partner_message.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "task",
            "in": "path",
            "required": true,
            "description": "A briefing_id (UUID) for an exact brief, or a ClickUp task id (e.g. 86e1nunzd) for that task's latest brief.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "briefing_id": "b1a2b3c4-d5e6-4789-a0b1-c2d3e4f5a6b7",
                  "task_id": "86abc12",
                  "church_name": "Grace Community Church",
                  "boards": [
                    {
                      "index": 0,
                      "url": "https://ai-image-gen-reference.vercel.app/api/briefing/86abc12/board/0"
                    }
                  ],
                  "partner_message": "Here are two directions…"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/briefing/{task}/board/{index}": {
      "get": {
        "operationId": "get_image_gen_briefing_board",
        "summary": "Get Briefing Mood-Board Image",
        "description": "A briefing's code-rendered mood-board JPEG (index 1 or 2). The path value is the briefing_id, so each brief has its own immutable image URL (a ClickUp task id also resolves, to that task's latest brief).",
        "tags": [
          "Moodboards"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "parameters": [
          {
            "name": "task",
            "in": "path",
            "required": true,
            "description": "The briefing_id (UUID), or a ClickUp task id for that task's latest brief.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "index",
            "in": "path",
            "required": true,
            "description": "Board number: 1 or 2.",
            "schema": {
              "type": "integer",
              "enum": [
                1,
                2
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/branding/{account}": {
      "get": {
        "operationId": "get_image_gen_brand_profile",
        "summary": "Get Brand Profile",
        "description": "A church's extracted brand profile: colors, fonts, logo rules, links, notes, and per-ministry sub-brands, plus stable URLs for its assets and code-rendered cards. Includes `card_url` (the combined brand card) and `card_section_urls` (per-section cards — `colors`/`logos`/`fonts`, each null when that band has no content) for the main brand and every sub-brand. For a fast 'what's available' check that avoids the asset join, use the `/availability` endpoint instead.",
        "tags": [
          "Branding"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_image_gen_brand_profile",
            "description": "A church's extracted brand profile (colors, fonts, logo rules, links, sub-brands) with stable asset URLs, the combined brand-card URL, and per-section card URLs (colors/logos/fonts) for the main brand and each sub-brand.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "account",
            "in": "path",
            "required": true,
            "description": "Church account number (3- or 4-digit), e.g. 2049.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "account_number": "2049",
                  "church_name": "Grace Community Church",
                  "colors": [
                    {
                      "hex": "#0a7d3b",
                      "name": "Forest",
                      "role": "primary"
                    },
                    {
                      "hex": "#f4ede1",
                      "name": "Cream",
                      "role": "background"
                    },
                    {
                      "hex": "#1a1a1a",
                      "name": "Charcoal",
                      "role": "text"
                    }
                  ],
                  "fonts": [
                    {
                      "family": "Sohne",
                      "role": "heading"
                    },
                    {
                      "family": "Tiempos Text",
                      "role": "body"
                    }
                  ],
                  "logo_rules": {
                    "clear_space": "1x cap-height",
                    "min_width_px": 120
                  },
                  "links": [
                    {
                      "label": "Website",
                      "url": "https://gracecommunity.church"
                    }
                  ],
                  "notes": "Warm, editorial, restrained.",
                  "status": "ready",
                  "source": "dropbox",
                  "error": null,
                  "extracted_at": "2026-06-18T09:00:00Z",
                  "card_url": "https://ai-image-gen-reference.vercel.app/api/branding/2049/card",
                  "card_section_urls": {
                    "colors": "https://ai-image-gen-reference.vercel.app/api/branding/2049/card?parts=colors",
                    "logos": "https://ai-image-gen-reference.vercel.app/api/branding/2049/card?parts=logos",
                    "fonts": "https://ai-image-gen-reference.vercel.app/api/branding/2049/card?parts=fonts"
                  },
                  "logos": [
                    {
                      "id": "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7",
                      "file_name": "logo-color.svg",
                      "url": "https://ai-image-gen-reference.vercel.app/api/b/a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7",
                      "variant": "color"
                    }
                  ],
                  "sub_brands": [
                    {
                      "name": "kids",
                      "colors": [
                        {
                          "hex": "#f59e0b",
                          "name": "Amber",
                          "role": "primary"
                        }
                      ],
                      "fonts": [],
                      "card_url": "https://ai-image-gen-reference.vercel.app/api/branding/2049/card?sub=kids",
                      "card_section_urls": {
                        "colors": "https://…?parts=colors&sub=kids",
                        "logos": null,
                        "fonts": null
                      },
                      "logos": []
                    }
                  ],
                  "font_files": [
                    {
                      "id": "f1f2f3f4-a5b6-4789-a0b1-c2d3e4f5a6b7",
                      "file_name": "Sohne-Buch.woff2",
                      "url": "https://ai-image-gen-reference.vercel.app/api/b/f1f2f3f4-a5b6-4789-a0b1-c2d3e4f5a6b7"
                    }
                  ],
                  "brand_guides": [
                    {
                      "id": "d0c1b2a3-e4f5-4678-90ab-cdef01234567",
                      "file_name": "Grace-Brand-Standards.pdf",
                      "url": "https://ai-image-gen-reference.vercel.app/api/b/d0c1b2a3-e4f5-4678-90ab-cdef01234567",
                      "media_type": "application/pdf",
                      "is_pdf": true,
                      "sub_brand": null
                    }
                  ],
                  "docs": [],
                  "sources": []
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "No brand profile for this account - extract it first"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      },
      "patch": {
        "operationId": "edit_image_gen_brand_colors",
        "summary": "Edit Brand Colors",
        "description": "Replace a church's brand color palette. Body: { colors: [{ hex, name?, role? }] }. Creates the profile if absent, re-renders the brand card, and LOCKS it so automatic re-extraction won't overwrite the church's edits.",
        "tags": [
          "Branding"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "edit_image_gen_brand_colors",
            "description": "Replace a church's brand color palette (hex/name/role). Re-renders the brand card and locks the profile against auto re-extraction.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "account",
            "in": "path",
            "required": true,
            "description": "Church account number (3- or 4-digit), e.g. 2049.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "colors": [
                  {
                    "hex": "#0a7d3b",
                    "name": "Forest",
                    "role": "primary"
                  },
                  {
                    "hex": "#f4c430",
                    "name": "Gold",
                    "role": "accent"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "account_number": "2049",
                  "locked": true,
                  "card_url": "https://ai-image-gen-reference.vercel.app/api/branding/2049/card",
                  "colors": [
                    {
                      "hex": "#0a7d3b",
                      "name": "Forest",
                      "role": "primary"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/branding/{account}/assets": {
      "post": {
        "operationId": "add_image_gen_brand_asset",
        "summary": "Add Brand Asset (Logo/Font)",
        "description": "Upload a logo or font FILE to a church's brand profile. multipart/form-data: kind=logo|font, file=<binary>. Re-renders the card and LOCKS the profile. Logos: png/jpeg/webp/svg; fonts: ttf/otf.",
        "tags": [
          "Branding"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "add_image_gen_brand_asset",
            "description": "Upload a logo or font file to a church's brand profile (multipart: kind, file). Re-renders the card and locks the profile.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "account",
            "in": "path",
            "required": true,
            "description": "Church account number (3- or 4-digit), e.g. 2049.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "kind",
                  "file"
                ],
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "logo",
                      "font"
                    ]
                  },
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "asset": {
                    "id": "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7",
                    "kind": "logo",
                    "file_name": "logo.svg",
                    "url": "https://ai-image-gen-reference.vercel.app/api/b/a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7"
                  },
                  "card_url": "https://ai-image-gen-reference.vercel.app/api/branding/2049/card"
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "413": {
            "description": "File too large"
          },
          "415": {
            "description": "Unsupported file type"
          },
          "422": {
            "description": "Unreadable font file"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/branding/{account}/assets/{id}": {
      "delete": {
        "operationId": "remove_image_gen_brand_asset",
        "summary": "Remove Brand Asset",
        "description": "Delete a logo or font asset from a church's brand profile, re-render the card, and LOCK the profile.",
        "tags": [
          "Branding"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "remove_image_gen_brand_asset",
            "description": "Delete a logo/font asset from a church's brand profile. Re-renders the card and locks the profile.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": true,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "account",
            "in": "path",
            "required": true,
            "description": "Church account number (3- or 4-digit), e.g. 2049.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Brand asset id (uuid) to remove.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "ok": true,
                  "deleted": "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7",
                  "card_url": "https://ai-image-gen-reference.vercel.app/api/branding/2049/card"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Asset not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/branding/{account}/availability": {
      "get": {
        "operationId": "get_image_gen_brand_availability",
        "summary": "Get Brand Availability",
        "description": "Fast availability manifest for an account — a single brand-profile row read (no asset join, no storage calls). Lists which card sections (`colors`/`logos`/`fonts`) have renderable content for the main brand and each sub-brand, with the ready-to-use `?parts=` card URLs and colors/fonts counts. Use it to learn, up front, which `?parts=` requests return real bands vs. fall back to the combined card.",
        "tags": [
          "Branding"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_image_gen_brand_availability",
            "description": "Fast manifest of what's available for an account: which card sections (colors/logos/fonts) have content for the main brand and each sub-brand, with the ?parts= card URLs and counts.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "account",
            "in": "path",
            "required": true,
            "description": "Church account number (3- or 4-digit), e.g. 2049.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "account_number": "2049",
                  "has_profile": true,
                  "sections": {
                    "colors": true,
                    "logos": true,
                    "fonts": true
                  },
                  "sub_brands": [
                    {
                      "name": "kids",
                      "sections": {
                        "colors": true,
                        "logos": true,
                        "fonts": false
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "No brand profile for this account - extract it first"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/branding/{account}/card": {
      "get": {
        "operationId": "get_image_gen_brand_card",
        "summary": "Get Brand Card Image",
        "description": "The church's code-rendered brand-card JPEG (logo chips + color swatches + font specimens) — the single text-free image handed to the image-gen model as brand context. `?parts=` selects any subset of `colors`,`logos`,`fonts`, stacked into one image on the fly (canonical order logos·colors·fonts); omit `parts` for the full combined card. `?sub=` returns a ministry sub-brand's card. The two compose, e.g. `?parts=colors&sub=kids`. A requested section with no content (or a profile extracted before per-section strips existed) falls back to the combined card. Stable URL; a re-extract replaces the image.",
        "tags": [
          "Branding"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "parameters": [
          {
            "name": "account",
            "in": "path",
            "required": true,
            "description": "Church account number (3- or 4-digit), e.g. 2049.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "parts",
            "in": "query",
            "required": false,
            "description": "Comma-separated subset of card sections to stack into one image: any of colors, logos, fonts. Omit for the full combined card.",
            "schema": {
              "type": "string",
              "example": "colors,logos"
            }
          },
          {
            "name": "sub",
            "in": "query",
            "required": false,
            "description": "Ministry sub-brand name (e.g. kids, youth, women) to return that sub-brand's card instead of the main identity.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/references/backfill": {
      "get": {
        "operationId": "backfill_status_image_gen_references",
        "summary": "Backfill Status",
        "description": "Progress/coverage of the backfill jobs (text coverage, content categories, image embeddings, etc.).",
        "tags": [
          "Library"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "backfill_status_image_gen_references",
            "description": "Progress/coverage of the backfill jobs (text coverage, content categories, image embeddings, etc.).",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "text_coverage",
            "in": "query",
            "required": false,
            "description": "Check text-coverage backfill.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "content_category",
            "in": "query",
            "required": false,
            "description": "Check content-category backfill.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "remaining": {
                    "text_coverage": 0,
                    "image_embeddings": 12,
                    "quality": 0,
                    "style": 5,
                    "style_facets": 5,
                    "content_category": 8
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      },
      "post": {
        "operationId": "backfill_image_gen_references",
        "summary": "Backfill Derived Data",
        "description": "Recompute derived data (colors / design styles / perceptual hash / embeddings) for rows missing it.",
        "tags": [
          "Ingest"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "backfill_image_gen_references",
            "description": "Recompute derived data (colors / design styles / perceptual hash / embeddings) for rows missing it.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "force",
            "in": "query",
            "required": false,
            "description": "force=true reprocesses every row.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "text_coverage",
            "in": "query",
            "required": false,
            "description": "text_coverage=true backfills text-coverage ratings.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "image_embeddings",
            "in": "query",
            "required": false,
            "description": "image_embeddings=true backfills image embeddings.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "quality",
            "in": "query",
            "required": false,
            "description": "quality=true backfills quality flags.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "craft",
            "in": "query",
            "required": false,
            "description": "craft=true backfills craft scores.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Cap the number of rows processed this run.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "mode": "text_coverage",
                  "processed": 50,
                  "done": 48,
                  "failed": 2,
                  "remaining": 0
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/references/{id}": {
      "patch": {
        "operationId": "update_image_gen_reference",
        "summary": "Update A Reference",
        "description": "Update editable fields: palette_tag, type, season, imagery_mode, type_voices, style_note, design_styles, source, task_id, account_number, designer_email, active.",
        "tags": [
          "Per-reference"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "update_image_gen_reference",
            "description": "Update editable fields: palette_tag, type, season, imagery_mode, type_voices, style_note, design_styles, source, task_id, account_number, designer_email, active.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Reference id (UUID).",
            "schema": {
              "type": "string"
            },
            "example": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "type": "sermon",
                "season": "easter",
                "design_styles": [
                  "type-driven",
                  "minimal"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "reference": {
                    "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                    "ref_id": "REF-12345",
                    "url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/original",
                    "preview_url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/preview",
                    "colors": [
                      "#0a7d3b",
                      "#f4ede1",
                      "#1a1a1a"
                    ],
                    "color_names": [
                      "Forest",
                      "Cream",
                      "Charcoal"
                    ],
                    "palette_tag": "warm-sage",
                    "type": "sermon",
                    "season": "summer",
                    "design_styles": [
                      "editorial"
                    ],
                    "aspect": "social",
                    "text_coverage": "minimal",
                    "account_number": "2049",
                    "active": true
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      },
      "delete": {
        "operationId": "delete_image_gen_reference",
        "summary": "Remove A Reference",
        "description": "Remove a reference. mode=soft (default) deactivates; mode=hard deletes the row + Wasabi file.",
        "tags": [
          "Per-reference"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "delete_image_gen_reference",
            "description": "Remove a reference. mode=soft (default) deactivates; mode=hard deletes the row + Wasabi file.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": true,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Reference id (UUID).",
            "schema": {
              "type": "string"
            },
            "example": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9"
          },
          {
            "name": "mode",
            "in": "query",
            "required": false,
            "description": "soft deactivates (reversible); hard deletes the row + file.",
            "schema": {
              "type": "string",
              "enum": [
                "soft",
                "hard"
              ]
            },
            "example": "soft"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "ok": true,
                  "mode": "soft",
                  "reference": {
                    "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                    "ref_id": "REF-12345",
                    "url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/original",
                    "preview_url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/preview",
                    "colors": [
                      "#0a7d3b",
                      "#f4ede1",
                      "#1a1a1a"
                    ],
                    "color_names": [
                      "Forest",
                      "Cream",
                      "Charcoal"
                    ],
                    "palette_tag": "warm-sage",
                    "type": "sermon",
                    "season": "summer",
                    "design_styles": [
                      "editorial"
                    ],
                    "aspect": "social",
                    "text_coverage": "minimal",
                    "account_number": "2049",
                    "active": false
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/references/{id}/retag": {
      "post": {
        "operationId": "retag_image_gen_reference",
        "summary": "Re-run Auto-tagging",
        "description": "Re-run auto-tagging (downscales oversized images for the vision call). Apparel auto-retires.",
        "tags": [
          "Per-reference"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "retag_image_gen_reference",
            "description": "Re-run auto-tagging (downscales oversized images for the vision call). Apparel auto-retires.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Reference id (UUID).",
            "schema": {
              "type": "string"
            },
            "example": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "reference": {
                    "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                    "ref_id": "REF-12345",
                    "url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/original",
                    "preview_url": "https://ai-image-gen-reference.vercel.app/api/r/8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b/preview",
                    "colors": [
                      "#0a7d3b",
                      "#f4ede1",
                      "#1a1a1a"
                    ],
                    "color_names": [
                      "Forest",
                      "Cream",
                      "Charcoal"
                    ],
                    "palette_tag": "warm-sage",
                    "type": "sermon",
                    "season": "summer",
                    "design_styles": [
                      "editorial"
                    ],
                    "aspect": "social",
                    "text_coverage": "minimal",
                    "account_number": "2049",
                    "active": true
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/references/{id}/feedback": {
      "post": {
        "operationId": "image_gen_reference_feedback",
        "summary": "Record Generation Feedback",
        "description": "Record generation feedback. Soft-retires past a downvote-rate threshold once it has enough impressions.",
        "tags": [
          "Per-reference"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "image_gen_reference_feedback",
            "description": "Record generation feedback. Soft-retires past a downvote-rate threshold once it has enough impressions.",
            "annotations": {
              "readOnlyHint": false,
              "destructiveHint": false,
              "idempotentHint": false
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Reference id (UUID).",
            "schema": {
              "type": "string"
            },
            "example": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "vote": "down"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "ref_id": "REF-12345",
                  "impressions": 120,
                  "downvotes": 4,
                  "active": true,
                  "retired": false
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/admin/account-tier": {
      "post": {
        "operationId": "image_gen_v2_admin_account_tier",
        "summary": "Set an Account's Tier",
        "description": "Assign a church account (`member_id`) to a tier within a tenant, and/or set per-account overrides (`weekly_generations`, `timezone`). Full-state upsert keyed by `(tenant_id, member_id)` — re-posting replaces the account's row. Limits resolve account → tier → tenant default. Key auth only (first-party integrators); tenant/tier creation is managed internally, not on this gateway.",
        "tags": [
          "Usage & Limits"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "tenant_id": "ets",
                "member_id": 2049,
                "tier_key": "pro",
                "weekly_generations": 200,
                "timezone": "America/Chicago"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "account": {
                      "tenant_id": "ets",
                      "member_id": 2049,
                      "tier_key": "pro",
                      "weekly_generations": 200,
                      "timezone": "America/Chicago",
                      "updated_at": "2026-06-25T18:42:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "member_id required"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/generate": {
      "post": {
        "operationId": "image_gen_v2_generate",
        "summary": "Generate a Design",
        "description": "Start a design generation: crafts a church-style prompt, renders (gpt-image-2 / Gemini fallback), self-grades, and rerolls to the best. Inserts two divergent rows and runs them async on Trigger.dev. Poll the rows or subscribe with the returned realtime token for progress.\n\n**Tenant (`tenant_id`)**: identifies the business the request belongs to so usage and limits are tracked separately. Codes: `thesquad` (TheSquad — the default when omitted), `ets` (Extend The Sermon), `churchcreate` (Church Create). `member_id` is the church account *within* that tenant.\n\n**Aspect ratio (`aspect_ratio`)**: presets `16:9`, `1:1`, `9:16`, `4:5`, `2:3`, `3:2` always work. Custom ratios (any `W:H`, ≤ 5:1, also accepts pixel dims like `1080:1350` which reduce to `4:5`) are allowed only when the tenant/tier has `allow_custom_aspect` enabled — otherwise a custom ratio returns `400 custom_aspect_not_enabled`.\n\n**Count (`count`)**: how many divergent designs to return, 1–6. Defaults to the tenant/tier `default_count` (2 if unset). The response `data.rows` holds one row per design; each runs and grades independently.",
        "tags": [
          "Design Generation"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "tenant_id": "thesquad",
                "member_id": 2049,
                "clickup_user_id": "90120098765",
                "prompt": "Summer in Psalms sermon series, warm and hopeful",
                "aspect_ratio": "16:9",
                "style_directions": [
                  "minimal"
                ],
                "inspiration_image_urls": [],
                "branding": {
                  "include_logo": false,
                  "include_colors": true,
                  "include_fonts": true
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "rows": [
                      {
                        "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                        "status": "queued",
                        "kind": "generate",
                        "aspect_ratio": "1:1",
                        "conversation_id": "3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c",
                        "style_direction": null,
                        "concept_route": "bold",
                        "concept_directive": "Sun-bleached desert horizon at dawn…"
                      },
                      {
                        "id": "c1d2e3f4-a5b6-4789-90ab-cdef01234567",
                        "status": "queued",
                        "kind": "generate",
                        "aspect_ratio": "1:1",
                        "conversation_id": "3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c",
                        "style_direction": null,
                        "concept_route": "minimal",
                        "concept_directive": "A single open psalter, generous negative space…"
                      }
                    ],
                    "conversation_id": "3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c",
                    "realtime": {
                      "token": "pk_tr_xxx",
                      "tag": "gen-conv:3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/refine": {
      "post": {
        "operationId": "image_gen_v2_refine",
        "summary": "Refine a Design",
        "description": "Refine a completed design in place (gpt-image-2 edit), or — when the ask reads as a redesign — auto-reroute through a fresh generation. Pass dry_run:true to classify without generating; force_kind to skip the classifier.",
        "tags": [
          "Design Generation"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "parent_row_id": "b2c3d4e5-f6a7-4859-9b0c-1d2e3f4a5b6c",
                "prompt": "make the title bigger and warmer",
                "member_id": 2049,
                "clickup_user_id": "90120098765"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "row": {
                      "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                      "status": "queued",
                      "kind": "refine",
                      "aspect_ratio": "1:1",
                      "conversation_id": "3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c",
                      "parent_row_id": "b2c3d4e5-f6a7-4859-9b0c-1d2e3f4a5b6c"
                    },
                    "realtime": {
                      "token": "pk_tr_xxx",
                      "tag": "gen-conv:3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/resize": {
      "post": {
        "operationId": "image_gen_v2_resize",
        "summary": "Resize a Design",
        "description": "Re-render a completed design to one or more new aspect ratios (Gemini image-to-image), fanned out as one batch workflow. hd:true renders the 4K tier.",
        "tags": [
          "Deliverables"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "parent_row_id": "b2c3d4e5-f6a7-4859-9b0c-1d2e3f4a5b6c",
                "aspect_ratios": [
                  "9:16",
                  "4:5"
                ],
                "member_id": 2049,
                "hd": false,
                "clickup_user_id": "90120098765"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "rows": [
                      {
                        "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                        "status": "queued",
                        "kind": "resize",
                        "aspect_ratio": "9:16",
                        "conversation_id": "3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c",
                        "parent_row_id": "b2c3d4e5-f6a7-4859-9b0c-1d2e3f4a5b6c"
                      },
                      {
                        "id": "d4e5f6a7-b8c9-4012-a3b4-c5d6e7f8a9b0",
                        "status": "queued",
                        "kind": "resize",
                        "aspect_ratio": "4:5",
                        "conversation_id": "3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c",
                        "parent_row_id": "b2c3d4e5-f6a7-4859-9b0c-1d2e3f4a5b6c"
                      }
                    ],
                    "row": {
                      "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b"
                    },
                    "realtime": {
                      "token": "pk_tr_xxx",
                      "tag": "gen-conv:3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/resize-uploaded": {
      "post": {
        "operationId": "image_gen_v2_resize_uploaded",
        "summary": "Resize an Uploaded Image",
        "description": "Resize a user-uploaded image (not a prior generation) to one or more aspect ratios. The source URL must be a public http(s) URL (SSRF-guarded).",
        "tags": [
          "Deliverables"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "source_url": "https://example.com/upload.png",
                "aspect_ratios": [
                  "1:1",
                  "16:9"
                ],
                "tenant_id": "thesquad",
                "member_id": 2049,
                "clickup_user_id": "90120098765"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "rows": [
                      {
                        "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                        "status": "queued",
                        "kind": "resize-uploaded",
                        "aspect_ratio": "1:1",
                        "conversation_id": "3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c"
                      }
                    ],
                    "conversation_id": "3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c",
                    "realtime": {
                      "token": "pk_tr_xxx",
                      "tag": "gen-conv:3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/background": {
      "post": {
        "operationId": "image_gen_v2_background",
        "summary": "Make a Background Plate",
        "description": "Turn a completed design into a clean, text-free background plate (subject + copy removed) the church can typeset over.",
        "tags": [
          "Deliverables"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "parent_row_id": "b2c3d4e5-f6a7-4859-9b0c-1d2e3f4a5b6c",
                "member_id": 2049,
                "clickup_user_id": "90120098765"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "row": {
                      "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                      "status": "queued",
                      "kind": "background",
                      "aspect_ratio": "1:1",
                      "conversation_id": "3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c",
                      "parent_row_id": "b2c3d4e5-f6a7-4859-9b0c-1d2e3f4a5b6c"
                    },
                    "realtime": {
                      "token": "pk_tr_xxx",
                      "tag": "gen-conv:3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/retitle": {
      "post": {
        "operationId": "image_gen_v2_retitle",
        "summary": "Swap Text on a Design",
        "description": "Replace or remove text on a completed design (empty `to` removes). Other elements are preserved.",
        "tags": [
          "Deliverables"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "parent_row_id": "b2c3d4e5-f6a7-4859-9b0c-1d2e3f4a5b6c",
                "replacements": [
                  {
                    "from": "Summer in Psalms",
                    "to": "Fall in Proverbs"
                  }
                ],
                "member_id": 2049,
                "clickup_user_id": "90120098765"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "row": {
                      "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                      "status": "queued",
                      "kind": "retitle",
                      "aspect_ratio": "1:1",
                      "conversation_id": "3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c",
                      "parent_row_id": "b2c3d4e5-f6a7-4859-9b0c-1d2e3f4a5b6c"
                    },
                    "realtime": {
                      "token": "pk_tr_xxx",
                      "tag": "gen-conv:3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/conversations": {
      "get": {
        "operationId": "image_gen_v2_list_conversations",
        "summary": "List Threads",
        "description": "List design threads for an ACCOUNT (member_id), a USER (clickup_user_id), or both — at least one is required. Newest activity first.",
        "tags": [
          "Conversations"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "parameters": [
          {
            "name": "member_id",
            "in": "query",
            "required": false,
            "description": "Church account number. Required unless clickup_user_id is given.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "clickup_user_id",
            "in": "query",
            "required": false,
            "description": "End-user id. Required unless member_id is given.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max threads (1-100, default 30).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include_archived",
            "in": "query",
            "required": false,
            "description": "'1' to include archived threads.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "threads": [
                      {
                        "conversation_id": "3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c",
                        "title": "Summer in Psalms",
                        "first_prompt": "Summer in Psalms sermon series…",
                        "last_activity_at": "2026-06-22T15:04:00Z",
                        "latest_kind": "generate",
                        "latest_status": "completed",
                        "latest_image_url": "https://ai-image-gen-reference.vercel.app/api/g/gen/90120098765/abc.jpg",
                        "latest_aspect_ratio": "16:9",
                        "archived": false
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/conversation": {
      "get": {
        "operationId": "image_gen_v2_get_conversation",
        "summary": "Get a Thread",
        "description": "Hydrate one thread — every row sharing the conversation_id, chronological. Staff-only debug fields (crafted prompt, token/cost counters) are included only for staff-authenticated callers.",
        "tags": [
          "Conversations"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "description": "Conversation UUID.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "conversation_id": "3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c",
                    "rows": [
                      {
                        "id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                        "parent_row_id": null,
                        "prompt": "Summer in Psalms…",
                        "kind": "generate",
                        "style_direction": null,
                        "aspect_ratio": "16:9",
                        "status": "completed",
                        "public_url": "https://ai-image-gen-reference.vercel.app/api/g/gen/90120098765/abc.jpg",
                        "created_at": "2026-06-22T15:03:00Z",
                        "rating": null,
                        "grade_pass": true,
                        "refine_suggestions": [
                          "Bolder headline",
                          "Warmer palette",
                          "Add a sunrise glow",
                          "Tighter crop"
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/conversation/rename": {
      "post": {
        "operationId": "image_gen_v2_rename_conversation",
        "summary": "Rename a Thread",
        "description": "Set the thread title (written on the earliest row). Empty title clears it.",
        "tags": [
          "Conversations"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "conversation_id": "3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c",
                "title": "Summer Series Hero",
                "clickup_user_id": "90120098765"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "conversation_id": "3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c",
                    "title": "Summer Series Hero"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/conversation/archive": {
      "post": {
        "operationId": "image_gen_v2_archive_conversation",
        "summary": "Archive a Thread",
        "description": "Archive (or unarchive) a thread — sets archived_at on every row in it.",
        "tags": [
          "Conversations"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "conversation_id": "3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c",
                "archived": true,
                "clickup_user_id": "90120098765"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "conversation_id": "3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c",
                    "archived_at": "2026-06-22T15:10:00Z",
                    "row_count": 3
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/conversation/title": {
      "post": {
        "operationId": "image_gen_v2_title_conversation",
        "summary": "Auto-Title a Thread",
        "description": "Generate a 2-5 word title from the first prompt (Haiku). Idempotent unless force:true.",
        "tags": [
          "Conversations"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "conversation_id": "3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c",
                "clickup_user_id": "90120098765"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "conversation_id": "3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c",
                    "title": "Summer in Psalms"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/conversation/select-source": {
      "post": {
        "operationId": "image_gen_v2_select_source",
        "summary": "Set the Main Design",
        "description": "Mark one completed row as the thread's main design (clears it on the others).",
        "tags": [
          "Conversations"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "conversation_id": "3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c",
                "row_id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                "clickup_user_id": "90120098765"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "conversation_id": "3a7c1e90-2b4d-4f81-9c2a-6d5e8f0a1b2c",
                    "row_id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/rate-generation": {
      "post": {
        "operationId": "image_gen_v2_rate_generation",
        "summary": "Rate a Generation",
        "description": "Thumbs up/down (+ optional notes) on a generation; feeds the account's style memory. rating:null clears it.",
        "tags": [
          "Feedback & Ratings"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "row_id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                "rating": "good",
                "notes": "love the palette",
                "clickup_user_id": "90120098765"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "row_id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                    "rating": "good",
                    "rated_at": "2026-06-22T15:12:00Z",
                    "rating_notes": null
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/rate-reference": {
      "post": {
        "operationId": "image_gen_v2_rate_reference",
        "summary": "Rate a Library Reference",
        "description": "Thumbs up/down on a library reference (the Hot-or-Not trainer); account-shared. rating:null un-rates.",
        "tags": [
          "Feedback & Ratings"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "member_id": 2049,
                "ref_id": "REF-12345",
                "rating": "good",
                "ref_url": "https://ai-image-gen-reference.vercel.app/api/r/.../original"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "ref_id": "REF-12345",
                    "rating": "good"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/feedback": {
      "post": {
        "operationId": "image_gen_v2_feedback",
        "summary": "Submit Design Feedback",
        "description": "Training-break capture after a download: mirrors the thumb onto the row + records structured follow-ups (issue tags, time-saved).",
        "tags": [
          "Feedback & Ratings"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "row_id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                "rating": "good",
                "issue_tags": [
                  "fonts"
                ],
                "time_saved": "yes",
                "notes": "shipped as-is",
                "clickup_user_id": "90120098765"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "row_id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                    "rating": "good",
                    "feedback_id": "f1a2b3c4-d5e6-4789-a0b1-c2d3e4f5a6b7"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/signal": {
      "post": {
        "operationId": "image_gen_v2_signal",
        "summary": "Log Behavioral Signals",
        "description": "Log implicit-positive signals ('download') against completed designs. Deduped server-side.",
        "tags": [
          "Feedback & Ratings"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "signals": [
                  {
                    "row_id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                    "signal_type": "download"
                  }
                ],
                "clickup_user_id": "90120098765"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "inserted": 1
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/liked-references": {
      "get": {
        "operationId": "image_gen_v2_liked_references",
        "summary": "List Liked References (staff)",
        "description": "An account's thumbs-up library references. Staff-only (returns internal library URLs).",
        "tags": [
          "Feedback & Ratings"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "parameters": [
          {
            "name": "account_id",
            "in": "query",
            "required": true,
            "description": "Church account number.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows (1-100, default 40).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "references": [
                      {
                        "ref_id": "REF-12345",
                        "ref_url": "https://…/api/r/…/original",
                        "ref_preview_url": "https://…/api/r/…/preview",
                        "ref_colors": [
                          "#0a7d3b",
                          "#f4ede1"
                        ],
                        "ref_palette_tag": "warm-sage"
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/reroll-preference": {
      "post": {
        "operationId": "image_gen_v2_reroll_preference",
        "summary": "Choose Original vs Reroll",
        "description": "Record whether the user preferred the original or the rerolled variant, and promote the chosen one to the row's main image.",
        "tags": [
          "Feedback & Ratings"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "row_id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                "preferred": "reroll",
                "clickup_user_id": "90120098765"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "row_id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                    "user_preferred_reroll": true,
                    "public_url": "https://ai-image-gen-reference.vercel.app/api/g/gen/90120098765/abc.jpg"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/usage": {
      "get": {
        "operationId": "image_gen_v2_usage",
        "summary": "Daily Usage / Cap",
        "description": "Current local-day new-project usage + cap for a user (cap anchored to the church timezone).",
        "tags": [
          "Usage & Limits"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "parameters": [
          {
            "name": "member_id",
            "in": "query",
            "required": false,
            "description": "Church account number (for the timezone).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "clickup_user_id",
            "in": "query",
            "required": false,
            "description": "End-user id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "used": 2,
                    "limit": 5,
                    "dayStart": "2026-06-22T04:00:00Z",
                    "resetsAt": "2026-06-23T04:00:00Z",
                    "timezone": "America/New_York",
                    "exempt": false
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/style-memory": {
      "get": {
        "operationId": "image_gen_v2_get_style_memory",
        "summary": "Get Style Memory",
        "description": "The account's distilled style memory (from ratings + selected-source picks), injected into future craft prompts.",
        "tags": [
          "Style Memory"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "parameters": [
          {
            "name": "member_id",
            "in": "query",
            "required": true,
            "description": "Church account number.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "memory": {
                      "member_id": 2049,
                      "memory_md": "## What this account keeps\n- Warm cream backgrounds with a single high-contrast charcoal headline\n…",
                      "signals_used": 14,
                      "status": "ready",
                      "generated_at": "2026-06-20T12:00:00Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      },
      "post": {
        "operationId": "image_gen_v2_rebuild_style_memory",
        "summary": "Rebuild Style Memory",
        "description": "Rebuild the account's style memory now (synchronous, 10-30s). 409 when there isn't enough rating/selection signal yet.",
        "tags": [
          "Style Memory"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "member_id": 2049
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "memory": {
                      "member_id": 2049,
                      "memory_md": "## What this account keeps\n- …",
                      "signals_used": 14,
                      "status": "ready"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/refine-suggestions": {
      "post": {
        "operationId": "image_gen_v2_refine_suggestions",
        "summary": "Quick-Edit Suggestions",
        "description": "4 short refinement directions for a completed design (pre-computed on completion; lazy fallback otherwise).",
        "tags": [
          "Prompt Tools"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "row_id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                "clickup_user_id": "90120098765"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "row_id": "8f14e45f-ceea-467d-9a3b-2c1d0e9f7a6b",
                    "suggestions": [
                      "Bolder headline",
                      "Warmer palette",
                      "Add a sunrise glow",
                      "Tighter crop"
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/clarify-prompt": {
      "post": {
        "operationId": "image_gen_v2_clarify_prompt",
        "summary": "Clarifying Questions",
        "description": "0-3 pre-generation clarifying questions (conflicts first, then missing info). Empty array = the prompt is specific enough.",
        "tags": [
          "Prompt Tools"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "prompt": "Easter service graphic",
                "style": "minimal",
                "project_type": "event",
                "has_references": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "questions": [
                      {
                        "id": "central-image",
                        "question": "What's the one image this should center on?",
                        "kind": "text"
                      },
                      {
                        "id": "service-time",
                        "question": "Which service time should it show?",
                        "kind": "choice",
                        "choices": [
                          "9am",
                          "11am",
                          "Both"
                        ],
                        "resolves": "conflict"
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/prompt-coach": {
      "post": {
        "operationId": "image_gen_v2_prompt_coach",
        "summary": "Prompt Coach Pills",
        "description": "0-3 one-click plain-English additions that push a thin prompt toward a more distinctive design. Empty array = already strong.",
        "tags": [
          "Prompt Tools"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "prompt": "graphic for our youth night",
                "project_type": "event",
                "has_references": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "suggestions": [
                      {
                        "id": "tight-crop",
                        "label": "Crop in tight on a face mid-laugh",
                        "kind": "append",
                        "replacement": ", cropped tight on one teen mid-laugh, not a posed group"
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/game-score": {
      "post": {
        "operationId": "image_gen_v2_game_score",
        "summary": "Submit Wait-Game Score",
        "description": "Record a wait-state mini-game score (engagement during render waits).",
        "tags": [
          "Engagement"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              },
              "example": {
                "game_key": "flappy-dove",
                "score": 42,
                "member_id": 2049,
                "clickup_user_id": "90120098765"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "ok": true
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/image-gen/gen/v2/leaderboards": {
      "get": {
        "operationId": "image_gen_v2_leaderboards",
        "summary": "Wait-Game Leaderboards",
        "description": "personal_best + account + global top-10 for a wait-state game.",
        "tags": [
          "Engagement"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/image-gen/proxy)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          }
        },
        "parameters": [
          {
            "name": "game_key",
            "in": "query",
            "required": true,
            "description": "Game key (flappy-dove, tap-sparkles, trivia, echo).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "account_id",
            "in": "query",
            "required": false,
            "description": "Church account number.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "clickup_user_id",
            "in": "query",
            "required": false,
            "description": "End-user id (for personal best).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "data": {
                    "personal_best": 42,
                    "account": [
                      {
                        "clickup_user_id": "90120098765",
                        "username": "jordan",
                        "profile_picture": null,
                        "score": 88
                      }
                    ],
                    "global": [
                      {
                        "clickup_user_id": "90120011111",
                        "username": "alex",
                        "profile_picture": null,
                        "score": 120
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    },
    "/v1/forms/{form_id}/previous-submission-values": {
      "get": {
        "operationId": "get_form_previous_submission_values",
        "summary": "Previous Submission Values",
        "description": "Returns an account's previously submitted answers for a given form, grouped by field id. Provide exactly one of 'account' or 'giid' (giid resolves the account via prf_general_submissions). Each field has its latest type and a 'responses' array (newest first) of { submission_id, value, timestamp } from every matching submission for that account. Account is matched via each form submission's giid -> prf_general_submissions.account. Defaults to completed submissions; pass ?status=all or a comma-separated list (in_progress,completed,abandoned). Blank/empty values are skipped.",
        "tags": [
          "Forms"
        ],
        "x-zuplo-route": {
          "corsPolicy": "none",
          "handler": {
            "export": "default",
            "module": "$import(./modules/forms/previous-submission-values)"
          },
          "policies": {
            "inbound": [
              "rate-limit-5-per-sec",
              "api-key-soft-inbound",
              "api-key-or-jwt-inbound"
            ],
            "outbound": [
              "request-logger-outbound"
            ]
          },
          "mcp": {
            "type": "tool",
            "name": "get_form_previous_submission_values",
            "description": "An account's previously submitted answers for a project type's form, grouped by field id; each field lists { submission_id, value, timestamp } newest-first across submissions.",
            "annotations": {
              "readOnlyHint": true,
              "destructiveHint": false,
              "idempotentHint": true
            }
          }
        },
        "parameters": [
          {
            "name": "form_id",
            "in": "path",
            "required": true,
            "description": "Form ID (forms.forms.id).",
            "schema": {
              "type": "string"
            },
            "example": "4Vf2NpDd"
          },
          {
            "name": "account",
            "in": "query",
            "required": false,
            "description": "Account / member number. Provide either this or 'giid' (exactly one required).",
            "schema": {
              "type": "integer"
            },
            "example": 306
          },
          {
            "name": "giid",
            "in": "query",
            "required": false,
            "description": "General submission id (UUID). When provided, the account is resolved from prf_general_submissions. Provide either this or 'account' (exactly one required).",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "2b223a36-81da-424a-aee7-ab1e3f7b7240"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Submission statuses to include. Defaults to 'completed'. Use 'all' for every status, or a comma-separated subset of in_progress, completed, abandoned.",
            "schema": {
              "type": "string",
              "default": "completed"
            },
            "example": "completed"
          }
        ],
        "responses": {
          "200": {
            "description": "Field history grouped by field id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing/invalid project_type_id or account"
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key"
          },
          "404": {
            "description": "Form not found"
          },
          "502": {
            "description": "Upstream service error"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "PrfGeneralSubmissionCreated": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Server-minted general submission id (giid)."
          },
          "status": {
            "type": "string",
            "enum": [
              "in_progress",
              "completed",
              "abandoned"
            ]
          },
          "member_info": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer"
              },
              "church_name": {
                "type": "string"
              }
            }
          },
          "response": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "url_params": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "idempotent_replay": {
            "type": "boolean",
            "description": "Present when this is a replay of a recent identical request."
          }
        }
      },
      "PrfCreateError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "bad_request"
            ]
          },
          "reason": {
            "type": "string",
            "description": "Machine-readable reason code.",
            "enum": [
              "cannot_update_legacy_submission",
              "invalid_body_shape",
              "invalid_json",
              "invalid_key_metadata",
              "invalid_response",
              "invalid_response_entry",
              "missing_key_metadata",
              "missing_submitter",
              "missing_submitter_clickupUserId",
              "missing_submitter_memberid",
              "unknown_member"
            ]
          },
          "message": {
            "type": "string"
          },
          "missing": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "invalid_urls": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "project_types": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          },
          "missing_project_types": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "PRF Client API",
      "description": "Client-facing API for external systems and MCP agents to create PRF requests. Identity is derived from the API key; no member id or user id is passed in the body."
    },
    {
      "name": "Forms",
      "description": "Endpoints for forms.forms metadata and related project type associations."
    },
    {
      "name": "Library",
      "description": "AI image-gen reference library — read the curated reference set."
    },
    {
      "name": "Sampling",
      "description": "Variety-constrained 'roll' the renderer pulls references from."
    },
    {
      "name": "Ingest",
      "description": "Add references. All paths run the same pipeline (Wasabi → palette → phash → auto-tag → embedding; apparel auto-retires)."
    },
    {
      "name": "Per-reference",
      "description": "Operate on a single reference by id."
    },
    {
      "name": "Maintenance",
      "description": "Tagging triage and de-duplication."
    },
    {
      "name": "Branding",
      "description": "Church brand profiles and the code-rendered brand cards (combined or per-section colors/logos/fonts) handed to the image-gen model."
    },
    {
      "name": "QC",
      "description": "Objective pre-send quality gate for a task's concepts or final files (brief compliance, spelling, deliverable coverage, video review)."
    },
    {
      "name": "Design Generation",
      "description": "Generate and refine designs — the core image-generation calls (brand-DNA-aware craft, render, self-grade, reroll)."
    },
    {
      "name": "Deliverables",
      "description": "Derive deliverables from a generated design: resize, resize an upload, background plate, retitle."
    },
    {
      "name": "Conversations",
      "description": "Design threads — list, fetch, rename, archive, title, and pick the selected source."
    },
    {
      "name": "Prompt Tools",
      "description": "Prompt assistance — clarify a brief, coach a prompt, and suggest refinements."
    },
    {
      "name": "Feedback & Ratings",
      "description": "Rate generations and references, log feedback and behavior signals, and capture reroll preference."
    },
    {
      "name": "Style Memory",
      "description": "An account's distilled style preferences — read the current memory or rebuild it."
    },
    {
      "name": "Usage & Limits",
      "description": "Per-user usage against the daily cap, and assigning an account to a tier."
    },
    {
      "name": "Engagement",
      "description": "Wait-game score submission and leaderboards shown during long renders."
    },
    {
      "name": "Variants",
      "description": "Cluster near-duplicate references by image-embedding similarity and cull each group to the craft-best."
    },
    {
      "name": "Import",
      "description": "Bulk import references from external sources (Slack favorites, Remix, Dropbox)."
    },
    {
      "name": "Moodboards",
      "description": "Task briefings — partner-aware mood boards and a designer message rendered as board images."
    }
  ]
}