{"openapi":"3.1.0","info":{"title":"E-Commerce API","description":"Portfolio ecommerce API backed by PostgreSQL. Public catalog reads use GBP decimal prices. Product writes require an active admin. Register a customer, then use Authorize with your email in the username field to obtain a bearer token. Admin bootstrap is an operator CLI, never a public endpoint. Signed-in carts use current prices without reserving stock. Order placement claims stock; configured sandbox orders use hosted Checkout.","version":"0.1.0"},"paths":{"/health":{"get":{"tags":["Health"],"summary":"Check API process liveness","description":"Simple health check endpoint.\nReturns \"OK\" if the API is running.","operationId":"health_check_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Health Check Health Get"}}}}}}},"/api/v1/categories/":{"get":{"tags":["Categories"],"summary":"List categories","description":"Public workspace categories in display order. Each count uses the same name search and stock filter as the catalog, and is not limited to one category. All is not a category; omit the catalog category filter to browse everything. Uncategorized is the stored fallback.","operationId":"read_categories_api_v1_categories__get","parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":100,"pattern":"^[^\\x00]*$"},{"type":"null"}],"title":"Q"}},{"name":"in_stock","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"In Stock"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CategoryCount"},"title":"Response Read Categories Api V1 Categories  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/products/":{"get":{"tags":["Products"],"summary":"List products","description":"Public catalog in stable ID order. Pagination limit is 1–100; skip is 0–100000.","operationId":"read_products_api_v1_products__get","parameters":[{"name":"skip","in":"query","required":false,"schema":{"type":"integer","maximum":100000,"minimum":0,"default":0,"title":"Skip"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":10,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ProductRead"},"title":"Response Read Products Api V1 Products  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Products"],"summary":"Create a product","description":"Active admin only. GBP price has at most two decimal places; stock is a nonnegative integer. Unknown fields are rejected.","operationId":"create_new_product_api_v1_products__post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductRead"}}}},"401":{"description":"Active authenticated account required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Admin privileges required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests. Retry after the Retry-After delay (seconds).","headers":{"Retry-After":{"description":"Seconds until the current window expires.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request limit for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/products/search":{"get":{"tags":["Products"],"summary":"Search and page the complete catalog","description":"Public, server-filtered catalog page. Search matches product names, ignores case using the database locale and treats percent/underscore as literal characters. Featured order uses stable product IDs; name ordering follows the database collation after lowercasing. All sorts break ties by ID. An optional category slug narrows the same snapshot; omit it to browse every category. Each page and its count share one database snapshot; catalog edits can change later requests.","operationId":"search_catalog_api_v1_products_search_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":100,"pattern":"^[^\\x00]*$"},{"type":"null"}],"title":"Q"}},{"name":"in_stock","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"In Stock"}},{"name":"sort","in":"query","required":false,"schema":{"enum":["featured","name","price-asc","price-desc"],"type":"string","default":"featured","title":"Sort"}},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$"},{"type":"null"}],"description":"Stored category slug. Omit to include every category.","title":"Category"},"description":"Stored category slug. Omit to include every category."},{"name":"skip","in":"query","required":false,"schema":{"type":"integer","maximum":100000,"minimum":0,"default":0,"title":"Skip"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":24,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductPageRead"}}}},"404":{"description":"Category does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/products/{product_id}":{"get":{"tags":["Products"],"summary":"Read a product","operationId":"read_product_api_v1_products__product_id__get","parameters":[{"name":"product_id","in":"path","required":true,"schema":{"type":"integer","title":"Product Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductRead"}}}},"404":{"description":"Product does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Products"],"summary":"Partially update a product","description":"Active admin only. Omitted fields retain their values. Description, image, material and dimensions may be null to clear them. Name, price, currency, stock and category may not be null. This PUT intentionally has partial-update semantics.","operationId":"update_existing_product_api_v1_products__product_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"product_id","in":"path","required":true,"schema":{"type":"integer","title":"Product Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductRead"}}}},"401":{"description":"Active authenticated account required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Admin privileges required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests. Retry after the Retry-After delay (seconds).","headers":{"Retry-After":{"description":"Seconds until the current window expires.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request limit for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Product does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Conflict"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Products"],"summary":"Delete a product","description":"Active admin only. Returns no body on success.","operationId":"delete_existing_product_api_v1_products__product_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"product_id","in":"path","required":true,"schema":{"type":"integer","title":"Product Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Active authenticated account required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Admin privileges required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests. Retry after the Retry-After delay (seconds).","headers":{"Retry-After":{"description":"Seconds until the current window expires.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request limit for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Product does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Conflict"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/register":{"post":{"tags":["auth"],"summary":"Register a customer account","description":"Create an active customer. Registration never grants admin privileges.","operationId":"register_api_v1_auth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserRead"}}}},"400":{"description":"Email is already registered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests. Retry after the Retry-After delay (seconds).","headers":{"Retry-After":{"description":"Seconds until the current window expires.","schema":{"type":"integer","minimum":1.0}},"X-RateLimit-Limit":{"description":"Request limit for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/login":{"post":{"tags":["auth"],"summary":"Sign in with email and password","description":"Submit an application/x-www-form-urlencoded body. The username field is the\naccount email. Returns a bearer JWT; use it in Authorization: Bearer <token>.","operationId":"login_api_v1_auth_login_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_login_api_v1_auth_login_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Token"}}}},"401":{"description":"Invalid credentials or disabled account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests. Retry after the Retry-After delay (seconds).","headers":{"Retry-After":{"description":"Seconds until the current window expires.","schema":{"type":"integer","minimum":1.0}},"X-RateLimit-Limit":{"description":"Request limit for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/me":{"get":{"tags":["auth"],"summary":"Read the active user profile","description":"Get the currently authenticated user.","operationId":"me_api_v1_auth_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserRead"}}}},"401":{"description":"Missing, invalid or expired token, or inactive account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/orders/drafts":{"post":{"tags":["Orders"],"summary":"Post Draft","description":"Save a GBP quotation only: no stock reservation, cart change or placement.\n\nRepeating creation produces a new draft. The separate placement endpoint\nuses a customer-scoped idempotency key for safe retries.\nPrices are copied from the backend and must be revalidated before purchase.","operationId":"post_draft_api_v1_orders_drafts_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRead"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests. Retry after the Retry-After delay (seconds).","headers":{"Retry-After":{"description":"Seconds until the current window expires.","schema":{"type":"integer","minimum":1.0}},"X-RateLimit-Limit":{"description":"Request limit for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/orders/":{"get":{"tags":["Orders"],"summary":"Get Orders","description":"List your saved orders by ascending ID; use the last ID as after_id.","operationId":"get_orders_api_v1_orders__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"after_id","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"After Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/OrderRead"},"title":"Response Get Orders Api V1 Orders  Get"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Unauthorized"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/orders/{order_id}":{"get":{"tags":["Orders"],"summary":"Get Order","description":"Return your snapshot; other customers' IDs are indistinguishable from missing.","operationId":"get_order_api_v1_orders__order_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"integer","maximum":2147483647,"minimum":1,"title":"Order Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRead"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/orders/{order_id}/place":{"post":{"tags":["Orders"],"summary":"Post Placement","description":"Place your quotation once; reuse the same key after response loss.\n\nPrice or cart changes require a fresh draft and deliberate confirmation.\nThis endpoint claims inventory but does not collect payment.","operationId":"post_placement_api_v1_orders__order_id__place_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"integer","maximum":2147483647,"minimum":1,"title":"Order Id"}},{"name":"idempotency-key","in":"header","required":true,"schema":{"type":"string","minLength":1,"maxLength":128,"pattern":"^[A-Za-z0-9_-]+$","title":"Idempotency-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRead"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Conflict"},"429":{"description":"Too many requests. Retry after the Retry-After delay (seconds).","headers":{"Retry-After":{"description":"Seconds until the current window expires.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request limit for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/orders/{order_id}/payment":{"post":{"tags":["Orders"],"summary":"Post Payment","description":"Start or recover the same sandbox Checkout session; never create a second charge.","operationId":"post_payment_api_v1_orders__order_id__payment_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"integer","maximum":2147483647,"minimum":1,"title":"Order Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentRead"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Conflict"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Service Unavailable"},"429":{"description":"Too many requests. Retry after the Retry-After delay (seconds).","headers":{"Retry-After":{"description":"Seconds until the current window expires.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request limit for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/orders/{order_id}/cancel":{"post":{"tags":["Orders"],"summary":"Post Cancel","description":"Release inventory only after unstarted or provider-confirmed cancelled payment.","operationId":"post_cancel_api_v1_orders__order_id__cancel_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"integer","maximum":2147483647,"minimum":1,"title":"Order Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRead"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Conflict"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Service Unavailable"},"429":{"description":"Too many requests. Retry after the Retry-After delay (seconds).","headers":{"Retry-After":{"description":"Seconds until the current window expires.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request limit for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/orders/{order_id}/reconcile":{"post":{"tags":["Orders"],"summary":"Post Reconcile","description":"Read authoritative provider state; browser redirects do not establish payment.","operationId":"post_reconcile_api_v1_orders__order_id__reconcile_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"integer","maximum":2147483647,"minimum":1,"title":"Order Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRead"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Conflict"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Service Unavailable"},"429":{"description":"Too many requests. Retry after the Retry-After delay (seconds).","headers":{"Retry-After":{"description":"Seconds until the current window expires.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request limit for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/payments/webhook":{"post":{"tags":["Payments"],"summary":"Webhook","description":"Verify the exact raw signed body and commit deduplication with its effects.","operationId":"webhook_api_v1_payments_webhook_post","parameters":[{"name":"stripe-signature","in":"header","required":false,"schema":{"type":"string","maxLength":2048,"default":"","title":"Stripe-Signature"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"boolean"},"title":"Response Webhook Api V1 Payments Webhook Post"}}}},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Bad Request"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Conflict"},"413":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Request Entity Too Large"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Service Unavailable"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/cart/":{"get":{"tags":["Cart"],"summary":"Read your saved cart","description":"Current GBP prices and availability; stock is not reserved. Saved shortages remain visible. Deleted products are removed from carts.","operationId":"get_cart_api_v1_cart__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CartRead"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/cart/items/{product_id}":{"put":{"tags":["Cart"],"summary":"Set an absolute cart quantity","description":"Idempotent quantity 1–99. New lines/increases require stock; reductions and unchanged quantities remain allowed during shortages. Concurrent writes serialize per customer: last serialized write wins. Prices and ownership cannot be supplied.","operationId":"put_cart_item_api_v1_cart_items__product_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"product_id","in":"path","required":true,"schema":{"type":"integer","maximum":2147483647,"minimum":1,"title":"Product Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CartQuantity"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CartRead"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Conflict"},"429":{"description":"Too many requests. Retry after the Retry-After delay (seconds).","headers":{"Retry-After":{"description":"Seconds until the current window expires.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request limit for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Cart"],"summary":"Remove a product from your cart","description":"Idempotent: missing lines also return 204. Only your own line can be removed.","operationId":"delete_cart_item_api_v1_cart_items__product_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"product_id","in":"path","required":true,"schema":{"type":"integer","maximum":2147483647,"minimum":1,"title":"Product Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Unauthorized"},"429":{"description":"Too many requests. Retry after the Retry-After delay (seconds).","headers":{"Retry-After":{"description":"Seconds until the current window expires.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request limit for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"Body_login_api_v1_auth_login_post":{"properties":{"grant_type":{"anyOf":[{"type":"string","pattern":"^password$"},{"type":"null"}],"title":"Grant Type"},"username":{"type":"string","title":"Username"},"password":{"type":"string","format":"password","title":"Password"},"scope":{"type":"string","title":"Scope","default":""},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"client_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"format":"password","title":"Client Secret"}},"type":"object","required":["username","password"],"title":"Body_login_api_v1_auth_login_post"},"CartItem":{"properties":{"product":{"$ref":"#/components/schemas/ProductRead"},"quantity":{"type":"integer","title":"Quantity"},"available":{"type":"boolean","title":"Available"},"line_total":{"type":"string","title":"Line Total"}},"type":"object","required":["product","quantity","available","line_total"],"title":"CartItem"},"CartQuantity":{"properties":{"quantity":{"type":"integer","maximum":99.0,"minimum":1.0,"title":"Quantity"}},"additionalProperties":false,"type":"object","required":["quantity"],"title":"CartQuantity"},"CartRead":{"properties":{"items":{"items":{"$ref":"#/components/schemas/CartItem"},"type":"array","title":"Items"},"currency":{"type":"string","const":"GBP","title":"Currency","default":"GBP"},"subtotal":{"type":"string","title":"Subtotal"}},"type":"object","required":["items","subtotal"],"title":"CartRead"},"CategoryCount":{"properties":{"slug":{"type":"string","title":"Slug"},"name":{"type":"string","title":"Name"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["slug","name","count"],"title":"CategoryCount"},"CategorySummary":{"properties":{"slug":{"type":"string","title":"Slug"},"name":{"type":"string","title":"Name"}},"type":"object","required":["slug","name"],"title":"CategorySummary"},"DraftCreate":{"properties":{"lines":{"items":{"$ref":"#/components/schemas/DraftLineCreate"},"type":"array","maxItems":100,"minItems":1,"title":"Lines"}},"additionalProperties":false,"type":"object","required":["lines"],"title":"DraftCreate"},"DraftLineCreate":{"properties":{"product_id":{"type":"integer","maximum":2147483647.0,"minimum":1.0,"title":"Product Id"},"quantity":{"type":"integer","maximum":99.0,"minimum":1.0,"title":"Quantity"}},"additionalProperties":false,"type":"object","required":["product_id","quantity"],"title":"DraftLineCreate"},"ErrorResponse":{"properties":{"detail":{"type":"string","title":"Detail"}},"type":"object","required":["detail"],"title":"ErrorResponse","description":"HTTP errors use a detail string; validation errors have FastAPI's 422 schema."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"OrderLineRead":{"properties":{"product_id":{"type":"integer","title":"Product Id"},"product_name":{"type":"string","title":"Product Name"},"unit_price":{"type":"string","title":"Unit Price"},"quantity":{"type":"integer","title":"Quantity"},"line_total":{"type":"string","title":"Line Total"}},"type":"object","required":["product_id","product_name","unit_price","quantity","line_total"],"title":"OrderLineRead"},"OrderRead":{"properties":{"id":{"type":"integer","title":"Id"},"status":{"type":"string","enum":["draft","placed"],"title":"Status"},"payment_status":{"anyOf":[{"type":"string","enum":["pending","paid","failed","cancelled","expired"]},{"type":"null"}],"title":"Payment Status"},"payment_expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Payment Expires At"},"currency":{"type":"string","const":"GBP","title":"Currency"},"total":{"type":"string","title":"Total"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"lines":{"items":{"$ref":"#/components/schemas/OrderLineRead"},"type":"array","title":"Lines"}},"type":"object","required":["id","status","currency","total","created_at","lines"],"title":"OrderRead"},"PaymentRead":{"properties":{"order":{"$ref":"#/components/schemas/OrderRead"},"checkout_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Checkout Url"}},"type":"object","required":["order","checkout_url"],"title":"PaymentRead"},"ProductCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}],"title":"Description"},"price":{"anyOf":[{"type":"number","maximum":9999999999.99,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*(?:\\d{0,10}|(?=[\\d.]{1,13}0*$)\\d{0,10}\\.\\d{0,2}0*$)"}],"title":"Price"},"currency":{"type":"string","const":"GBP","title":"Currency","default":"GBP"},"stock":{"type":"integer","maximum":2147483647.0,"minimum":0.0,"title":"Stock"},"category":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","title":"Category"},"image":{"anyOf":[{"$ref":"#/components/schemas/ProductImage"},{"type":"null"}]},"material":{"anyOf":[{"type":"string","maxLength":80,"minLength":1},{"type":"null"}],"title":"Material"},"width_mm":{"anyOf":[{"type":"integer","maximum":10000.0,"minimum":1.0},{"type":"null"}],"title":"Width Mm"},"depth_mm":{"anyOf":[{"type":"integer","maximum":10000.0,"minimum":1.0},{"type":"null"}],"title":"Depth Mm"},"height_mm":{"anyOf":[{"type":"integer","maximum":10000.0,"minimum":1.0},{"type":"null"}],"title":"Height Mm"}},"additionalProperties":false,"type":"object","required":["name","price","stock","category"],"title":"ProductCreate"},"ProductImage":{"properties":{"key":{"type":"string","enum":["stand","mat","lamp","tray","notebooks","cup","keyboard","headphones","bottle","planter","clock","mouse"],"title":"Key"},"alt":{"type":"string","maxLength":200,"minLength":1,"title":"Alt"}},"additionalProperties":false,"type":"object","required":["key","alt"],"title":"ProductImage","description":"One allowlisted local photograph and its stored alternative text."},"ProductPageRead":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ProductRead"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"},"limit":{"type":"integer","title":"Limit"},"skip":{"type":"integer","title":"Skip"}},"type":"object","required":["items","total","limit","skip"],"title":"ProductPageRead","description":"A bounded catalog slice and the count of all matching products."},"ProductRead":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}],"title":"Description"},"price":{"type":"string","title":"Price"},"currency":{"type":"string","const":"GBP","title":"Currency","default":"GBP"},"stock":{"type":"integer","maximum":2147483647.0,"minimum":0.0,"title":"Stock"},"id":{"type":"integer","title":"Id"},"category":{"$ref":"#/components/schemas/CategorySummary"},"image":{"anyOf":[{"$ref":"#/components/schemas/ProductImage"},{"type":"null"}]},"material":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Material"},"width_mm":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Width Mm"},"depth_mm":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Depth Mm"},"height_mm":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Height Mm"}},"additionalProperties":false,"type":"object","required":["name","price","stock","id","category"],"title":"ProductRead"},"ProductUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}],"title":"Description"},"price":{"anyOf":[{"type":"number","maximum":9999999999.99,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*(?:\\d{0,10}|(?=[\\d.]{1,13}0*$)\\d{0,10}\\.\\d{0,2}0*$)"},{"type":"null"}],"title":"Price"},"currency":{"anyOf":[{"type":"string","const":"GBP"},{"type":"null"}],"title":"Currency"},"stock":{"anyOf":[{"type":"integer","maximum":2147483647.0,"minimum":0.0},{"type":"null"}],"title":"Stock"},"category":{"anyOf":[{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$"},{"type":"null"}],"title":"Category"},"image":{"anyOf":[{"$ref":"#/components/schemas/ProductImage"},{"type":"null"}]},"material":{"anyOf":[{"type":"string","maxLength":80,"minLength":1},{"type":"null"}],"title":"Material"},"width_mm":{"anyOf":[{"type":"integer","maximum":10000.0,"minimum":1.0},{"type":"null"}],"title":"Width Mm"},"depth_mm":{"anyOf":[{"type":"integer","maximum":10000.0,"minimum":1.0},{"type":"null"}],"title":"Depth Mm"},"height_mm":{"anyOf":[{"type":"integer","maximum":10000.0,"minimum":1.0},{"type":"null"}],"title":"Height Mm"}},"additionalProperties":false,"type":"object","title":"ProductUpdate"},"Token":{"properties":{"access_token":{"type":"string","title":"Access Token"},"token_type":{"type":"string","title":"Token Type","default":"bearer"},"expires_in":{"type":"integer","title":"Expires In"}},"type":"object","required":["access_token","expires_in"],"title":"Token"},"UserCreate":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","maxLength":128,"minLength":8,"title":"Password"}},"type":"object","required":["email","password"],"title":"UserCreate"},"UserRead":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"id":{"type":"integer","title":"Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"is_active":{"type":"boolean","title":"Is Active"},"is_superuser":{"type":"boolean","title":"Is Superuser"}},"type":"object","required":["email","id","created_at","is_active","is_superuser"],"title":"UserRead"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"OAuth2PasswordBearer":{"type":"oauth2","flows":{"password":{"scopes":{},"tokenUrl":"/api/v1/auth/login"}}}}},"tags":[{"name":"Health","description":"Process liveness; does not check database readiness."},{"name":"Products","description":"Public catalog reads and admin-only product management. Prices serialize as two-place GBP strings."},{"name":"auth","description":"Customer registration, password login and active-user profile."}]}