{"openapi":"3.1.0","info":{"title":"Shelf API","version":"1.0.0","description":"The REST API of Shelf, a practice web shop for software testers – the same shop as the website, with GraphQL at /graphql. Get a token with POST /api/auth/register or /api/auth/login and send `Authorization: Bearer <token>`, or create a key with POST /api/keys and send `X-API-Key: <key>`. Everything you create is visible only to you; POST /api/me/reset clears it. Every response carries `x-trace-id` – quote it when you report a bug."},"servers":[{"url":"https://api.qasteps.com"}],"tags":[{"name":"Account"},{"name":"Catalog"},{"name":"Cart"},{"name":"Orders"},{"name":"Service"},{"name":"Keys"},{"name":"Tasks"}],"paths":{"/api/auth/register":{"post":{"tags":["Account"],"summary":"Create an account","description":"Username 3–15 characters (letters, digits, _), password 8–72 characters. Use a made-up username, not your real name.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Credentials"},"example":{"username":"night_tester","password":"kettle-on-42"}}}},"responses":{"201":{"description":"Account created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthResult"}}}},"400":{"description":"Validation failed – see error.details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Username taken","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/auth/login":{"post":{"tags":["Account"],"summary":"Sign in and get a bearer token (valid 8 hours)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Credentials"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthResult"}}}},"400":{"description":"Validation failed – see error.details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Wrong username or password","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/me":{"get":{"tags":["Account"],"summary":"Your account","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}}},"401":{"description":"Not signed in or the token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Account"],"summary":"Delete your account and everything in it","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"204":{"description":"Deleted"},"401":{"description":"Not signed in or the token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/me/reset":{"post":{"tags":["Account"],"summary":"Reset my data: delete your cart, coupon, orders, payments, products and task progress (the account and keys stay)","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"deleted":{"type":"object","properties":{"cart_items":{"type":"integer"},"orders":{"type":"integer"},"products":{"type":"integer"}}}}}}}},"401":{"description":"Not signed in or the token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/categories":{"get":{"tags":["Catalog"],"summary":"Categories with product counts","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Category"}}}}}}}},"/api/products":{"get":{"tags":["Catalog"],"summary":"List products","description":"The shared catalog plus, when you send a token, your own products.","parameters":[{"name":"category","in":"query","schema":{"type":"string"},"example":"kettles"},{"name":"q","in":"query","description":"Search in name and brand","schema":{"type":"string"}},{"name":"min_price","in":"query","schema":{"type":"number"}},{"name":"max_price","in":"query","schema":{"type":"number"}},{"name":"in_stock","in":"query","schema":{"type":"boolean"}},{"name":"free_shipping","in":"query","schema":{"type":"boolean"}},{"name":"mine","in":"query","description":"Only products you created","schema":{"type":"boolean"}},{"name":"sort","in":"query","schema":{"type":"string","enum":["featured","price_asc","price_desc","name_asc","rating"],"default":"featured"}},{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1},"description":"Page N holds items (N−1)·page_size + 1 … N·page_size; pages never overlap."},{"name":"page_size","in":"query","schema":{"type":"integer","minimum":1,"maximum":48,"default":12}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductPage"}}}},"400":{"description":"Validation failed – see error.details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Catalog"],"summary":"Create your own product (visible only to you)","security":[{"bearerAuth":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductInput"},"example":{"name":"Test Kettle 1 l","brand":"Night Shift","category":"kettles","price":19.99,"stock":5}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Product"}}},"headers":{"Location":{"schema":{"type":"string"}}}},"400":{"description":"Validation failed – see error.details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in or the token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"You already have 50 products","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/products/{id}":{"parameters":[{"name":"id","in":"path","required":true,"description":"Product id","schema":{"type":"integer","minimum":1}}],"get":{"tags":["Catalog"],"summary":"One product","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Product"}}}},"400":{"description":"Validation failed – see error.details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found (or not yours)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Catalog"],"summary":"Replace your product (all fields)","security":[{"bearerAuth":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductInput"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Product"}}}},"400":{"description":"Validation failed – see error.details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in or the token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Shared catalog products are read-only","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found (or not yours)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Catalog"],"summary":"Change some fields of your product","security":[{"bearerAuth":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductInput","required":[]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Product"}}}},"400":{"description":"Validation failed – see error.details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in or the token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Shared catalog products are read-only","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found (or not yours)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Catalog"],"summary":"Delete your product","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"204":{"description":"Deleted"},"401":{"description":"Not signed in or the token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Read-only","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found (or not yours)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/cart":{"get":{"tags":["Cart"],"summary":"Your cart with totals","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cart"}}}},"401":{"description":"Not signed in or the token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Cart"],"summary":"Empty your cart","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"204":{"description":"Emptied"},"401":{"description":"Not signed in or the token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/cart/items":{"post":{"tags":["Cart"],"summary":"Add a product (adds to the quantity if it is already in the cart)","security":[{"bearerAuth":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["product_id"],"properties":{"product_id":{"type":"integer"},"quantity":{"type":"integer","minimum":1,"maximum":10,"default":1}}},"example":{"product_id":1,"quantity":2}}}},"responses":{"200":{"description":"Quantity increased","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cart"}}}},"201":{"description":"Added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cart"}}}},"400":{"description":"Validation failed – see error.details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in or the token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found (or not yours)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Out of stock or not enough stock","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"More than 10 of one product","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/cart/coupon":{"post":{"tags":["Cart"],"summary":"Apply a coupon to your cart","description":"A coupon takes a fixed amount off the subtotal, never more than the subtotal. One coupon per cart: applying a second one, or the same one again, is refused with 409. Codes are case-insensitive. SAVE25 (USD 25.00 off) is valid only for carts of 1 to 10 items in total, all quantities added up; with more it is refused with 422 (coupon_cart_size). Try SAVE25.","security":[{"bearerAuth":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","maxLength":40}}},"example":{"code":"SAVE25"}}}},"responses":{"200":{"description":"Applied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cart"}}}},"400":{"description":"Validation failed – see error.details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in or the token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Cart empty (cart_empty), this coupon is already applied (coupon_already_applied) or another one is (one_coupon_only)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Unknown code (coupon_unknown), expired coupon (coupon_expired) or a cart outside the coupon's 1 to 10 items (coupon_cart_size)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Cart"],"summary":"Remove the coupon from your cart","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"204":{"description":"Removed (also when there was none)"},"401":{"description":"Not signed in or the token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/cart/items/{itemId}":{"parameters":[{"name":"itemId","in":"path","required":true,"description":"Cart item id (not the product id)","schema":{"type":"integer","minimum":1}}],"patch":{"tags":["Cart"],"summary":"Set the quantity of a cart item","security":[{"bearerAuth":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["quantity"],"properties":{"quantity":{"type":"integer","minimum":1,"maximum":10}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cart"}}}},"400":{"description":"Validation failed – see error.details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in or the token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found (or not yours)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Not enough stock","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Cart"],"summary":"Remove an item from the cart","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"204":{"description":"Removed"},"401":{"description":"Not signed in or the token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found (or not yours)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/orders":{"get":{"tags":["Orders"],"summary":"Your orders, newest first","security":[{"bearerAuth":[]},{"apiKey":[]}],"parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["awaiting_payment","paid","cancelled"]}},{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},{"name":"page_size","in":"query","schema":{"type":"integer","minimum":1,"maximum":50,"default":10}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderPage"}}}},"400":{"description":"Validation failed – see error.details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in or the token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Orders"],"summary":"Place an order from your cart (status awaiting_payment; the cart is emptied)","security":[{"bearerAuth":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["shipping"],"properties":{"shipping":{"$ref":"#/components/schemas/ShippingAddress"}}},"example":{"shipping":{"name":"Alex Example","address":"12 Harbour Street","city":"Lisbon","postcode":"1100-148","country":"PT"}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}},"headers":{"Location":{"schema":{"type":"string"}}}},"400":{"description":"Validation failed – see error.details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in or the token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Cart empty or not enough stock","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/orders/{id}":{"parameters":[{"name":"id","in":"path","required":true,"description":"Order id","schema":{"type":"integer","minimum":1}}],"get":{"tags":["Orders"],"summary":"One of your orders with items and payment attempts","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"400":{"description":"Validation failed – see error.details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in or the token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found (or not yours)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/orders/{id}/items/{itemId}":{"parameters":[{"name":"id","in":"path","required":true,"description":"Order id","schema":{"type":"integer","minimum":1}},{"name":"itemId","in":"path","required":true,"description":"Order item id","schema":{"type":"integer","minimum":1}}],"delete":{"tags":["Orders"],"summary":"Remove an item from an order that is awaiting payment; totals are recalculated","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"401":{"description":"Not signed in or the token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found (or not yours)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Order is paid, or it is the last item","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/orders/{id}/cancel":{"parameters":[{"name":"id","in":"path","required":true,"description":"Order id","schema":{"type":"integer","minimum":1}}],"post":{"tags":["Orders"],"summary":"Cancel an order that is awaiting payment","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"401":{"description":"Not signed in or the token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found (or not yours)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Not cancellable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/orders/{id}/reorder":{"parameters":[{"name":"id","in":"path","required":true,"description":"Order id","schema":{"type":"integer","minimum":1}}],"post":{"tags":["Orders"],"summary":"Put the items of a past order back into the cart","description":"Respects stock and the limit of 10 per product. The response lists what was added and what was skipped (out of stock, no longer sold, fewer left than before). 409 when nothing could be added.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"Items added; `added`, `skipped` and the new `cart`","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Not signed in or the token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found (or not yours)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Nothing could be added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/orders/{id}/pay":{"parameters":[{"name":"id","in":"path","required":true,"description":"Order id","schema":{"type":"integer","minimum":1}}],"post":{"tags":["Orders"],"summary":"Pay for an order with a test card","description":"Only the numbers from GET /api/test-cards are accepted. The card decides the outcome. Every attempt is stored as a payment.","security":[{"bearerAuth":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentInput"},"example":{"card_number":"4242 4242 4242 4242","expiry_month":12,"expiry_year":2030,"cvc":"123","cardholder":"Alex Example"}}}},"responses":{"200":{"description":"Paid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"400":{"description":"Validation failed – see error.details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in or the token expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Declined: error.code is card_declined, insufficient_funds, expired_card or incorrect_cvc","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found (or not yours)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Order is not awaiting payment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Not a test card","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/test-cards":{"get":{"tags":["Service"],"summary":"Test cards and their outcomes","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string"},"cards":{"type":"array","items":{"$ref":"#/components/schemas/TestCard"}}}}}}}}}},"/api/countries":{"get":{"tags":["Service"],"summary":"Delivery countries","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"}}}}}}}}}},"/api/health":{"get":{"tags":["Service"],"summary":"Liveness","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"database":{"type":"string"}}}}}}}}},"/api/keys":{"get":{"tags":["Keys"],"summary":"Your API keys (without the keys themselves)","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}},"limit":{"type":"integer","example":5}}}}}},"401":{"description":"No token or key, or an invalid or expired one","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Keys"],"summary":"Create an API key – the key is in this response only","description":"Send it later as `X-API-Key: <key>`. Needs a bearer token: a key cannot create keys (403). Up to 5 keys.","security":[{"bearerAuth":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","pattern":"^[A-Za-z0-9 _.-]{1,40}$","default":"My key"}}},"example":{"name":"Postman"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ApiKey"},{"type":"object","properties":{"key":{"type":"string","example":"shelf_Xb3…"},"note":{"type":"string"}}}]}}},"headers":{"Location":{"schema":{"type":"string"}}}},"400":{"description":"Invalid name","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No token or key, or an invalid or expired one","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Called with an API key instead of a token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"You already have 5 keys","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/keys/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"delete":{"tags":["Keys"],"summary":"Revoke a key: it stops working at once","security":[{"bearerAuth":[]}],"responses":{"204":{"description":"Revoked"},"401":{"description":"No token or key, or an invalid or expired one","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Called with an API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such key of yours","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tasks":{"get":{"tags":["Tasks"],"summary":"Practice tasks of the API course, with your code for each task you have done","description":"Type the code into the lesson. A code is personal: it is computed over your account, so a code from someone else does not pass.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Task"}}}}}}},"401":{"description":"No token or key, or an invalid or expired one","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"},"apiKey":{"type":"apiKey","in":"header","name":"X-API-Key"}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"validation_failed"},"message":{"type":"string"},"details":{"type":"array","items":{"type":"object"}}}},"trace_id":{"type":"string","example":"4bf92f3577b34da6a3ce929d0e0e4736"}}},"Credentials":{"type":"object","required":["username","password"],"properties":{"username":{"type":"string","pattern":"^[A-Za-z0-9_]{3,15}$"},"password":{"type":"string","minLength":8,"maxLength":72}}},"User":{"type":"object","properties":{"id":{"type":"integer"},"username":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}},"AuthResult":{"type":"object","properties":{"token":{"type":"string"},"user":{"$ref":"#/components/schemas/User"}}},"Me":{"allOf":[{"$ref":"#/components/schemas/User"},{"type":"object","properties":{"stats":{"type":"object","properties":{"orders":{"type":"integer"},"cart_items":{"type":"integer"},"own_products":{"type":"integer"}}}}}]},"Category":{"type":"object","properties":{"slug":{"type":"string","example":"kettles"},"name":{"type":"string"},"count":{"type":"integer"}}},"Product":{"type":"object","properties":{"id":{"type":"integer"},"sku":{"type":["string","null"]},"name":{"type":"string"},"brand":{"type":"string"},"category":{"type":"string"},"price":{"type":"number","description":"Amount in currency units, two decimals","example":49},"currency":{"type":"string","example":"USD"},"stock":{"type":"integer"},"in_stock":{"type":"boolean"},"free_shipping":{"type":"boolean"},"rating":{"type":["number","null"]},"reviews":{"type":"integer"},"description":{"type":"string"},"specs":{"type":"object","additionalProperties":{"type":"string"}},"swatch":{"type":"string","description":"Colour of the product tile in the UI"},"image":{"type":"string","nullable":true,"description":"Path of the product illustration, or null when the product has none yet"},"own":{"type":"boolean","description":"You created this product"}}},"ProductInput":{"type":"object","required":["name","brand","category","price"],"properties":{"name":{"type":"string","minLength":2,"maxLength":80},"brand":{"type":"string","minLength":1,"maxLength":40},"category":{"type":"string","description":"A slug from GET /api/categories"},"price":{"type":"number","exclusiveMinimum":0,"maximum":100000},"stock":{"type":"integer","minimum":0,"maximum":999,"default":10},"description":{"type":"string","maxLength":500},"free_shipping":{"type":"boolean","default":false}}},"ProductPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Product"}},"page":{"type":"integer"},"page_size":{"type":"integer"},"total":{"type":"integer"},"total_pages":{"type":"integer"}}},"CartItem":{"type":"object","properties":{"id":{"type":"integer"},"quantity":{"type":"integer"},"line_total":{"type":"number","description":"Amount in currency units, two decimals","example":49},"product":{"$ref":"#/components/schemas/Product"}}},"Cart":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CartItem"}},"count":{"type":"integer"},"subtotal":{"type":"number","description":"Amount in currency units, two decimals","example":49},"coupon":{"type":["object","null"],"properties":{"code":{"type":"string","example":"SAVE25"},"label":{"type":"string"}}},"discount":{"type":"number","description":"Taken off by the coupon: at most the subtotal; 0 without a coupon","example":49},"shipping":{"type":"number","description":"USD 4.99, free from USD 50.00 (subtotal before the discount) or when every item ships free","example":49},"total":{"type":"number","description":"subtotal − discount + shipping","example":49},"currency":{"type":"string"},"free_shipping_from":{"type":"number","description":"Amount in currency units, two decimals","example":49}}},"ShippingAddress":{"type":"object","required":["name","address","city","postcode","country"],"description":"Use made-up details – this is a practice shop.","properties":{"name":{"type":"string","minLength":2,"maxLength":60},"address":{"type":"string","minLength":3,"maxLength":120},"city":{"type":"string","minLength":2,"maxLength":60},"postcode":{"type":"string","minLength":2,"maxLength":12,"pattern":"^[A-Za-z0-9 -]+$"},"country":{"type":"string","minLength":2,"maxLength":2,"description":"Code from GET /api/countries"}}},"OrderItem":{"type":"object","properties":{"id":{"type":"integer"},"product_id":{"type":["integer","null"]},"name":{"type":"string"},"unit_price":{"type":"number","description":"Amount in currency units, two decimals","example":49},"quantity":{"type":"integer"},"line_total":{"type":"number","description":"Amount in currency units, two decimals","example":49}}},"Payment":{"type":"object","properties":{"id":{"type":"integer"},"result":{"type":"string","enum":["succeeded","declined"]},"decline_code":{"type":["string","null"]},"card_last4":{"type":"string"},"amount":{"type":"number","description":"Amount in currency units, two decimals","example":49},"currency":{"type":"string"},"trace_id":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}},"OrderSummary":{"type":"object","properties":{"id":{"type":"integer"},"number":{"type":"string","example":"SH-100042"},"status":{"type":"string","enum":["awaiting_payment","paid","cancelled"]},"subtotal":{"type":"number","description":"Amount in currency units, two decimals","example":49},"discount":{"type":"number","description":"Amount in currency units, two decimals","example":49},"coupon_code":{"type":["string","null"]},"shipping":{"type":"number","description":"Amount in currency units, two decimals","example":49},"total":{"type":"number","description":"Amount in currency units, two decimals","example":49},"currency":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"paid_at":{"type":["string","null"],"format":"date-time"},"delivery_by":{"type":["string","null"],"format":"date"},"card_last4":{"type":["string","null"]},"item_count":{"type":"integer"}}},"Order":{"allOf":[{"$ref":"#/components/schemas/OrderSummary"},{"type":"object","properties":{"shipping_address":{"$ref":"#/components/schemas/ShippingAddress"},"items":{"type":"array","items":{"$ref":"#/components/schemas/OrderItem"}},"payments":{"type":"array","items":{"$ref":"#/components/schemas/Payment"}}}}]},"OrderPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/OrderSummary"}},"page":{"type":"integer"},"page_size":{"type":"integer"},"total":{"type":"integer"},"total_pages":{"type":"integer"}}},"PaymentInput":{"type":"object","required":["card_number","expiry_month","expiry_year","cvc","cardholder"],"properties":{"card_number":{"type":"string","description":"A test card number; spaces allowed"},"expiry_month":{"type":"integer","minimum":1,"maximum":12},"expiry_year":{"type":"integer","description":"Two or four digits, in the future"},"cvc":{"type":"string","pattern":"^[0-9]{3}$"},"cardholder":{"type":"string","pattern":"^[A-Za-z][A-Za-z .'-]{1,49}$"}}},"TestCard":{"type":"object","properties":{"number":{"type":"string"},"brand":{"type":"string"},"outcome":{"type":"string"},"label":{"type":"string"}}},"ApiKey":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"prefix":{"type":"string","example":"shelf_Xb3k"},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":["string","null"],"format":"date-time"}}},"Task":{"type":"object","properties":{"id":{"type":"string","example":"crud-chain"},"lesson":{"type":"string","example":"C6.4"},"title":{"type":"string"},"goal":{"type":"string"},"done":{"type":"boolean"},"code":{"type":["string","null"],"example":"API-7F3K-Q9WM"},"verifiable":{"type":"boolean","description":"False for an account not opened from a lesson: the platform cannot check its codes"}}}}}}