{
  "openapi": "3.0.3",
  "info": {
    "title": "Kontraktr Public API",
    "description": "The Kontraktr Public API powers the customer portal, job submission form, QR code scanner, art marketplace, and quote calculator. These are the same endpoints used by the Kontraktr web app — no separate API layer required.\n\n## Authentication\n\nKontraktr uses three authentication patterns:\n\n- **Slug auth** — The customer's unique portal slug (e.g. `fuzed-labs`) is embedded in the URL path and acts as a read/write credential scoped to that customer's data. No API key required.\n- **Portal JWT** — Optional HTTP-only cookie session (`portal_session`) issued after customer login. Required only for portal auth endpoints.\n- **No auth** — Fully public endpoints (marketplace browse, QR scan, quote calculator). Rate-limited at the platform level.\n\n## Rate Limits\n\n| Endpoint | Limit | Behavior |\n|---|---|---|\n| `POST /api/public/submit` | 5 req/min per IP | `429` + `Retry-After` header |\n| `POST /api/public/portal/auth/login` | 10 req/min per IP | Account locks 15 min after 5 failures |\n| All other public endpoints | 60 req/min per IP | `429` returned |\n\n## Errors\n\nAll errors return JSON with an `error` or `message` field and an appropriate HTTP status code.\n\n```json\n{ \"error\": \"Slug not found\" }\n```",
    "version": "1.0.0",
    "contact": {
      "name": "Kontraktr Support",
      "email": "support@kontraktr.io",
      "url": "https://app.kontraktr.io/public/api-reference"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://app.kontraktr.io/public/terms"
    }
  },
  "servers": [
    {
      "url": "https://app.kontraktr.io",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Customer Portal",
      "description": "Slug-based public endpoints powering the customer portal. The portal slug acts as authentication, scoping all access to a single customer."
    },
    {
      "name": "Portal Authentication",
      "description": "Optional JWT-based login for customer portal accounts. Slug routes work without authentication."
    },
    {
      "name": "Job Submission",
      "description": "Endpoints used by the customer-facing quote submission form. Upload files first, then submit the full job."
    },
    {
      "name": "QR Code Scanning",
      "description": "Public job lookup by job number. Used by the Kontraktr QR scanner."
    },
    {
      "name": "Art Marketplace",
      "description": "Public art template marketplace. Browse and search published designs without authentication."
    },
    {
      "name": "Quote Calculator",
      "description": "Public pricing endpoint returning the full pricing matrix and optional quote calculations."
    },
    {
      "name": "Discount Codes",
      "description": "Validate discount codes during checkout without exposing the full code database."
    }
  ],
  "paths": {
    "/api/public/portal/{slug}": {
      "get": {
        "tags": [
          "Customer Portal"
        ],
        "operationId": "getPortalOverview",
        "summary": "Portal overview",
        "description": "Returns the customer's profile, job/invoice/thread counts, and shop branding. This is the first call made when a customer visits their portal link.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The customer's unique portal slug (e.g. fuzed-labs)",
            "schema": {
              "type": "string",
              "example": "fuzed-labs"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Customer portal overview",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortalOverview"
                },
                "example": {
                  "customer": {
                    "id": "cus_01HXYZ",
                    "companyName": "Fuzed Labs",
                    "contactName": "Jane Smith",
                    "email": "jane@fuzedlabs.com",
                    "phone": "555-555-0100"
                  },
                  "counts": {
                    "jobs": 14,
                    "activeJobs": 3,
                    "invoices": 8,
                    "openInvoices": 1,
                    "threads": 5,
                    "awaitingApproval": 1
                  },
                  "shopInfo": {
                    "name": "Kontraktr Print Co.",
                    "email": "hello@printco.com",
                    "phone": "555-555-0199",
                    "website": "https://printco.com"
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/public/portal/{slug}/jobs": {
      "get": {
        "tags": [
          "Customer Portal"
        ],
        "operationId": "listPortalJobs",
        "summary": "List customer jobs",
        "description": "Returns all jobs for the customer, newest first. Includes primary mockup thumbnail, pending approval flag, linked invoice summary, and tracking numbers.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Customer portal slug",
            "schema": {
              "type": "string",
              "example": "fuzed-labs"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of jobs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "jobs": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/JobSummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/portal/{slug}/jobs/{jobId}": {
      "get": {
        "tags": [
          "Customer Portal"
        ],
        "operationId": "getPortalJob",
        "summary": "Job detail",
        "description": "Full job record with mockups, print locations (including ink colors), status history, attached files (artwork/films excluded), message threads, and tracking packages.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "fuzed-labs"
            }
          },
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "description": "Internal job ID",
            "schema": {
              "type": "string",
              "example": "job_01HXYZ"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Full job detail",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobDetail"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/portal/{slug}/jobs/{jobId}/approve": {
      "post": {
        "tags": [
          "Customer Portal"
        ],
        "operationId": "submitMockupApproval",
        "summary": "Submit mockup approval",
        "description": "Approve, decline, or request revisions on job mockups. Can target a single mockup by ID or apply to all pending mockups on the job. Automatically transitions job status when all mockups are decided.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApprovalRequest"
              },
              "example": {
                "action": "approve",
                "feedback": "Looks great!",
                "senderName": "Jane Smith",
                "mockupId": "mku_01"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Approval recorded",
            "content": {
              "application/json": {
                "example": {
                  "success": true,
                  "action": "approve",
                  "status": "APPROVED"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/portal/{slug}/invoices": {
      "get": {
        "tags": [
          "Customer Portal"
        ],
        "operationId": "listPortalInvoices",
        "summary": "List invoices",
        "description": "Returns all non-draft, non-void invoices for the customer. Includes totals, status, due date, and linked job names. Also returns a stripeEnabled flag.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "fuzed-labs"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Invoice list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invoices": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/InvoiceSummary"
                      }
                    },
                    "stripeEnabled": {
                      "type": "boolean",
                      "description": "Whether the shop has Stripe configured for online payments"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/portal/{slug}/invoices/{invoiceId}/checkout": {
      "post": {
        "tags": [
          "Customer Portal"
        ],
        "operationId": "createStripeCheckout",
        "summary": "Create Stripe checkout session",
        "description": "Creates a Stripe Checkout session for an invoice and returns the hosted payment URL. Only works when Stripe is configured and the invoice is in SENT or OVERDUE status.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "invoiceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Stripe checkout URL",
            "content": {
              "application/json": {
                "example": {
                  "url": "https://checkout.stripe.com/pay/cs_test_abc123..."
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "description": "Stripe not configured on this account",
            "content": {
              "application/json": {
                "example": {
                  "error": "Stripe is not configured"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/portal/{slug}/threads": {
      "get": {
        "tags": [
          "Customer Portal"
        ],
        "operationId": "listPortalThreads",
        "summary": "List message threads",
        "description": "Returns all message threads visible to this customer, ordered by most recently updated.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Thread list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "threads": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ThreadSummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "Customer Portal"
        ],
        "operationId": "createPortalThread",
        "summary": "Create message thread",
        "description": "Opens a new message thread from the customer. Optionally linked to a specific job. Staff are notified immediately.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subject",
                  "body",
                  "senderName"
                ],
                "properties": {
                  "subject": {
                    "type": "string",
                    "example": "Question about sleeve print"
                  },
                  "body": {
                    "type": "string",
                    "example": "Can we move the sleeve print 2 inches up?"
                  },
                  "senderName": {
                    "type": "string",
                    "example": "Jane Smith"
                  },
                  "jobId": {
                    "type": "string",
                    "description": "Optional — links thread to a specific job",
                    "example": "job_01HXYZ"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Thread created",
            "content": {
              "application/json": {
                "example": {
                  "id": "thr_02",
                  "subject": "Question about sleeve print",
                  "customerId": "cus_01HXYZ",
                  "jobId": "job_01HXYZ",
                  "messages": [
                    {
                      "id": "msg_01",
                      "senderName": "Jane Smith",
                      "senderType": "CUSTOMER",
                      "body": "Can we move the sleeve print 2 inches up?",
                      "createdAt": "2025-03-20T10:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/portal/{slug}/jobs/{jobId}/tracking": {
      "get": {
        "tags": [
          "Customer Portal"
        ],
        "operationId": "getTrackingPackages",
        "summary": "Get tracking packages",
        "description": "Returns all tracking packages associated with a job, ordered newest first.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tracking packages",
            "content": {
              "application/json": {
                "example": {
                  "packages": [
                    {
                      "id": "pkg_01",
                      "trackingNumber": "1Z999AA10123456784",
                      "carrier": "UPS",
                      "addedBy": "jane@fuzedlabs.com",
                      "notes": "Fragile",
                      "addedAt": "2025-03-21T08:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "Customer Portal"
        ],
        "operationId": "addTrackingNumbers",
        "summary": "Add tracking numbers",
        "description": "Customer submits tracking numbers for a job. Carrier is auto-detected if not specified. Deduplicates against existing packages. Notifies staff.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "entries"
                ],
                "properties": {
                  "entries": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "trackingNumber"
                      ],
                      "properties": {
                        "trackingNumber": {
                          "type": "string"
                        },
                        "carrier": {
                          "type": "string",
                          "enum": [
                            "UPS",
                            "FEDEX",
                            "USPS",
                            "DHL"
                          ],
                          "description": "Auto-detected if omitted"
                        },
                        "notes": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "submitterEmail": {
                    "type": "string",
                    "format": "email"
                  }
                }
              },
              "example": {
                "entries": [
                  {
                    "trackingNumber": "1Z999AA10123456784",
                    "carrier": "UPS",
                    "notes": "2 boxes"
                  },
                  {
                    "trackingNumber": "9400111899223397210800"
                  }
                ],
                "submitterEmail": "jane@fuzedlabs.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Packages added",
            "content": {
              "application/json": {
                "example": {
                  "packages": [
                    {
                      "id": "pkg_01",
                      "trackingNumber": "1Z999AA10123456784",
                      "carrier": "UPS",
                      "addedBy": "jane@fuzedlabs.com",
                      "notes": "2 boxes",
                      "addedAt": "2025-03-21T08:00:00.000Z"
                    }
                  ],
                  "added": 1
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/portal/auth/login": {
      "post": {
        "tags": [
          "Portal Authentication"
        ],
        "operationId": "portalLogin",
        "summary": "Customer login",
        "description": "Authenticates a customer portal user. Returns a JWT set as an HTTP-only `portal_session` cookie. Rate-limited to 10 attempts per minute per IP. Accounts lock for 15 minutes after 5 failed attempts.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "password"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "email": "jane@fuzedlabs.com",
                "password": "correct-password"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Login successful — sets portal_session cookie",
            "headers": {
              "Set-Cookie": {
                "description": "HTTP-only session cookie",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "success": true,
                  "user": {
                    "id": "usr_01",
                    "name": "Jane Smith",
                    "email": "jane@fuzedlabs.com",
                    "customerId": "cus_01HXYZ",
                    "customerName": "Fuzed Labs",
                    "portalSlug": "fuzed-labs"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/public/portal/auth/logout": {
      "post": {
        "tags": [
          "Portal Authentication"
        ],
        "operationId": "portalLogout",
        "summary": "Customer logout",
        "description": "Clears the customer session cookie.",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Logged out",
            "content": {
              "application/json": {
                "example": {
                  "success": true
                }
              }
            }
          }
        }
      }
    },
    "/api/public/portal/auth/me": {
      "get": {
        "tags": [
          "Portal Authentication"
        ],
        "operationId": "portalMe",
        "summary": "Current session",
        "description": "Returns the currently authenticated customer user. Useful for restoring session on page load.",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Current session user",
            "content": {
              "application/json": {
                "example": {
                  "id": "usr_01",
                  "name": "Jane Smith",
                  "email": "jane@fuzedlabs.com",
                  "customerId": "cus_01HXYZ"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/public/submit/files": {
      "post": {
        "tags": [
          "Job Submission"
        ],
        "operationId": "uploadSubmissionFile",
        "summary": "Upload submission file",
        "description": "Uploads a file before the job exists using a temporary submission token. Accepts multipart/form-data. Max 25MB. Returns the created file record.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file",
                  "submissionToken"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The file to upload (max 25MB)"
                  },
                  "submissionToken": {
                    "type": "string",
                    "description": "Temporary UUID generated client-side to group files before job creation"
                  },
                  "category": {
                    "type": "string",
                    "description": "File category (default: CUSTOMER_SUBMITTED)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "File uploaded",
            "content": {
              "application/json": {
                "example": {
                  "id": "fil_01",
                  "fileName": "artwork-front.pdf",
                  "fileUrl": "/uploads/submissions/tok_abc123/artwork-front.pdf",
                  "fileSize": 2097152,
                  "mimeType": "application/pdf",
                  "category": "CUSTOMER_SUBMITTED",
                  "submissionToken": "tok_abc123",
                  "jobId": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Job Submission"
        ],
        "operationId": "deleteSubmissionFile",
        "summary": "Delete submission file",
        "description": "Removes a file uploaded during the submission flow. Requires the submission token for ownership verification.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "fileId": "fil_01",
                "submissionToken": "tok_abc123"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "File deleted",
            "content": {
              "application/json": {
                "example": {
                  "success": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/submit": {
      "post": {
        "tags": [
          "Job Submission"
        ],
        "operationId": "submitJob",
        "summary": "Submit quote / new job",
        "description": "Creates a full job record from customer-submitted data. Links files uploaded with the submission token, creates print locations and garment groups. Sends confirmation email and notifies admins. Rate-limited to 5 submissions per minute per IP.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JobSubmissionRequest"
              },
              "example": {
                "customerId": "cus_01HXYZ",
                "submitterEmail": "jane@fuzedlabs.com",
                "submissionToken": "tok_abc123",
                "title": "Spring '25 Event Tees",
                "poNumber": "PO-2025-003",
                "dueDate": "2025-04-15",
                "deliveryType": "Pickup",
                "customerNotes": "Rush if possible!",
                "groups": [
                  {
                    "groupNumber": 1,
                    "enabledSizes": [
                      "S",
                      "M",
                      "L",
                      "XL"
                    ],
                    "printLocations": [
                      {
                        "locationName": "Front Center",
                        "artworkWidth": 12,
                        "artworkHeight": 14,
                        "numberOfColors": 3
                      }
                    ],
                    "lineItems": [
                      {
                        "garmentDesc": "Gildan 5000",
                        "garmentColor": "Black",
                        "qty_S": 12,
                        "qty_M": 24,
                        "qty_L": 36,
                        "qty_XL": 12
                      }
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Job created",
            "content": {
              "application/json": {
                "example": {
                  "success": true,
                  "jobNumber": "J-1042",
                  "jobId": "job_01HXYZ",
                  "portalSlug": "fuzed-labs"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/public/scan/{code}": {
      "get": {
        "tags": [
          "QR Code Scanning"
        ],
        "operationId": "scanQrCode",
        "summary": "QR code lookup",
        "description": "Looks up a job by its job number (encoded in the QR code). Returns basic job info and the customer's portal slug for routing decisions on the scan landing page.",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "description": "URL-encoded job number (e.g. J-1042)",
            "schema": {
              "type": "string",
              "example": "J-1042"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Job found",
            "content": {
              "application/json": {
                "example": {
                  "id": "job_01HXYZ",
                  "jobNumber": "J-1042",
                  "title": "Spring '25 Event Tees",
                  "status": "IN_PRODUCTION",
                  "priority": "RUSH",
                  "dueDate": "2025-04-15T00:00:00.000Z",
                  "totalQuantity": 144,
                  "receivingStatus": "RECEIVED",
                  "customer": {
                    "companyName": "Fuzed Labs",
                    "publicSlug": "fuzed-labs"
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/marketplace": {
      "get": {
        "tags": [
          "Art Marketplace"
        ],
        "operationId": "listMarketplaceDesigns",
        "summary": "Browse designs",
        "description": "Returns paginated published marketplace designs with category listings. Does not include canvasJson (full design data) — that requires purchase.",
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by category slug (e.g. vintage, sports)"
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Full-text search across name, description, and tags"
          },
          {
            "name": "featured",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Set to true to return only featured designs"
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Design list with categories and pagination",
            "content": {
              "application/json": {
                "example": {
                  "designs": [
                    {
                      "id": "des_01",
                      "name": "Retro Wave",
                      "description": "80s-inspired wave design",
                      "previewImageUrl": "/marketplace/retro-wave-preview.png",
                      "garmentType": "T-Shirt",
                      "printLocation": "Front Center",
                      "tags": [
                        "retro",
                        "80s",
                        "wave"
                      ],
                      "price": 0,
                      "isFeatured": true,
                      "downloadCount": 48,
                      "category": {
                        "id": "cat_01",
                        "name": "Vintage",
                        "slug": "vintage",
                        "iconEmoji": "🎞️"
                      }
                    }
                  ],
                  "categories": [
                    {
                      "id": "cat_01",
                      "name": "Vintage",
                      "slug": "vintage",
                      "iconEmoji": "🎞️",
                      "_count": {
                        "designs": 4
                      }
                    }
                  ],
                  "pagination": {
                    "page": 1,
                    "limit": 20,
                    "total": 20,
                    "totalPages": 1
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/public/marketplace/{id}": {
      "get": {
        "tags": [
          "Art Marketplace"
        ],
        "operationId": "getMarketplaceDesign",
        "summary": "Design detail",
        "description": "Returns full metadata for a single marketplace design. canvasJson is excluded — only accessible via the authenticated purchase endpoint.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "des_01"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Design detail",
            "content": {
              "application/json": {
                "example": {
                  "id": "des_01",
                  "name": "Retro Wave",
                  "description": "80s-inspired wave design",
                  "previewImageUrl": "/marketplace/retro-wave-preview.png",
                  "garmentType": "T-Shirt",
                  "printLocation": "Front Center",
                  "tags": [
                    "retro",
                    "80s",
                    "wave"
                  ],
                  "price": 0,
                  "isFeatured": true,
                  "downloadCount": 48,
                  "category": {
                    "id": "cat_01",
                    "name": "Vintage",
                    "slug": "vintage",
                    "iconEmoji": "🎞️"
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/quote-calculator": {
      "get": {
        "tags": [
          "Quote Calculator"
        ],
        "operationId": "getQuoteCalculator",
        "summary": "Get pricing matrix / calculate quote",
        "description": "Returns pricing tiers for screen printing or embroidery. Pass quantity + colors (or stitchCount for embroidery) to get a specific quote. Optionally scoped to a customer's pricing matrix via their portal slug.",
        "parameters": [
          {
            "name": "method",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "screen_print",
                "embroidery"
              ],
              "default": "screen_print"
            }
          },
          {
            "name": "slug",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Customer portal slug — uses customer-specific matrix if assigned"
          },
          {
            "name": "quantity",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Number of pieces to quote"
          },
          {
            "name": "colors",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Number of ink colors (screen print)"
          },
          {
            "name": "locations",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            },
            "description": "Number of print locations"
          },
          {
            "name": "stitchCount",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Stitch count for embroidery quotes"
          }
        ],
        "responses": {
          "200": {
            "description": "Pricing matrix and optional quote",
            "content": {
              "application/json": {
                "example": {
                  "matrix": {
                    "id": "mat_01",
                    "name": "Standard",
                    "source": "default",
                    "maxColors": 8
                  },
                  "tiers": [
                    {
                      "minQuantity": 12,
                      "color1": 8.5,
                      "color2": 9.25,
                      "color3": 10,
                      "color4": 10.75
                    },
                    {
                      "minQuantity": 24,
                      "color1": 7,
                      "color2": 7.75,
                      "color3": 8.5,
                      "color4": 9.25
                    },
                    {
                      "minQuantity": 72,
                      "color1": 5.5,
                      "color2": 6.25,
                      "color3": 7,
                      "color4": 7.75
                    }
                  ],
                  "addons": [
                    {
                      "id": "addon_1",
                      "name": "Color Change Fee",
                      "fee": 15,
                      "feeType": "flat"
                    }
                  ],
                  "locationPresets": [
                    "Front Center",
                    "Back Full",
                    "Left Chest",
                    "Right Sleeve"
                  ],
                  "quote": {
                    "quantity": 48,
                    "colors": 2,
                    "locations": 1,
                    "tierUsed": 24,
                    "pricePerImpression": 7.75,
                    "totalImpressions": 48,
                    "impressionCost": 372,
                    "pricePerPiece": 7.75
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/discount-codes/validate": {
      "post": {
        "tags": [
          "Discount Codes"
        ],
        "operationId": "validateDiscountCode",
        "summary": "Validate discount code",
        "description": "Checks a discount code for validity. Returns the discount type and value if valid, or a human-readable reason if not. Does not consume (use) the code — that happens on job creation.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "example": "SPRING25"
                  },
                  "customerId": {
                    "type": "string",
                    "description": "Optional — enables customer-specific code restrictions",
                    "example": "cus_01HXYZ"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation result",
            "content": {
              "application/json": {
                "examples": {
                  "valid": {
                    "summary": "Valid code",
                    "value": {
                      "valid": true,
                      "type": "PERCENTAGE",
                      "value": 10,
                      "description": "10% off spring orders",
                      "minOrderAmount": null
                    }
                  },
                  "invalid": {
                    "summary": "Invalid code",
                    "value": {
                      "valid": false,
                      "reason": "Code has expired"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "slugAuth": {
        "type": "apiKey",
        "in": "path",
        "name": "slug",
        "description": "The customer's unique portal slug embedded in the URL path. Acts as a read/write credential scoped to a single customer."
      },
      "cookieAuth": {
        "type": "apiKey",
        "in": "cookie",
        "name": "portal_session",
        "description": "HTTP-only JWT cookie issued by POST /api/public/portal/auth/login"
      }
    },
    "schemas": {
      "PortalOverview": {
        "type": "object",
        "properties": {
          "customer": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "companyName": {
                "type": "string"
              },
              "contactName": {
                "type": "string"
              },
              "email": {
                "type": "string",
                "format": "email"
              },
              "phone": {
                "type": "string"
              }
            }
          },
          "counts": {
            "type": "object",
            "properties": {
              "jobs": {
                "type": "integer"
              },
              "activeJobs": {
                "type": "integer",
                "description": "Jobs not in COMPLETED_ORDER or COMPLETED_PAID"
              },
              "invoices": {
                "type": "integer"
              },
              "openInvoices": {
                "type": "integer",
                "description": "Invoices in SENT or OVERDUE status"
              },
              "threads": {
                "type": "integer"
              },
              "awaitingApproval": {
                "type": "integer",
                "description": "Jobs in ART_APPROVAL_SENT status"
              }
            }
          },
          "shopInfo": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "email": {
                "type": "string"
              },
              "phone": {
                "type": "string"
              },
              "website": {
                "type": "string"
              }
            }
          }
        }
      },
      "JobSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "jobNumber": {
            "type": "string",
            "example": "J-1042"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "See Job Statuses reference"
          },
          "priority": {
            "type": "string",
            "enum": [
              "NORMAL",
              "RUSH",
              "HOT_RUSH"
            ]
          },
          "dueDate": {
            "type": "string",
            "format": "date-time"
          },
          "totalQuantity": {
            "type": "integer"
          },
          "hasPendingApproval": {
            "type": "boolean"
          },
          "hasBagging": {
            "type": "boolean"
          },
          "fileCount": {
            "type": "integer"
          },
          "totalMockups": {
            "type": "integer"
          },
          "primaryMockup": {
            "type": "object",
            "nullable": true,
            "properties": {
              "thumbnailUrl": {
                "type": "string"
              },
              "imageUrl": {
                "type": "string"
              }
            }
          },
          "invoice": {
            "type": "object",
            "nullable": true,
            "properties": {
              "id": {
                "type": "string"
              },
              "invoiceNumber": {
                "type": "string"
              },
              "status": {
                "type": "string"
              },
              "total": {
                "type": "number"
              }
            }
          }
        }
      },
      "JobDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "jobNumber": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string"
          },
          "priority": {
            "type": "string"
          },
          "dueDate": {
            "type": "string",
            "format": "date-time"
          },
          "totalQuantity": {
            "type": "integer"
          },
          "poNumber": {
            "type": "string",
            "nullable": true
          },
          "deliveryType": {
            "type": "string",
            "nullable": true
          },
          "hasBagging": {
            "type": "boolean"
          },
          "mockups": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "imageUrl": {
                  "type": "string"
                },
                "thumbnailUrl": {
                  "type": "string"
                },
                "garmentColor": {
                  "type": "string"
                },
                "isPrimary": {
                  "type": "boolean"
                },
                "approvalStatus": {
                  "type": "string",
                  "enum": [
                    "PENDING",
                    "SENT",
                    "APPROVED",
                    "DECLINED",
                    "REVISION_REQUESTED"
                  ]
                },
                "approvalFeedback": {
                  "type": "string",
                  "nullable": true
                }
              }
            }
          },
          "printLocations": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "locationName": {
                  "type": "string"
                },
                "numberOfColors": {
                  "type": "integer"
                },
                "artworkWidth": {
                  "type": "number"
                },
                "artworkHeight": {
                  "type": "number"
                },
                "inks": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "pantoneCode": {
                        "type": "string"
                      },
                      "colorName": {
                        "type": "string"
                      },
                      "pantoneHex": {
                        "type": "string"
                      },
                      "inkType": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "statusHistory": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "fromStatus": {
                  "type": "string",
                  "nullable": true
                },
                "toStatus": {
                  "type": "string"
                },
                "changedAt": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          },
          "files": {
            "type": "array"
          },
          "threads": {
            "type": "array"
          },
          "trackingPackages": {
            "type": "array"
          }
        }
      },
      "InvoiceSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "invoiceNumber": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "SENT",
              "OVERDUE",
              "PAID",
              "PARTIAL"
            ]
          },
          "subtotal": {
            "type": "number"
          },
          "taxAmount": {
            "type": "number"
          },
          "total": {
            "type": "number"
          },
          "creditApplied": {
            "type": "number"
          },
          "dueDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "paidDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "itemCount": {
            "type": "integer"
          },
          "jobs": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "jobName": {
                  "type": "string"
                },
                "poNumber": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "ThreadSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "subject": {
            "type": "string"
          },
          "jobId": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "messageCount": {
            "type": "integer"
          },
          "latestMessage": {
            "type": "object",
            "properties": {
              "senderName": {
                "type": "string"
              },
              "senderType": {
                "type": "string",
                "enum": [
                  "CUSTOMER",
                  "STAFF"
                ]
              },
              "body": {
                "type": "string"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      },
      "ApprovalRequest": {
        "type": "object",
        "required": [
          "action"
        ],
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "approve",
              "decline",
              "revision"
            ]
          },
          "feedback": {
            "type": "string",
            "description": "Optional customer notes or revision instructions"
          },
          "senderName": {
            "type": "string",
            "description": "Customer's name shown in thread message"
          },
          "mockupId": {
            "type": "string",
            "description": "Target a single mockup. Omit to apply to all SENT mockups"
          }
        }
      },
      "JobSubmissionRequest": {
        "type": "object",
        "required": [
          "customerId",
          "title",
          "poNumber",
          "dueDate",
          "groups"
        ],
        "properties": {
          "customerId": {
            "type": "string"
          },
          "submitterEmail": {
            "type": "string",
            "format": "email"
          },
          "submissionToken": {
            "type": "string",
            "description": "Token from file uploads — links pre-uploaded files to this job"
          },
          "title": {
            "type": "string"
          },
          "poNumber": {
            "type": "string"
          },
          "dueDate": {
            "type": "string",
            "format": "date"
          },
          "deliveryType": {
            "type": "string",
            "enum": [
              "Pickup",
              "Shipping"
            ]
          },
          "customerNotes": {
            "type": "string"
          },
          "groups": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object",
              "required": [
                "groupNumber",
                "enabledSizes",
                "printLocations",
                "lineItems"
              ],
              "properties": {
                "groupNumber": {
                  "type": "integer"
                },
                "enabledSizes": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "enum": [
                      "XS",
                      "S",
                      "M",
                      "L",
                      "XL",
                      "2XL",
                      "3XL",
                      "4XL",
                      "5XL"
                    ]
                  }
                },
                "printLocations": {
                  "type": "array",
                  "minItems": 1,
                  "items": {
                    "type": "object",
                    "required": [
                      "locationName",
                      "artworkWidth",
                      "artworkHeight",
                      "numberOfColors"
                    ],
                    "properties": {
                      "locationName": {
                        "type": "string"
                      },
                      "artworkWidth": {
                        "type": "number",
                        "description": "In inches"
                      },
                      "artworkHeight": {
                        "type": "number",
                        "description": "In inches"
                      },
                      "numberOfColors": {
                        "type": "integer"
                      }
                    }
                  }
                },
                "lineItems": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "garmentDesc": {
                        "type": "string"
                      },
                      "garmentColor": {
                        "type": "string"
                      },
                      "qty_XS": {
                        "type": "integer"
                      },
                      "qty_S": {
                        "type": "integer"
                      },
                      "qty_M": {
                        "type": "integer"
                      },
                      "qty_L": {
                        "type": "integer"
                      },
                      "qty_XL": {
                        "type": "integer"
                      },
                      "qty_2XL": {
                        "type": "integer"
                      },
                      "qty_3XL": {
                        "type": "integer"
                      },
                      "qty_4XL": {
                        "type": "integer"
                      },
                      "qty_5XL": {
                        "type": "integer"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Bad request — missing or invalid parameters",
        "content": {
          "application/json": {
            "example": {
              "error": "Missing required field: title"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Authentication required or credentials invalid",
        "content": {
          "application/json": {
            "example": {
              "error": "Invalid credentials"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "example": {
              "error": "Not found"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "example": {
              "error": "Rate limit exceeded. Try again in 60 seconds."
            }
          }
        }
      },
      "ServerError": {
        "description": "Internal server error",
        "content": {
          "application/json": {
            "example": {
              "error": "Internal server error"
            }
          }
        }
      }
    }
  }
}