{"openapi":"3.0.0","info":{"title":"Xtract API","version":"0.1.0","description":"The shared API for Xtract applications. Includes protected business-portal and platform-admin session checks, roasters, coffees, users, and role management. Auth endpoints require configured Supabase Auth and Postgres services."},"servers":[{"url":"/","description":"Current API host"}],"tags":[{"name":"Portal","description":"Scoped Brand and Roaster access and live-session checks"},{"name":"System","description":"Service diagnostics"},{"name":"Catalogue","description":"Active public coffee and roaster catalogue"},{"name":"Profiles","description":"Verified mobile profiles, onboarding and privacy"},{"name":"Admin","description":"Platform-admin access, roasters, coffees, users, and role management"}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}},"schemas":{"Health":{"type":"object","properties":{"status":{"type":"string","enum":["ok"],"description":"The HTTP service is responding.","example":"ok"},"service":{"type":"string","enum":["xtract-api"],"description":"Service identifier.","example":"xtract-api"}},"required":["status","service"]}},"parameters":{}},"paths":{"/admin/roasters":{"get":{"operationId":"listAdminRoasters","summary":"List, filter, and sort roasters","description":"Returns up to 50 roasters, searchable by name, slug, or contact email, filterable by status, and sorted by an allowlisted field. Requires current platform-admin membership and MFA.","tags":["Admin"],"security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100000,"default":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"string","maxLength":100,"default":""},"required":false,"name":"q","in":"query"},{"schema":{"type":"string","enum":["all","active","inactive"],"default":"all"},"required":false,"name":"status","in":"query"},{"schema":{"type":"string","enum":["name_asc","name_desc","newest","oldest","updated"],"default":"name_asc"},"required":false,"name":"sort","in":"query"}],"responses":{"200":{"description":"A page of roasters and pagination metadata","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"contact_email":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"address":{"type":"string","nullable":true},"website_url":{"type":"string","nullable":true},"shop_url":{"type":"string","nullable":true},"instagram_url":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"logo_url":{"type":"string","nullable":true},"is_active":{"type":"boolean"},"created_by":{"type":"string","nullable":true,"format":"uuid"},"updated_by":{"type":"string","nullable":true,"format":"uuid"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","slug","contact_email","country","address","website_url","shop_url","instagram_url","description","logo_url","is_active","created_by","updated_by","created_at","updated_at"]}},"total":{"type":"integer","minimum":0},"page":{"type":"integer","minimum":0,"exclusiveMinimum":true},"pageSize":{"type":"number","enum":[50]}},"required":["items","total","page","pageSize"]}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Not an MFA-verified platform administrator","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Admin request limit exceeded; retry after 60 seconds","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Access verification is unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"post":{"operationId":"createAdminRoaster","summary":"Create a roaster","description":"Creates a roaster and attributes the write to the authenticated platform administrator.","tags":["Admin"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":160},"slug":{"type":"string","minLength":1,"maxLength":160,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","description":"Optional exact slug override. Lowercase letters, numbers and single hyphens. Omit to generate on creation or preserve on update; a duplicate override returns 409."},"contact_email":{"type":"string","nullable":true,"maxLength":254,"format":"email"},"country":{"type":"string","nullable":true,"pattern":"^[A-Z]{2}$","description":"Two-letter country code, for example GB. Null clears the country; omission preserves it on update."},"address":{"type":"string","nullable":true,"maxLength":1000,"description":"Public business postal address. Null clears the address; omission preserves it on update."},"website_url":{"type":"string","nullable":true,"maxLength":2048,"format":"uri"},"shop_url":{"type":"string","nullable":true,"maxLength":2048,"format":"uri"},"instagram_url":{"type":"string","nullable":true,"maxLength":2048,"format":"uri"},"description":{"type":"string","nullable":true,"maxLength":4000},"logo_url":{"type":"string","nullable":true,"maxLength":2048,"format":"uri"},"is_active":{"type":"boolean"}},"required":["name","contact_email","website_url","shop_url","instagram_url","description","logo_url","is_active"],"additionalProperties":false}}}},"responses":{"201":{"description":"Created roaster","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"contact_email":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"address":{"type":"string","nullable":true},"website_url":{"type":"string","nullable":true},"shop_url":{"type":"string","nullable":true},"instagram_url":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"logo_url":{"type":"string","nullable":true},"is_active":{"type":"boolean"},"created_by":{"type":"string","nullable":true,"format":"uuid"},"updated_by":{"type":"string","nullable":true,"format":"uuid"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","slug","contact_email","country","address","website_url","shop_url","instagram_url","description","logo_url","is_active","created_by","updated_by","created_at","updated_at"]}}}},"400":{"description":"Invalid roaster details","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Not an MFA-verified platform administrator","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"A roaster already uses this slug","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Admin request limit exceeded; retry after 60 seconds","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Roaster creation failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/roasters/summary":{"get":{"operationId":"getAdminRoasterCounts","summary":"Count active and inactive roasters","tags":["Admin"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Roaster totals by status","content":{"application/json":{"schema":{"type":"object","properties":{"active":{"type":"integer","minimum":0},"inactive":{"type":"integer","minimum":0},"total":{"type":"integer","minimum":0}},"required":["active","inactive","total"]}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Not an MFA-verified platform administrator","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Admin request limit exceeded; retry after 60 seconds","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Access verification is unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/roasters/{id}":{"get":{"operationId":"getAdminRoaster","summary":"Get a roaster for editing","tags":["Admin"],"security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Roaster details","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"contact_email":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"address":{"type":"string","nullable":true},"website_url":{"type":"string","nullable":true},"shop_url":{"type":"string","nullable":true},"instagram_url":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"logo_url":{"type":"string","nullable":true},"is_active":{"type":"boolean"},"created_by":{"type":"string","nullable":true,"format":"uuid"},"updated_by":{"type":"string","nullable":true,"format":"uuid"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","slug","contact_email","country","address","website_url","shop_url","instagram_url","description","logo_url","is_active","created_by","updated_by","created_at","updated_at"]}}}},"400":{"description":"Invalid roaster ID","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Not an MFA-verified platform administrator","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Roaster not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Admin request limit exceeded; retry after 60 seconds","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Roaster lookup failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"patch":{"operationId":"updateAdminRoaster","summary":"Update a roaster","description":"Updates the editable roaster fields and records the authenticated administrator in the audit trigger.","tags":["Admin"],"security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":160},"slug":{"type":"string","minLength":1,"maxLength":160,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","description":"Optional exact slug override. Lowercase letters, numbers and single hyphens. Omit to generate on creation or preserve on update; a duplicate override returns 409."},"contact_email":{"type":"string","nullable":true,"maxLength":254,"format":"email"},"country":{"type":"string","nullable":true,"pattern":"^[A-Z]{2}$","description":"Two-letter country code, for example GB. Null clears the country; omission preserves it on update."},"address":{"type":"string","nullable":true,"maxLength":1000,"description":"Public business postal address. Null clears the address; omission preserves it on update."},"website_url":{"type":"string","nullable":true,"maxLength":2048,"format":"uri"},"shop_url":{"type":"string","nullable":true,"maxLength":2048,"format":"uri"},"instagram_url":{"type":"string","nullable":true,"maxLength":2048,"format":"uri"},"description":{"type":"string","nullable":true,"maxLength":4000},"logo_url":{"type":"string","nullable":true,"maxLength":2048,"format":"uri"},"is_active":{"type":"boolean"}},"required":["name","contact_email","website_url","shop_url","instagram_url","description","logo_url","is_active"],"additionalProperties":false}}}},"responses":{"200":{"description":"Updated roaster","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"contact_email":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"address":{"type":"string","nullable":true},"website_url":{"type":"string","nullable":true},"shop_url":{"type":"string","nullable":true},"instagram_url":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"logo_url":{"type":"string","nullable":true},"is_active":{"type":"boolean"},"created_by":{"type":"string","nullable":true,"format":"uuid"},"updated_by":{"type":"string","nullable":true,"format":"uuid"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","slug","contact_email","country","address","website_url","shop_url","instagram_url","description","logo_url","is_active","created_by","updated_by","created_at","updated_at"]}}}},"400":{"description":"Invalid roaster ID or details","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Not an MFA-verified platform administrator","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Roaster not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"A roaster already uses this slug","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Admin request limit exceeded; retry after 60 seconds","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Roaster update failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/coffees/tasting-notes":{"get":{"operationId":"searchAdminCoffeeTastingNotes","summary":"Find tasting notes for the coffee form","description":"Searches canonical names and reviewed synonyms; returns up to 50 canonical choices. Requires current platform-admin membership and MFA.","tags":["Admin"],"security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","maxLength":100,"default":""},"required":false,"name":"q","in":"query"}],"responses":{"200":{"description":"Canonical tasting-note choices","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"group_key":{"type":"string"}},"required":["id","name","group_key"]}}},"required":["items"]}}}},"400":{"description":"Invalid search","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Not an MFA-verified platform administrator","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Admin request limit exceeded","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Tasting-note search unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/coffees":{"get":{"operationId":"listAdminCoffees","summary":"List, filter, and sort coffees","description":"Returns up to 50 coffees, searchable by coffee name, slug, origin, or roaster name, filterable by status, and sorted by an allowlisted field. Requires current platform-admin membership and MFA.","tags":["Admin"],"security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100000,"default":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"string","maxLength":100,"default":""},"required":false,"name":"q","in":"query"},{"schema":{"type":"string","enum":["all","active","inactive"],"default":"all"},"required":false,"name":"status","in":"query"},{"schema":{"type":"string","enum":["name_asc","name_desc","newest","oldest","updated"],"default":"name_asc"},"required":false,"name":"sort","in":"query"}],"responses":{"200":{"description":"A page of coffees and pagination metadata","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"roaster_id":{"type":"string","format":"uuid"},"roaster_name":{"type":"string"},"roast_level":{"type":"string","nullable":true},"origin":{"type":"string","nullable":true},"process":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"image_url":{"type":"string","nullable":true},"shop_url":{"type":"string","nullable":true},"tasting_notes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"group_key":{"type":"string"}},"required":["id","name","group_key"]}},"is_active":{"type":"boolean"},"created_by":{"type":"string","nullable":true,"format":"uuid"},"updated_by":{"type":"string","nullable":true,"format":"uuid"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","slug","roaster_id","roast_level","origin","process","description","image_url","shop_url","tasting_notes","is_active","created_by","updated_by","created_at","updated_at"]}},"total":{"type":"integer","minimum":0},"page":{"type":"integer","minimum":0,"exclusiveMinimum":true},"pageSize":{"type":"number","enum":[50]}},"required":["items","total","page","pageSize"]}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Not an MFA-verified platform administrator","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Admin request limit exceeded; retry after 60 seconds","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Access verification is unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"post":{"operationId":"createAdminCoffee","summary":"Create a coffee","description":"Creates a coffee and attributes the write to the authenticated platform administrator.","tags":["Admin"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":160},"slug":{"type":"string","minLength":1,"maxLength":160,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","description":"Optional exact slug override. Lowercase letters, numbers and single hyphens. Omit to generate on creation or preserve on update; a duplicate override returns 409."},"roaster_id":{"type":"string","format":"uuid"},"roast_level":{"type":"string","nullable":true,"maxLength":100},"origin":{"type":"string","nullable":true,"maxLength":250},"process":{"type":"string","nullable":true,"maxLength":250},"description":{"type":"string","nullable":true,"maxLength":4000},"image_url":{"type":"string","nullable":true,"maxLength":2048,"format":"uri"},"shop_url":{"type":"string","nullable":true,"maxLength":2048,"format":"uri"},"tasting_note_ids":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":32,"description":"Up to 32 canonical tasting-note UUIDs. Omit to preserve existing links; use [] to clear them."},"is_active":{"type":"boolean"}},"required":["name","roaster_id","roast_level","origin","process","description","image_url","shop_url","is_active"],"additionalProperties":false}}}},"responses":{"201":{"description":"Created coffee","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"roaster_id":{"type":"string","format":"uuid"},"roaster_name":{"type":"string"},"roast_level":{"type":"string","nullable":true},"origin":{"type":"string","nullable":true},"process":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"image_url":{"type":"string","nullable":true},"shop_url":{"type":"string","nullable":true},"tasting_notes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"group_key":{"type":"string"}},"required":["id","name","group_key"]}},"is_active":{"type":"boolean"},"created_by":{"type":"string","nullable":true,"format":"uuid"},"updated_by":{"type":"string","nullable":true,"format":"uuid"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","slug","roaster_id","roast_level","origin","process","description","image_url","shop_url","tasting_notes","is_active","created_by","updated_by","created_at","updated_at"]}}}},"400":{"description":"Invalid coffee details","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Not an MFA-verified platform administrator","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"This roaster already has a coffee with this slug","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Admin request limit exceeded; retry after 60 seconds","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Coffee creation failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/coffees/summary":{"get":{"operationId":"getAdminCoffeeCounts","summary":"Count active and inactive coffees","tags":["Admin"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Coffee totals by status","content":{"application/json":{"schema":{"type":"object","properties":{"active":{"type":"integer","minimum":0},"inactive":{"type":"integer","minimum":0},"total":{"type":"integer","minimum":0}},"required":["active","inactive","total"]}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Not an MFA-verified platform administrator","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Admin request limit exceeded; retry after 60 seconds","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Access verification is unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/coffees/{id}":{"get":{"operationId":"getAdminCoffee","summary":"Get a coffee for editing","tags":["Admin"],"security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Coffee details","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"roaster_id":{"type":"string","format":"uuid"},"roaster_name":{"type":"string"},"roast_level":{"type":"string","nullable":true},"origin":{"type":"string","nullable":true},"process":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"image_url":{"type":"string","nullable":true},"shop_url":{"type":"string","nullable":true},"tasting_notes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"group_key":{"type":"string"}},"required":["id","name","group_key"]}},"is_active":{"type":"boolean"},"created_by":{"type":"string","nullable":true,"format":"uuid"},"updated_by":{"type":"string","nullable":true,"format":"uuid"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","slug","roaster_id","roast_level","origin","process","description","image_url","shop_url","tasting_notes","is_active","created_by","updated_by","created_at","updated_at"]}}}},"400":{"description":"Invalid coffee ID","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Not an MFA-verified platform administrator","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Coffee not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Admin request limit exceeded; retry after 60 seconds","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Coffee lookup failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"patch":{"operationId":"updateAdminCoffee","summary":"Update a coffee","description":"Updates the editable coffee fields and records the authenticated administrator in the audit trigger.","tags":["Admin"],"security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":160},"slug":{"type":"string","minLength":1,"maxLength":160,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","description":"Optional exact slug override. Lowercase letters, numbers and single hyphens. Omit to generate on creation or preserve on update; a duplicate override returns 409."},"roaster_id":{"type":"string","format":"uuid"},"roast_level":{"type":"string","nullable":true,"maxLength":100},"origin":{"type":"string","nullable":true,"maxLength":250},"process":{"type":"string","nullable":true,"maxLength":250},"description":{"type":"string","nullable":true,"maxLength":4000},"image_url":{"type":"string","nullable":true,"maxLength":2048,"format":"uri"},"shop_url":{"type":"string","nullable":true,"maxLength":2048,"format":"uri"},"tasting_note_ids":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":32,"description":"Up to 32 canonical tasting-note UUIDs. Omit to preserve existing links; use [] to clear them."},"is_active":{"type":"boolean"}},"required":["name","roaster_id","roast_level","origin","process","description","image_url","shop_url","is_active"],"additionalProperties":false}}}},"responses":{"200":{"description":"Updated coffee","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"roaster_id":{"type":"string","format":"uuid"},"roaster_name":{"type":"string"},"roast_level":{"type":"string","nullable":true},"origin":{"type":"string","nullable":true},"process":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"image_url":{"type":"string","nullable":true},"shop_url":{"type":"string","nullable":true},"tasting_notes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"group_key":{"type":"string"}},"required":["id","name","group_key"]}},"is_active":{"type":"boolean"},"created_by":{"type":"string","nullable":true,"format":"uuid"},"updated_by":{"type":"string","nullable":true,"format":"uuid"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","slug","roaster_id","roast_level","origin","process","description","image_url","shop_url","tasting_notes","is_active","created_by","updated_by","created_at","updated_at"]}}}},"400":{"description":"Invalid coffee ID or details","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Not an MFA-verified platform administrator","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Coffee not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"This roaster already has a coffee with this slug","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Admin request limit exceeded; retry after 60 seconds","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Coffee update failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/users/brands":{"get":{"tags":["Admin"],"security":[{"BearerAuth":[]}],"operationId":"searchAdminMembershipBrands","summary":"Find active brands for scoped user access","parameters":[{"schema":{"type":"string","maxLength":100,"default":""},"required":false,"name":"q","in":"query"}],"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}},"required":["id","name"]},"maxItems":50}}}},"400":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/users":{"get":{"tags":["Admin"],"security":[{"BearerAuth":[]}],"operationId":"listAdminUsers","summary":"Search, filter, and sort users or suspended accounts (50 per page)","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100000,"default":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"string","maxLength":100,"default":""},"required":false,"name":"q","in":"query"},{"schema":{"type":"string","enum":["true","false"],"default":"false"},"required":false,"name":"suspended","in":"query"},{"schema":{"type":"string","enum":["all","confirmed","unconfirmed"],"default":"all"},"required":false,"name":"confirmation","in":"query"},{"schema":{"type":"string","enum":["all","admin","non_admin"],"default":"all"},"required":false,"name":"access","in":"query"},{"schema":{"type":"string","enum":["newest","oldest","name_asc","name_desc","email_asc","email_desc"],"default":"name_asc"},"required":false,"name":"sort","in":"query"}],"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","nullable":true},"first_name":{"type":"string","nullable":true},"last_name":{"type":"string","nullable":true},"confirmed":{"type":"boolean"},"suspended":{"type":"boolean"},"is_admin":{"type":"boolean"},"created_at":{"type":"string"},"memberships":{"type":"array","items":{"type":"object","properties":{"roaster_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"role":{"type":"string","enum":["owner","editor"]}},"required":["roaster_id","name","role"]}},"brand_memberships":{"type":"array","items":{"type":"object","properties":{"brand_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"role":{"type":"string","enum":["owner","editor"]}},"required":["brand_id","name","role"]}}},"required":["id","email","first_name","last_name","confirmed","suspended","is_admin","created_at","memberships","brand_memberships"]}},"total":{"type":"number"},"page":{"type":"number"},"pageSize":{"type":"number","enum":[50]}},"required":["items","total","page","pageSize"]}}}},"400":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/users/summary":{"get":{"tags":["Admin"],"security":[{"BearerAuth":[]}],"operationId":"getAdminUserCounts","summary":"Total, active (not suspended), and suspended account counts; excludes deleted users","responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"integer","minimum":0},"active":{"type":"integer","minimum":0},"suspended":{"type":"integer","minimum":0}},"required":["total","active","suspended"]}}}},"400":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/users/invite":{"post":{"tags":["Admin"],"security":[{"BearerAuth":[]}],"operationId":"inviteAdminUser","summary":"Invite a new Supabase user by email; requires an MFA-authenticated admin session","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"first_name":{"type":"string","maxLength":100},"last_name":{"type":"string","maxLength":100},"email":{"type":"string","maxLength":254,"format":"email"}},"required":["first_name","last_name","email"],"additionalProperties":false}}}},"responses":{"201":{"description":"Successful operation","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"}},"required":["id"]}}}},"400":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/users/{id}":{"get":{"tags":["Admin"],"security":[{"BearerAuth":[]}],"operationId":"getAdminUser","summary":"Read a user's profile and current permissions","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","nullable":true},"first_name":{"type":"string","nullable":true},"last_name":{"type":"string","nullable":true},"confirmed":{"type":"boolean"},"suspended":{"type":"boolean"},"is_admin":{"type":"boolean"},"created_at":{"type":"string"},"memberships":{"type":"array","items":{"type":"object","properties":{"roaster_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"role":{"type":"string","enum":["owner","editor"]}},"required":["roaster_id","name","role"]}},"brand_memberships":{"type":"array","items":{"type":"object","properties":{"brand_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"role":{"type":"string","enum":["owner","editor"]}},"required":["brand_id","name","role"]}}},"required":["id","email","first_name","last_name","confirmed","suspended","is_admin","created_at","memberships","brand_memberships"]}}}},"400":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"patch":{"tags":["Admin"],"security":[{"BearerAuth":[]}],"operationId":"updateAdminUser","summary":"Update profile and Supabase login email; requires recent password and MFA","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"first_name":{"type":"string","maxLength":100},"last_name":{"type":"string","maxLength":100},"email":{"type":"string","maxLength":254,"format":"email"}},"required":["first_name","last_name","email"],"additionalProperties":false}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}},"required":["ok"]}}}},"400":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"tags":["Admin"],"security":[{"BearerAuth":[]}],"operationId":"deleteAdminUser","summary":"Delete a suspended Supabase user and their owned Storage objects","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"confirmation":{"type":"string","enum":["DELETE"]}},"required":["confirmation"],"additionalProperties":false}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}},"required":["ok"]}}}},"400":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/users/{id}/permissions":{"patch":{"tags":["Admin"],"security":[{"BearerAuth":[]}],"operationId":"updateAdminUserPermissions","summary":"Assign platform admin, roaster and brand memberships; revoke older sessions","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"is_admin":{"type":"boolean"},"memberships":{"type":"array","items":{"type":"object","properties":{"roaster_id":{"type":"string","format":"uuid"},"role":{"type":"string","enum":["owner","editor"]}},"required":["roaster_id","role"],"additionalProperties":false},"maxItems":50},"brand_memberships":{"type":"array","items":{"type":"object","properties":{"brand_id":{"type":"string","format":"uuid"},"role":{"type":"string","enum":["owner","editor"]}},"required":["brand_id","role"],"additionalProperties":false},"maxItems":50}},"required":["is_admin","memberships"],"additionalProperties":false}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}},"required":["ok"]}}}},"400":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/users/{id}/access":{"post":{"tags":["Admin"],"security":[{"BearerAuth":[]}],"operationId":"setAdminUserAccess","summary":"Suspend or restore a Supabase account; restoration requires recent authentication","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"suspended":{"type":"boolean"}},"required":["suspended"],"additionalProperties":false}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}},"required":["ok"]}}}},"400":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Recent authentication required","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/users/{id}/mfa/reset":{"post":{"tags":["Admin"],"security":[{"BearerAuth":[]}],"operationId":"resetAdminUserMfa","summary":"Remove a user's MFA factors and revoke existing sessions; requires recent password and MFA","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}},"required":["ok"]}}}},"400":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/users/welcome-email":{"post":{"tags":["Admin"],"security":[{"BearerAuth":[]}],"operationId":"resendAdminWelcomeEmail","summary":"Send Supabase password setup/reset emails to up to 10 selected users","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string","format":"uuid"},"minItems":1,"maxItems":10}},"required":["ids"],"additionalProperties":false}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"sent":{"type":"boolean"},"error":{"type":"string"}},"required":["id","sent"]}}},"required":["results"]}}}},"400":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/roles":{"get":{"tags":["Admin"],"security":[{"BearerAuth":[]}],"operationId":"listAdminRoles","summary":"Search, filter, and sort platform roles and assignment counts (50 per page)","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100000,"default":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"string","maxLength":100,"default":""},"required":false,"name":"q","in":"query"},{"schema":{"type":"string","enum":["all","assigned","unassigned"],"default":"all"},"required":false,"name":"assignment","in":"query"},{"schema":{"type":"string","enum":["name_asc","name_desc","users_desc","users_asc"],"default":"name_asc"},"required":false,"name":"sort","in":"query"}],"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"users":{"type":"number"}},"required":["id","name","description","users"]}},"total":{"type":"number"},"page":{"type":"number"},"pageSize":{"type":"number","enum":[50]}},"required":["items","total","page","pageSize"]}}}},"400":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"post":{"tags":["Admin"],"security":[{"BearerAuth":[]}],"operationId":"createAdminRole","summary":"Create a role with no permissions; requires recent password and MFA","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z][a-z0-9_-]*$"},"description":{"type":"string","maxLength":500}},"required":["name","description"],"additionalProperties":false}}}},"responses":{"201":{"description":"Successful operation","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"}},"required":["id"]}}}},"400":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/roles/{id}":{"get":{"tags":["Admin"],"security":[{"BearerAuth":[]}],"operationId":"getAdminRole","summary":"Read a platform role","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"users":{"type":"number"}},"required":["id","name","description","users"]}}}},"400":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"patch":{"tags":["Admin"],"security":[{"BearerAuth":[]}],"operationId":"updateAdminRole","summary":"Edit role name and description; reserved system role names cannot change","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z][a-z0-9_-]*$"},"description":{"type":"string","maxLength":500}},"required":["name","description"],"additionalProperties":false}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}},"required":["ok"]}}}},"400":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Invalid request, denied access, conflict, rate limit, or unavailable dependency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/access":{"get":{"operationId":"checkAdminAccess","summary":"Check staff access before MFA","description":"Requires a verified token, current platform-admin membership and a live session. Allows AAL1 for MFA setup and returns only the caller's user ID and MFA status.","tags":["Admin"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Current admin access status","content":{"application/json":{"schema":{"type":"object","properties":{"userId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["ready","mfa_required"]}},"required":["userId","status"]}}}},"401":{"description":"Request denied or access verification unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Request denied or access verification unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Request denied or access verification unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Request denied or access verification unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Request denied or access verification unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/session":{"get":{"operationId":"checkAdminSession","summary":"Verify a complete admin session","description":"Requires current platform-admin membership and MFA (AAL2). Rejects revoked, expired, banned or deleted-user sessions. Admin sessions have an eight-hour absolute limit.","tags":["Admin"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Current admin access status","content":{"application/json":{"schema":{"type":"object","properties":{"userId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["ready","mfa_required"]}},"required":["userId","status"]}}}},"401":{"description":"Request denied or access verification unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Request denied or access verification unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Request denied or access verification unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Request denied or access verification unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Request denied or access verification unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/portal/{portal}/access":{"get":{"operationId":"checkPortalAccess","tags":["Portal"],"summary":"Check business membership before optional MFA","description":"Requires a verified Supabase token, live confirmed account and active scoped membership. Sessions expire after eight hours; suspension, revocation and inactive memberships deny access. The session endpoint also requires AAL2 when the account has a verified MFA factor. Returns at most 100 businesses belonging to the caller. Platform admin and community roles alone do not grant portal access.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","enum":["brand","roaster"]},"required":true,"name":"portal","in":"path"}],"responses":{"200":{"description":"Current portal access","content":{"application/json":{"schema":{"type":"object","properties":{"userId":{"type":"string","format":"uuid"},"portal":{"type":"string","enum":["brand","roaster"]},"status":{"type":"string","enum":["ready","mfa_required"]},"memberships":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"role":{"type":"string","enum":["owner","editor"]}},"required":["id","name","role"]},"maxItems":100}},"required":["userId","portal","status","memberships"]}}}},"401":{"description":"Access denied or verification unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Access denied or verification unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Access denied or verification unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Access denied or verification unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/portal/{portal}/session":{"get":{"operationId":"checkPortalSession","tags":["Portal"],"summary":"Verify business portal session","description":"Requires a verified Supabase token, live confirmed account and active scoped membership. Sessions expire after eight hours; suspension, revocation and inactive memberships deny access. The session endpoint also requires AAL2 when the account has a verified MFA factor. Returns at most 100 businesses belonging to the caller. Platform admin and community roles alone do not grant portal access.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","enum":["brand","roaster"]},"required":true,"name":"portal","in":"path"}],"responses":{"200":{"description":"Current portal access","content":{"application/json":{"schema":{"type":"object","properties":{"userId":{"type":"string","format":"uuid"},"portal":{"type":"string","enum":["brand","roaster"]},"status":{"type":"string","enum":["ready","mfa_required"]},"memberships":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"role":{"type":"string","enum":["owner","editor"]}},"required":["id","name","role"]},"maxItems":100}},"required":["userId","portal","status","memberships"]}}}},"401":{"description":"Access denied or verification unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Access denied or verification unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Access denied or verification unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Access denied or verification unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/portal/{portal}/roasters/{roasterId}/coffees":{"get":{"tags":["Portal"],"security":[{"BearerAuth":[]}],"description":"Requires a live roaster owner/editor session, account MFA when configured, and active membership in this roaster. Records are scoped in SQL. Writes preserve ownership and slug and are attributed to the verified actor.","operationId":"listRoasterCoffees","summary":"List your roaster coffees","parameters":[{"schema":{"type":"string","enum":["roaster"]},"required":true,"name":"portal","in":"path"},{"schema":{"type":"string","format":"uuid"},"required":true,"name":"roasterId","in":"path"},{"schema":{"type":"integer","minimum":1,"maximum":100000,"default":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"string","maxLength":160,"default":""},"required":false,"name":"q","in":"query"}],"responses":{"200":{"description":"Coffee records, 50 per page","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"roaster_id":{"type":"string","format":"uuid"},"roast_level":{"type":"string","nullable":true},"origin":{"type":"string","nullable":true},"process":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"image_url":{"type":"string","nullable":true},"shop_url":{"type":"string","nullable":true},"tasting_notes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"group_key":{"type":"string"}},"required":["id","name","group_key"]}},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","slug","roaster_id","roast_level","origin","process","description","image_url","shop_url","tasting_notes","is_active","created_at","updated_at"]}},"total":{"type":"integer","minimum":0},"page":{"type":"integer","minimum":0,"exclusiveMinimum":true},"pageSize":{"type":"number","enum":[50]}},"required":["items","total","page","pageSize"]}}}},"400":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"post":{"tags":["Portal"],"security":[{"BearerAuth":[]}],"description":"Requires a live roaster owner/editor session, account MFA when configured, and active membership in this roaster. Records are scoped in SQL. Writes preserve ownership and slug and are attributed to the verified actor.","operationId":"createRoasterCoffee","summary":"Add a coffee to your roaster","parameters":[{"schema":{"type":"string","enum":["roaster"]},"required":true,"name":"portal","in":"path"},{"schema":{"type":"string","format":"uuid"},"required":true,"name":"roasterId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":160},"roast_level":{"type":"string","nullable":true,"maxLength":100},"origin":{"type":"string","nullable":true,"maxLength":250},"process":{"type":"string","nullable":true,"maxLength":250},"description":{"type":"string","nullable":true,"maxLength":4000},"image_url":{"type":"string","nullable":true,"maxLength":2048,"format":"uri"},"shop_url":{"type":"string","nullable":true,"maxLength":2048,"format":"uri"},"is_active":{"type":"boolean"}},"required":["name","roast_level","origin","process","description","image_url","shop_url","is_active"],"additionalProperties":false}}}},"responses":{"201":{"description":"Coffee created","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"roaster_id":{"type":"string","format":"uuid"},"roast_level":{"type":"string","nullable":true},"origin":{"type":"string","nullable":true},"process":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"image_url":{"type":"string","nullable":true},"shop_url":{"type":"string","nullable":true},"tasting_notes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"group_key":{"type":"string"}},"required":["id","name","group_key"]}},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","slug","roaster_id","roast_level","origin","process","description","image_url","shop_url","tasting_notes","is_active","created_at","updated_at"]}}}},"400":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/portal/{portal}/roasters/{roasterId}/coffees/{id}":{"get":{"tags":["Portal"],"security":[{"BearerAuth":[]}],"description":"Requires a live roaster owner/editor session, account MFA when configured, and active membership in this roaster. Records are scoped in SQL. Writes preserve ownership and slug and are attributed to the verified actor.","operationId":"getRoasterCoffee","summary":"Read your coffee for editing","parameters":[{"schema":{"type":"string","enum":["roaster"]},"required":true,"name":"portal","in":"path"},{"schema":{"type":"string","format":"uuid"},"required":true,"name":"roasterId","in":"path"},{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Coffee record","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"roaster_id":{"type":"string","format":"uuid"},"roast_level":{"type":"string","nullable":true},"origin":{"type":"string","nullable":true},"process":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"image_url":{"type":"string","nullable":true},"shop_url":{"type":"string","nullable":true},"tasting_notes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"group_key":{"type":"string"}},"required":["id","name","group_key"]}},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","slug","roaster_id","roast_level","origin","process","description","image_url","shop_url","tasting_notes","is_active","created_at","updated_at"]}}}},"400":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"patch":{"tags":["Portal"],"security":[{"BearerAuth":[]}],"description":"Requires a live roaster owner/editor session, account MFA when configured, and active membership in this roaster. Records are scoped in SQL. Writes preserve ownership and slug and are attributed to the verified actor.","operationId":"updateRoasterCoffee","summary":"Edit your roaster coffee","parameters":[{"schema":{"type":"string","enum":["roaster"]},"required":true,"name":"portal","in":"path"},{"schema":{"type":"string","format":"uuid"},"required":true,"name":"roasterId","in":"path"},{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":160},"roast_level":{"type":"string","nullable":true,"maxLength":100},"origin":{"type":"string","nullable":true,"maxLength":250},"process":{"type":"string","nullable":true,"maxLength":250},"description":{"type":"string","nullable":true,"maxLength":4000},"image_url":{"type":"string","nullable":true,"maxLength":2048,"format":"uri"},"shop_url":{"type":"string","nullable":true,"maxLength":2048,"format":"uri"},"is_active":{"type":"boolean"}},"required":["name","roast_level","origin","process","description","image_url","shop_url","is_active"],"additionalProperties":false}}}},"responses":{"200":{"description":"Coffee saved","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"roaster_id":{"type":"string","format":"uuid"},"roast_level":{"type":"string","nullable":true},"origin":{"type":"string","nullable":true},"process":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"image_url":{"type":"string","nullable":true},"shop_url":{"type":"string","nullable":true},"tasting_notes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"group_key":{"type":"string"}},"required":["id","name","group_key"]}},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","slug","roaster_id","roast_level","origin","process","description","image_url","shop_url","tasting_notes","is_active","created_at","updated_at"]}}}},"400":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Request rejected or service unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/mobile/discover":{"get":{"operationId":"getMobileDiscover","tags":["Catalogue"],"summary":"Read the active public discovery catalogue","description":"At most 100 recent coffees and roasters, plus up to 40 pictured new coffees and 200 pictured roasters. Uses cut-out coffee images and roaster backgrounds before ordinary photos/logos. Exact active counts; excludes inactive listings, private contacts, ownership and staff fields.","security":[],"responses":{"200":{"description":"Active public catalogue","content":{"application/json":{"schema":{"type":"object","properties":{"coffees":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"image_url":{"type":"string","nullable":true},"created_at":{"type":"string"}},"required":["id","name","image_url","created_at"]},"maxItems":100},"roasters":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"image_url":{"type":"string","nullable":true},"created_at":{"type":"string"}},"required":["id","name","image_url","created_at"]},"maxItems":100},"fresh_coffees":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"image_url":{"type":"string","nullable":true},"created_at":{"type":"string"}},"required":["id","name","image_url","created_at"]},"maxItems":40},"pictured_roasters":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"image_url":{"type":"string","nullable":true},"created_at":{"type":"string"}},"required":["id","name","image_url","created_at"]},"maxItems":200},"counts":{"type":"object","properties":{"coffees":{"type":"integer","minimum":0},"roasters":{"type":"integer","minimum":0}},"required":["coffees","roasters"]}},"required":["coffees","roasters","counts"]}}}},"429":{"description":"Catalogue request limit reached","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["SERVICE_UNAVAILABLE","RATE_LIMITED"]},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"Catalogue unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["SERVICE_UNAVAILABLE","RATE_LIMITED"]},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}}}}},"/mobile/catalogue/{kind}/letters":{"get":{"tags":["Catalogue"],"security":[],"description":"Public active catalogue fields only. Inactive roasters and their coffees remain hidden.","operationId":"getMobileCatalogueLetters","summary":"Count active listings by first letter","parameters":[{"schema":{"type":"string","enum":["coffee","roaster"]},"required":true,"name":"kind","in":"path"}],"responses":{"200":{"description":"Exact active letter counts","content":{"application/json":{"schema":{"type":"object","properties":{"counts":{"type":"object","additionalProperties":{"type":"integer","minimum":0}}},"required":["counts"]}}}},"400":{"description":"Catalogue unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"404":{"description":"Catalogue unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"Catalogue unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"Catalogue unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}}}}},"/mobile/catalogue/{kind}":{"get":{"tags":["Catalogue"],"security":[],"description":"Public active catalogue fields only. Inactive roasters and their coffees remain hidden.","operationId":"getMobileCatalogue","summary":"Browse 50 active listings per page","parameters":[{"schema":{"type":"string","enum":["coffee","roaster"]},"required":true,"name":"kind","in":"path"},{"schema":{"type":"integer","minimum":1,"maximum":10000,"default":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"string","pattern":"^[A-Z]$"},"required":false,"name":"letter","in":"query"}],"responses":{"200":{"description":"Alphabetically sorted catalogue","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"image_url":{"type":"string","nullable":true}},"required":["id","name","image_url"]},"maxItems":50},"has_more":{"type":"boolean"}},"required":["items","has_more"]}}}},"400":{"description":"Catalogue unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"404":{"description":"Catalogue unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"Catalogue unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"Catalogue unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}}}}},"/mobile/catalogue/{kind}/{id}":{"get":{"tags":["Catalogue"],"security":[],"description":"Public active catalogue fields only. Inactive roasters and their coffees remain hidden.","operationId":"getMobileCatalogueDetail","summary":"Read a public coffee or roaster","parameters":[{"schema":{"type":"string","enum":["coffee","roaster"]},"required":true,"name":"kind","in":"path"},{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Current catalogue fields and UUID tasting notes","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"image_url":{"type":"string","nullable":true},"kind":{"type":"string","enum":["coffee","roaster"]},"description":{"type":"string","nullable":true},"shop_url":{"type":"string","nullable":true},"website_url":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"origin":{"type":"string","nullable":true},"process":{"type":"string","nullable":true},"roast_level":{"type":"string","nullable":true},"roaster_id":{"type":"string","nullable":true,"format":"uuid"},"roaster_name":{"type":"string","nullable":true},"tasting_notes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}},"required":["id","name"]}}},"required":["id","name","image_url","kind","description","shop_url","website_url","country","origin","process","roast_level","roaster_id","roaster_name","tasting_notes"]}}}},"400":{"description":"Catalogue unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"404":{"description":"Catalogue unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"Catalogue unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"Catalogue unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}}}}},"/mobile/profile":{"get":{"tags":["Profiles"],"security":[{"BearerAuth":[]}],"operationId":"getOwnMobileProfile","summary":"Read your application profile","responses":{"200":{"description":"Visible profile; never includes email, roles or account state","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"first_name":{"type":"string","nullable":true},"last_name":{"type":"string","nullable":true},"display_name":{"type":"string","nullable":true},"username":{"type":"string","nullable":true},"bio":{"type":"string","nullable":true},"avatar_url":{"type":"string","nullable":true},"public_profile":{"type":"boolean"},"onboarding_completed_at":{"type":"string","nullable":true}},"required":["id","first_name","last_name","display_name","username","bio","avatar_url","public_profile","onboarding_completed_at"]}}}},"400":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"404":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"409":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"413":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}}}},"patch":{"tags":["Profiles"],"security":[{"BearerAuth":[]}],"operationId":"saveOwnMobileProfile","summary":"Complete onboarding or edit your own profile","description":"Own-profile writes only. Usernames are normalized and case-insensitively unique. Profile creation grants only the existing Community role. Private/incomplete profiles are invisible to other mobile users. Verified live sessions required, including AAL2 when MFA is enrolled.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"first_name":{"type":"string","minLength":1,"maxLength":100},"last_name":{"type":"string","minLength":1,"maxLength":100},"username":{"type":"string","pattern":"^[a-z0-9_.]{3,30}$"},"public_profile":{"type":"boolean"}},"required":["first_name","last_name","username","public_profile"],"additionalProperties":false}}}},"responses":{"200":{"description":"Visible profile; never includes email, roles or account state","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"first_name":{"type":"string","nullable":true},"last_name":{"type":"string","nullable":true},"display_name":{"type":"string","nullable":true},"username":{"type":"string","nullable":true},"bio":{"type":"string","nullable":true},"avatar_url":{"type":"string","nullable":true},"public_profile":{"type":"boolean"},"onboarding_completed_at":{"type":"string","nullable":true}},"required":["id","first_name","last_name","display_name","username","bio","avatar_url","public_profile","onboarding_completed_at"]}}}},"400":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"404":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"409":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"413":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}}}}},"/mobile/profiles/{id}":{"get":{"tags":["Profiles"],"security":[{"BearerAuth":[]}],"operationId":"getVisibleMobileProfile","summary":"Read your own or another completed public profile","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Visible profile; never includes email, roles or account state","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"first_name":{"type":"string","nullable":true},"last_name":{"type":"string","nullable":true},"display_name":{"type":"string","nullable":true},"username":{"type":"string","nullable":true},"bio":{"type":"string","nullable":true},"avatar_url":{"type":"string","nullable":true},"public_profile":{"type":"boolean"},"onboarding_completed_at":{"type":"string","nullable":true}},"required":["id","first_name","last_name","display_name","username","bio","avatar_url","public_profile","onboarding_completed_at"]}}}},"400":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"404":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"409":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"413":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}}}}},"/mobile/profile/avatar":{"post":{"tags":["Profiles"],"security":[{"BearerAuth":[]}],"operationId":"saveOwnMobileAvatar","summary":"Replace your own profile picture","description":"Private storage. API checks image bytes, re-encodes to 512px JPEG without metadata, and deletes the replaced object. Pictures are included only in authorized, uncached profile responses. No direct Storage grants or permanent public URLs.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"base64":{"type":"string","minLength":4,"maxLength":2796204,"pattern":"^[A-Za-z0-9+/]+={0,2}$"}},"required":["base64"],"additionalProperties":false}}}},"responses":{"200":{"description":"Visible profile; never includes email, roles or account state","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"first_name":{"type":"string","nullable":true},"last_name":{"type":"string","nullable":true},"display_name":{"type":"string","nullable":true},"username":{"type":"string","nullable":true},"bio":{"type":"string","nullable":true},"avatar_url":{"type":"string","nullable":true},"public_profile":{"type":"boolean"},"onboarding_completed_at":{"type":"string","nullable":true}},"required":["id","first_name","last_name","display_name","username","bio","avatar_url","public_profile","onboarding_completed_at"]}}}},"400":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"404":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"409":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"413":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"Request denied or unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}}}}},"/health":{"get":{"operationId":"getHealth","tags":["System"],"summary":"Check API liveness","description":"Confirms that the HTTP service is responding. Does not check Supabase or other dependencies.","security":[],"responses":{"200":{"description":"The HTTP service is responding.","content":{"application/json":{"example":{"status":"ok","service":"xtract-api"},"schema":{"$ref":"#/components/schemas/Health"}}}}}}}}}