openapi: 3.0.3 info: title: 'Dokio API Documentation' description: 'REST API for the Dokio multi-vendor marketplace platform.' version: 1.0.0 servers: - url: 'https://dokio.shop' tags: - name: Authentication description: 'Register, log in, verify email, and manage your session.' - name: Users description: 'Retrieve user accounts and related store data.' - name: Stores description: 'Create and manage vendor stores.' - name: Products description: 'Browse and manage marketplace products.' - name: Categories description: 'Manage product categories.' - name: Enums description: 'Lookup values used by other endpoints (e.g. store industries).' - name: Checkout description: 'Guest COD checkout.' - name: Endpoints description: '' - name: Orders description: 'Vendor order management.' - name: 'Product Options' description: 'Manage product options.' - name: 'Product Variants' description: 'Manage product variants.' - name: 'Storefront rows' description: 'Public homepage sections for a store.' - name: 'Storefront slides' description: 'Manage slides for homepage slider sections.' components: securitySchemes: default: type: http scheme: bearer description: " Obtain a token by calling **POST /api/login** or **POST /api/register**.\n Tokens are Laravel Sanctum personal access tokens and expire after one month.\n Pass the token as: `Authorization: Bearer {token}`.\n Email verification is required before login succeeds." security: - default: [] paths: /api/me: get: summary: 'Current user' operationId: currentUser description: 'Return the authenticated user, including their store when available.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'User fetched successfully' data: type: user id: '1' attributes: name: 'Jane Doe' email: jane@example.com relationships: store: data: id: '1' type: store links: self: 'https://api.dokio.local/api/v1/stores/1' properties: status: type: integer example: 200 message: type: string example: 'User fetched successfully' data: type: object properties: type: type: string example: user id: type: string example: '1' attributes: type: object properties: name: type: string example: 'Jane Doe' email: type: string example: jane@example.com relationships: type: object properties: store: type: object properties: data: type: object properties: id: type: string example: '1' type: type: string example: store links: type: object properties: self: type: string example: 'https://api.dokio.local/api/v1/stores/1' tags: - Authentication /api/login: post: summary: 'Log in' operationId: logIn description: 'Authenticate with email and password. Returns a Sanctum token valid for one month. The account must be email-verified.' parameters: [] responses: 200: description: 'Successful login' content: application/json: schema: type: object example: status: 200 message: 'Logged in successfully' data: token: 1|abcXYZ... verified: true properties: status: type: integer example: 200 message: type: string example: 'Logged in successfully' data: type: object properties: token: type: string example: 1|abcXYZ... verified: type: boolean example: true 400: description: 'Email not verified' content: application/json: schema: type: object example: errors: - status: '400' code: email.not_verified detail: 'Email not verified' properties: errors: type: array example: - status: '400' code: email.not_verified detail: 'Email not verified' items: type: object properties: status: type: string example: '400' code: type: string example: email.not_verified detail: type: string example: 'Email not verified' 401: description: 'Invalid credentials' content: application/json: schema: type: object example: errors: - status: '401' code: credentials.invalid detail: 'Invalid credentials' properties: errors: type: array example: - status: '401' code: credentials.invalid detail: 'Invalid credentials' items: type: object properties: status: type: string example: '401' code: type: string example: credentials.invalid detail: type: string example: 'Invalid credentials' tags: - Authentication requestBody: required: true content: application/json: schema: type: object properties: email: type: string description: 'The user email address. Must be a valid email address.' example: jane@example.com password: type: string description: 'The account password (minimum 8 characters). Must be at least 8 characters.' example: password123 required: - email - password security: [] /api/register: post: summary: Register operationId: register description: 'Create a new user account and receive a Sanctum token. A verification email is sent automatically.' parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: status: 201 message: 'User registered successfully' data: token: 1|abcXYZ... verified: false properties: status: type: integer example: 201 message: type: string example: 'User registered successfully' data: type: object properties: token: type: string example: 1|abcXYZ... verified: type: boolean example: false tags: - Authentication requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'The user full name. Must not be greater than 255 characters.' example: 'Jane Doe' email: type: string description: 'A unique email address for the account. Must be a valid email address.' example: jane@example.com password: type: string description: 'The account password (minimum 8 characters). Must be at least 8 characters.' example: password123 required: - name - email - password security: [] /api/auth/google: post: summary: 'Register or log in with Google' operationId: registerOrLogInWithGoogle description: 'Authenticate with a Google ID token from the client. Creates an account if needed, or links Google to an existing email account. Returns a Sanctum token valid for one month.' parameters: [] responses: 200: description: 'Successful Google authentication' content: application/json: schema: type: object example: status: 200 message: 'Logged in successfully' data: token: 1|abcXYZ... verified: true properties: status: type: integer example: 200 message: type: string example: 'Logged in successfully' data: type: object properties: token: type: string example: 1|abcXYZ... verified: type: boolean example: true 400: description: 'Google account email is not verified' content: application/json: schema: type: object example: errors: - status: '400' code: google.email_not_verified detail: 'Google email not verified' properties: errors: type: array example: - status: '400' code: google.email_not_verified detail: 'Google email not verified' items: type: object properties: status: type: string example: '400' code: type: string example: google.email_not_verified detail: type: string example: 'Google email not verified' 401: description: 'Invalid or expired ID token' content: application/json: schema: type: object example: errors: - status: '401' code: google.invalid_id_token detail: 'Invalid Google token' properties: errors: type: array example: - status: '401' code: google.invalid_id_token detail: 'Invalid Google token' items: type: object properties: status: type: string example: '401' code: type: string example: google.invalid_id_token detail: type: string example: 'Invalid Google token' tags: - Authentication requestBody: required: true content: application/json: schema: type: object properties: id_token: type: string description: 'The Google ID token obtained from Google Sign-In on the client.' example: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9... required: - id_token security: [] /api/auth/google/code: post: summary: 'Register or log in with a Google authorization code' operationId: registerOrLogInWithAGoogleAuthorizationCode description: 'Exchange a one-time Google popup code for an ID token, then authenticate the user. The request Origin must match FRONTEND_URL and X-Requested-With must be XMLHttpRequest. Returns a Sanctum token valid for one month.' parameters: [] responses: 200: description: 'Successful Google authentication' content: application/json: schema: type: object example: status: 200 message: 'Logged in successfully' data: token: 1|abcXYZ... verified: true properties: status: type: integer example: 200 message: type: string example: 'Logged in successfully' data: type: object properties: token: type: string example: 1|abcXYZ... verified: type: boolean example: true 401: description: 'Invalid or expired authorization code' content: application/json: schema: type: object example: errors: - status: '401' code: google.invalid_code detail: 'Invalid Google authorization code' properties: errors: type: array example: - status: '401' code: google.invalid_code detail: 'Invalid Google authorization code' items: type: object properties: status: type: string example: '401' code: type: string example: google.invalid_code detail: type: string example: 'Invalid Google authorization code' 403: description: 'Request origin is not the configured dashboard origin' content: application/json: schema: type: object example: errors: - status: '403' code: google.invalid_origin detail: 'Invalid Google sign-in origin' properties: errors: type: array example: - status: '403' code: google.invalid_origin detail: 'Invalid Google sign-in origin' items: type: object properties: status: type: string example: '403' code: type: string example: google.invalid_origin detail: type: string example: 'Invalid Google sign-in origin' tags: - Authentication requestBody: required: true content: application/json: schema: type: object properties: code: type: string description: 'The one-time authorization code returned by the Google popup.' example: synthetic-google-authorization-code required: - code security: [] /api/forgot-password: post: summary: 'Forgot password' operationId: forgotPassword description: 'Send a password reset email if the account exists.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Password reset email sent' data: [] properties: status: type: integer example: 200 message: type: string example: 'Password reset email sent' data: type: array example: [] 404: description: '' content: application/json: schema: type: object example: errors: - status: '404' code: user.not_found detail: 'User not found' properties: errors: type: array example: - status: '404' code: user.not_found detail: 'User not found' items: type: object properties: status: type: string example: '404' code: type: string example: user.not_found detail: type: string example: 'User not found' tags: - Authentication requestBody: required: true content: application/json: schema: type: object properties: email: type: string description: 'The email address of the account to reset. Must be a valid email address.' example: jane@example.com required: - email security: [] /api/reset-password: post: summary: 'Reset password' operationId: resetPassword description: 'Set a new password using the token from the reset email. All existing tokens are revoked.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Password reset successfully' data: [] properties: status: type: integer example: 200 message: type: string example: 'Password reset successfully' data: type: array example: [] 400: description: '' content: application/json: schema: type: object example: errors: - status: '400' code: passwords.reset_failed detail: 'Failed to reset password' properties: errors: type: array example: - status: '400' code: passwords.reset_failed detail: 'Failed to reset password' items: type: object properties: status: type: string example: '400' code: type: string example: passwords.reset_failed detail: type: string example: 'Failed to reset password' tags: - Authentication requestBody: required: true content: application/json: schema: type: object properties: token: type: string description: 'The password reset token from the email link.' example: abc123resetToken email: type: string description: 'The email address of the account being reset. Must be a valid email address.' example: jane@example.com password: type: string description: 'The new password (minimum 8 characters). Must be at least 8 characters.' example: newpassword123 required: - token - email - password security: [] /api/logout: post: summary: 'Log out' operationId: logOut description: 'Revoke the current access token.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Logged out successfully' data: [] properties: status: type: integer example: 200 message: type: string example: 'Logged out successfully' data: type: array example: [] tags: - Authentication '/api/email/verify/{id}/{hash}': get: summary: 'Verify email' operationId: verifyEmail description: 'Confirm the user email address using the link from the verification email.' parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: status: 201 message: 'Email verified successfully' data: token: 1|abcXYZ... verified: true properties: status: type: integer example: 201 message: type: string example: 'Email verified successfully' data: type: object properties: token: type: string example: 1|abcXYZ... verified: type: boolean example: true 400: description: '' content: application/json: schema: oneOf: - description: '' type: object example: errors: - status: '400' code: verification.invalid_link detail: 'Invalid verification link' properties: errors: type: array example: - status: '400' code: verification.invalid_link detail: 'Invalid verification link' items: type: object properties: status: type: string example: '400' code: type: string example: verification.invalid_link detail: type: string example: 'Invalid verification link' - description: '' type: object example: errors: - status: '400' code: verification.email_already_verified detail: 'Email already verified' properties: errors: type: array example: - status: '400' code: verification.email_already_verified detail: 'Email already verified' items: type: object properties: status: type: string example: '400' code: type: string example: verification.email_already_verified detail: type: string example: 'Email already verified' tags: - Authentication security: [] parameters: - in: path name: id description: 'The user ID.' example: 1 required: true schema: type: integer - in: path name: hash description: 'SHA1 hash of the user email (from the verification link).' example: a1b2c3d4e5f6 required: true schema: type: string /api/v1/users: get: summary: 'List users' operationId: listUsers description: 'Return a paginated list of users with their stores eager-loaded.' parameters: - in: query name: page description: 'Page number for pagination.' example: 1 required: false schema: type: integer description: 'Page number for pagination.' example: 1 responses: 200: description: '' content: application/json: schema: type: object example: data: - type: user id: '51' attributes: name: 'Morgan Hirthe' email: dare.emelie@example.com relationships: store: data: id: '47' type: store links: self: 'https://dokio.shop/api/v1/users?51' included: type: store id: 47 attributes: name: 'Prof. Mina Bauch' slug: prof-mina-bauch industry: fashion logo: 'https://dokio.shop/storage/https://via.placeholder.com/640x480.png/002222?text=nostrum' color: '#007bff' createdAt: '2026-09-21T17:28:48.000000Z' updatedAt: '2026-09-21T17:28:48.000000Z' relationships: user: data: id: 51 type: user links: self: 'https://dokio.shop/api/v1/users/47' links: self: 'https://dokio.shop/api/v1/users/47' links: self: 'https://dokio.shop/api/v1/users/51' - type: user id: '52' attributes: name: 'Carey Smitham' email: sleffler@example.org relationships: store: data: id: '48' type: store links: self: 'https://dokio.shop/api/v1/users?52' included: type: store id: 48 attributes: name: 'Matilda Feeney' slug: matilda-feeney industry: food logo: 'https://dokio.shop/storage/https://via.placeholder.com/640x480.png/0088cc?text=laboriosam' color: '#007bff' createdAt: '2026-09-21T17:28:48.000000Z' updatedAt: '2026-09-21T17:28:48.000000Z' relationships: user: data: id: 52 type: user links: self: 'https://dokio.shop/api/v1/users/48' links: self: 'https://dokio.shop/api/v1/users/48' links: self: 'https://dokio.shop/api/v1/users/52' links: first: '/?page=1' last: '/?page=1' prev: null next: null meta: current_page: 1 from: 1 last_page: 1 links: - url: null label: '« Previous' page: null active: false - url: '/?page=1' label: '1' page: 1 active: true - url: null label: 'Next »' page: null active: false path: / per_page: 15 to: 2 total: 2 properties: data: type: array example: - type: user id: '51' attributes: name: 'Morgan Hirthe' email: dare.emelie@example.com relationships: store: data: id: '47' type: store links: self: 'https://dokio.shop/api/v1/users?51' included: type: store id: 47 attributes: name: 'Prof. Mina Bauch' slug: prof-mina-bauch industry: fashion logo: 'https://dokio.shop/storage/https://via.placeholder.com/640x480.png/002222?text=nostrum' color: '#007bff' createdAt: '2026-09-21T17:28:48.000000Z' updatedAt: '2026-09-21T17:28:48.000000Z' relationships: user: data: id: 51 type: user links: self: 'https://dokio.shop/api/v1/users/47' links: self: 'https://dokio.shop/api/v1/users/47' links: self: 'https://dokio.shop/api/v1/users/51' - type: user id: '52' attributes: name: 'Carey Smitham' email: sleffler@example.org relationships: store: data: id: '48' type: store links: self: 'https://dokio.shop/api/v1/users?52' included: type: store id: 48 attributes: name: 'Matilda Feeney' slug: matilda-feeney industry: food logo: 'https://dokio.shop/storage/https://via.placeholder.com/640x480.png/0088cc?text=laboriosam' color: '#007bff' createdAt: '2026-09-21T17:28:48.000000Z' updatedAt: '2026-09-21T17:28:48.000000Z' relationships: user: data: id: 52 type: user links: self: 'https://dokio.shop/api/v1/users/48' links: self: 'https://dokio.shop/api/v1/users/48' links: self: 'https://dokio.shop/api/v1/users/52' items: type: object properties: type: type: string example: user id: type: string example: '51' attributes: type: object properties: name: type: string example: 'Morgan Hirthe' email: type: string example: dare.emelie@example.com relationships: type: object properties: store: type: object properties: data: type: object properties: id: type: string example: '47' type: type: string example: store links: type: object properties: self: type: string example: 'https://dokio.shop/api/v1/users?51' included: type: object properties: type: type: string example: store id: type: integer example: 47 attributes: type: object properties: name: type: string example: 'Prof. Mina Bauch' slug: type: string example: prof-mina-bauch industry: type: string example: fashion logo: type: string example: 'https://dokio.shop/storage/https://via.placeholder.com/640x480.png/002222?text=nostrum' color: type: string example: '#007bff' createdAt: type: string example: '2026-09-21T17:28:48.000000Z' updatedAt: type: string example: '2026-09-21T17:28:48.000000Z' relationships: type: object properties: user: type: object properties: data: type: object properties: { id: { type: integer, example: 51 }, type: { type: string, example: user } } links: type: object properties: { self: { type: string, example: 'https://dokio.shop/api/v1/users/47' } } links: type: object properties: self: type: string example: 'https://dokio.shop/api/v1/users/47' links: type: object properties: self: type: string example: 'https://dokio.shop/api/v1/users/51' links: type: object properties: first: type: string example: '/?page=1' last: type: string example: '/?page=1' prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 1 links: type: array example: - url: null label: '« Previous' page: null active: false - url: '/?page=1' label: '1' page: 1 active: true - url: null label: 'Next »' page: null active: false items: type: object properties: url: type: string example: null nullable: true label: type: string example: '« Previous' page: type: string example: null nullable: true active: type: boolean example: false path: type: string example: / per_page: type: integer example: 15 to: type: integer example: 2 total: type: integer example: 2 tags: - Users '/api/v1/users/{id}': get: summary: 'Get user' operationId: getUser description: 'Return a single user by ID, including their store.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: type: user id: '53' attributes: name: 'Morgan Hirthe' email: imclaughlin@example.org createdAt: '2026-09-21T17:28:48.000000Z' updatedAt: '2026-09-21T17:28:48.000000Z' emailVerifiedAt: '2026-09-21T17:28:48.000000Z' relationships: store: data: id: '49' type: store links: self: 'http://dokio.shop/api/v1/users?53' included: type: store id: 49 attributes: name: 'Mr. Oswald Koch' slug: mr-oswald-koch industry: home logo: 'http://dokio.shop/storage/https://via.placeholder.com/640x480.png/00ee77?text=consequatur' color: '#007bff' createdAt: '2026-09-21T17:28:48.000000Z' updatedAt: '2026-09-21T17:28:48.000000Z' relationships: user: data: id: 53 type: user links: self: 'http://dokio.shop/api/v1/users/49' links: self: 'http://dokio.shop/api/v1/users/49' links: self: 'http://dokio.shop/api/v1/users/53' properties: data: type: object properties: type: type: string example: user id: type: string example: '53' attributes: type: object properties: name: type: string example: 'Morgan Hirthe' email: type: string example: imclaughlin@example.org createdAt: type: string example: '2026-09-21T17:28:48.000000Z' updatedAt: type: string example: '2026-09-21T17:28:48.000000Z' emailVerifiedAt: type: string example: '2026-09-21T17:28:48.000000Z' relationships: type: object properties: store: type: object properties: data: type: object properties: id: type: string example: '49' type: type: string example: store links: type: object properties: self: type: string example: 'http://dokio.shop/api/v1/users?53' included: type: object properties: type: type: string example: store id: type: integer example: 49 attributes: type: object properties: name: type: string example: 'Mr. Oswald Koch' slug: type: string example: mr-oswald-koch industry: type: string example: home logo: type: string example: 'http://dokio.shop/storage/https://via.placeholder.com/640x480.png/00ee77?text=consequatur' color: type: string example: '#007bff' createdAt: type: string example: '2026-09-21T17:28:48.000000Z' updatedAt: type: string example: '2026-09-21T17:28:48.000000Z' relationships: type: object properties: user: type: object properties: data: type: object properties: id: { type: integer, example: 53 } type: { type: string, example: user } links: type: object properties: self: { type: string, example: 'http://dokio.shop/api/v1/users/49' } links: type: object properties: self: type: string example: 'http://dokio.shop/api/v1/users/49' links: type: object properties: self: type: string example: 'http://dokio.shop/api/v1/users/53' tags: - Users parameters: - in: path name: id description: 'The ID of the user.' example: 1 required: true schema: type: integer - in: path name: user description: 'The user ID.' example: 1 required: true schema: type: integer /api/v1/stores: post: summary: 'Create store' operationId: createStore description: 'Create a store for the authenticated user. Accepts `multipart/form-data` when uploading a logo.' parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: status: 201 message: 'Store created successfully' data: type: store id: 1 attributes: name: 'Acme Fashion' slug: acme-fashion industry: fashion logo: null color: '#007bff' properties: status: type: integer example: 201 message: type: string example: 'Store created successfully' data: type: object properties: type: type: string example: store id: type: integer example: 1 attributes: type: object properties: name: type: string example: 'Acme Fashion' slug: type: string example: acme-fashion industry: type: string example: fashion logo: type: string example: null nullable: true color: type: string example: '#007bff' tags: - Stores requestBody: required: true content: multipart/form-data: schema: type: object properties: name: type: string description: 'The store display name. Must not be greater than 255 characters.' example: 'Acme Fashion' industry: type: string description: 'Store industry. Allowed: fashion, electronics, food, beauty, home, other.' example: fashion enum: - fashion - electronics - food - beauty - home - other logo: type: string format: binary description: 'Optional store logo (jpeg, png, jpg, gif, or svg; max 2MB). Raster logos have whitespace trimmed with GD. SVG uploads are sanitized and their viewBox is tightened around artwork (full-canvas backgrounds ignored); they remain SVG. Must be an image. Must not be greater than 2048 kilobytes.' color: type: string description: 'Must not be greater than 255 characters.' example: b required: - name - industry - color '/api/v1/stores/{slug}': put: summary: 'Update store' operationId: updateStore description: 'Update an existing store. Accepts `multipart/form-data` when uploading a new logo.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Store updated successfully' data: type: store id: 1 attributes: name: 'Acme Fashion' slug: acme-fashion industry: fashion color: '#007bff' properties: status: type: integer example: 200 message: type: string example: 'Store updated successfully' data: type: object properties: type: type: string example: store id: type: integer example: 1 attributes: type: object properties: name: type: string example: 'Acme Fashion' slug: type: string example: acme-fashion industry: type: string example: fashion color: type: string example: '#007bff' tags: - Stores requestBody: required: true content: multipart/form-data: schema: type: object properties: name: type: string description: 'The store display name. Must not be greater than 255 characters.' example: 'Acme Fashion' industry: type: string description: 'Store industry. Allowed: fashion, electronics, food, beauty, home, other.' example: fashion enum: - fashion - electronics - food - beauty - home - other logo: type: string format: binary description: 'Optional new store logo (jpeg, png, jpg, gif, or svg; max 2MB). Replaces the existing logo. Raster logos have whitespace trimmed with GD. SVG uploads are sanitized and their viewBox is tightened around artwork (full-canvas backgrounds ignored); they remain SVG. Must be an image. Must not be greater than 2048 kilobytes.' color: type: string description: 'Must not be greater than 255 characters.' example: b required: - name - industry - color delete: summary: 'Delete store' operationId: deleteStore description: 'Remove a store when it has no related catalog data or orders.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Store deleted successfully' data: type: store id: 1 attributes: name: 'Acme Fashion' slug: acme-fashion properties: status: type: integer example: 200 message: type: string example: 'Store deleted successfully' data: type: object properties: type: type: string example: store id: type: integer example: 1 attributes: type: object properties: name: type: string example: 'Acme Fashion' slug: type: string example: acme-fashion 403: description: '' content: application/json: schema: type: object example: errors: - status: '403' code: store.forbidden detail: 'You are not authorized to delete this store' properties: errors: type: array example: - status: '403' code: store.forbidden detail: 'You are not authorized to delete this store' items: type: object properties: status: type: string example: '403' code: type: string example: store.forbidden detail: type: string example: 'You are not authorized to delete this store' 422: description: '' content: application/json: schema: type: object example: errors: - status: '422' code: store.has_related_records detail: 'Cannot delete store because it has related products, categories, options, or orders' properties: errors: type: array example: - status: '422' code: store.has_related_records detail: 'Cannot delete store because it has related products, categories, options, or orders' items: type: object properties: status: type: string example: '422' code: type: string example: store.has_related_records detail: type: string example: 'Cannot delete store because it has related products, categories, options, or orders' tags: - Stores get: summary: 'Get store' operationId: getStore description: 'Return a single store by ID. This endpoint is public.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: type: store id: 50 attributes: name: 'Morgan Hirthe' slug: morgan-hirthe industry: fashion logo: 'http://dokio.shop/storage/https://via.placeholder.com/640x480.png/0077ff?text=qui' color: '#007bff' createdAt: '2026-09-21T17:28:48.000000Z' updatedAt: '2026-09-21T17:28:48.000000Z' relationships: user: data: id: 54 type: user links: self: 'http://dokio.shop/api/v1/users/50' links: self: 'http://dokio.shop/api/v1/users/50' properties: data: type: object properties: type: type: string example: store id: type: integer example: 50 attributes: type: object properties: name: type: string example: 'Morgan Hirthe' slug: type: string example: morgan-hirthe industry: type: string example: fashion logo: type: string example: 'http://dokio.shop/storage/https://via.placeholder.com/640x480.png/0077ff?text=qui' color: type: string example: '#007bff' createdAt: type: string example: '2026-09-21T17:28:48.000000Z' updatedAt: type: string example: '2026-09-21T17:28:48.000000Z' relationships: type: object properties: user: type: object properties: data: type: object properties: id: type: integer example: 54 type: type: string example: user links: type: object properties: self: type: string example: 'http://dokio.shop/api/v1/users/50' links: type: object properties: self: type: string example: 'http://dokio.shop/api/v1/users/50' tags: - Stores security: [] parameters: - in: path name: slug description: 'The slug of the store.' example: backs required: true schema: type: string - in: path name: store description: 'The store ID.' example: 1 required: true schema: type: integer '/api/v1/users/{id}/store': get: summary: "Get user's store" operationId: getUsersStore description: 'Return the store associated with a user. This endpoint is public.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: - type: store id: 51 attributes: name: 'Rowan Gulgowski' slug: rowan-gulgowski industry: beauty logo: 'http://dokio.shop/storage/https://via.placeholder.com/640x480.png/00bb11?text=sunt' color: '#007bff' createdAt: '2026-09-21T17:28:49.000000Z' updatedAt: '2026-09-21T17:28:49.000000Z' relationships: user: data: id: 55 type: user links: self: 'http://dokio.shop/api/v1/users/51' links: self: 'http://dokio.shop/api/v1/users/51' - type: store id: 52 attributes: name: 'Mr. Gilbert Swift V' slug: mr-gilbert-swift-v industry: fashion logo: 'http://dokio.shop/storage/https://via.placeholder.com/640x480.png/005500?text=quidem' color: '#007bff' createdAt: '2026-09-21T17:28:49.000000Z' updatedAt: '2026-09-21T17:28:49.000000Z' relationships: user: data: id: 56 type: user links: self: 'http://dokio.shop/api/v1/users/52' links: self: 'http://dokio.shop/api/v1/users/52' properties: data: type: array example: - type: store id: 51 attributes: name: 'Rowan Gulgowski' slug: rowan-gulgowski industry: beauty logo: 'http://dokio.shop/storage/https://via.placeholder.com/640x480.png/00bb11?text=sunt' color: '#007bff' createdAt: '2026-09-21T17:28:49.000000Z' updatedAt: '2026-09-21T17:28:49.000000Z' relationships: user: data: id: 55 type: user links: self: 'http://dokio.shop/api/v1/users/51' links: self: 'http://dokio.shop/api/v1/users/51' - type: store id: 52 attributes: name: 'Mr. Gilbert Swift V' slug: mr-gilbert-swift-v industry: fashion logo: 'http://dokio.shop/storage/https://via.placeholder.com/640x480.png/005500?text=quidem' color: '#007bff' createdAt: '2026-09-21T17:28:49.000000Z' updatedAt: '2026-09-21T17:28:49.000000Z' relationships: user: data: id: 56 type: user links: self: 'http://dokio.shop/api/v1/users/52' links: self: 'http://dokio.shop/api/v1/users/52' items: type: object properties: type: type: string example: store id: type: integer example: 51 attributes: type: object properties: name: type: string example: 'Rowan Gulgowski' slug: type: string example: rowan-gulgowski industry: type: string example: beauty logo: type: string example: 'http://dokio.shop/storage/https://via.placeholder.com/640x480.png/00bb11?text=sunt' color: type: string example: '#007bff' createdAt: type: string example: '2026-09-21T17:28:49.000000Z' updatedAt: type: string example: '2026-09-21T17:28:49.000000Z' relationships: type: object properties: user: type: object properties: data: type: object properties: id: type: integer example: 55 type: type: string example: user links: type: object properties: self: type: string example: 'http://dokio.shop/api/v1/users/51' links: type: object properties: self: type: string example: 'http://dokio.shop/api/v1/users/51' tags: - Stores security: [] parameters: - in: path name: id description: 'The ID of the user.' example: 1 required: true schema: type: integer - in: path name: user description: 'The user ID.' example: 1 required: true schema: type: integer '/api/v1/stores/{store_slug}/products': get: summary: 'List store products' operationId: listStoreProducts description: 'Return all products belonging to a store. This endpoint is public.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Products retrieved successfully' data: - type: product id: 1 attributes: name: 'Classic Tee' price: '29.99' image: 'https://api.dokio.local/storage/product/tee.jpg' status: 'in stock' relationships: product_variants: data: id: 1 type: product_variant links: self: 'https://api.example.com/products/1' properties: status: type: integer example: 200 message: type: string example: 'Products retrieved successfully' data: type: array example: - type: product id: 1 attributes: name: 'Classic Tee' price: '29.99' image: 'https://api.dokio.local/storage/product/tee.jpg' status: 'in stock' relationships: product_variants: data: id: 1 type: product_variant links: self: 'https://api.example.com/products/1' items: type: object properties: type: type: string example: product id: type: integer example: 1 attributes: type: object properties: name: type: string example: 'Classic Tee' price: type: string example: '29.99' image: type: string example: 'https://api.dokio.local/storage/product/tee.jpg' status: type: string example: 'in stock' relationships: type: object properties: product_variants: type: object properties: data: type: object properties: id: type: integer example: 1 type: type: string example: product_variant links: type: object properties: self: type: string example: 'https://api.example.com/products/1' tags: - Products security: [] parameters: - in: path name: store_slug description: 'The slug of the store.' example: backs required: true schema: type: string - in: path name: store description: 'The store ID.' example: 1 required: true schema: type: integer '/api/v1/stores/{store_slug}/products/{slug}': get: summary: 'Get a store product' operationId: getAStoreProduct description: 'Return a single product belonging to a store. This endpoint is public.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Product retrieved successfully' data: type: product id: 1 attributes: name: 'Classic Tee' slug: classic-tee description_html: '

Soft cotton t-shirt in classic fit.

' relationships: product_variants: data: id: 1 type: product_variant includes: product_variants: data: id: 1 type: product_variant attributes: price: '29.99' stock: 100 sku: TSHIRT-BLUE-M image: 'https://api.dokio.local/storage/product/tee.jpg' status: 'in stock' links: self: 'https://api.example.com/products/1' properties: status: type: integer example: 200 message: type: string example: 'Product retrieved successfully' data: type: object properties: type: type: string example: product id: type: integer example: 1 attributes: type: object properties: name: type: string example: 'Classic Tee' slug: type: string example: classic-tee description_html: type: string example: '

Soft cotton t-shirt in classic fit.

' relationships: type: object properties: product_variants: type: object properties: data: type: object properties: id: type: integer example: 1 type: type: string example: product_variant includes: type: object properties: product_variants: type: object properties: data: type: object properties: id: type: integer example: 1 type: type: string example: product_variant attributes: type: object properties: price: { type: string, example: '29.99' } stock: { type: integer, example: 100 } sku: { type: string, example: TSHIRT-BLUE-M } image: { type: string, example: 'https://api.dokio.local/storage/product/tee.jpg' } status: { type: string, example: 'in stock' } links: type: object properties: self: type: string example: 'https://api.example.com/products/1' tags: - Products security: [] parameters: - in: path name: store_slug description: 'The slug of the store.' example: backs required: true schema: type: string - in: path name: slug description: 'The slug of the product.' example: sds required: true schema: type: string - in: path name: store description: 'The store slug.' example: acme-fashion required: true schema: type: string - in: path name: product description: 'The product slug.' example: classic-tee required: true schema: type: string /api/v1/products: get: summary: 'List products' operationId: listProducts description: 'Return a paginated list of products. Supports filtering by status, search, dates, and includes.' parameters: - in: query name: status description: 'Filter by status. Comma-separated for multiple values.' example: active required: false schema: type: string description: 'Filter by status. Comma-separated for multiple values.' example: active - in: query name: search description: 'Search product name. Use `*` as a wildcard (e.g. `*shirt*`).' example: '*shirt*' required: false schema: type: string description: 'Search product name. Use `*` as a wildcard (e.g. `*shirt*`).' example: '*shirt*' - in: query name: createdAt description: 'Filter by created date (`Y-m-d`) or range (`start,end`).' example: '2024-01-01' required: false schema: type: string description: 'Filter by created date (`Y-m-d`) or range (`start,end`).' example: '2024-01-01' - in: query name: updatedAt description: 'Filter by updated date (`Y-m-d`) or range (`start,end`).' example: '2024-01-01,2024-12-31' required: false schema: type: string description: 'Filter by updated date (`Y-m-d`) or range (`start,end`).' example: '2024-01-01,2024-12-31' - in: query name: include description: 'Eager-load relations (e.g. `product_variants`).' example: product_variants required: false schema: type: string description: 'Eager-load relations (e.g. `product_variants`).' example: product_variants - in: query name: page description: 'Page number for pagination.' example: 1 required: false schema: type: integer description: 'Page number for pagination.' example: 1 responses: 200: description: 'Paginated products' content: application/json: schema: type: object example: status: 200 message: 'Products retrieved successfully' data: - type: product id: 1 attributes: name: 'Classic Tee' slug: classic-tee createdAt: '2024-01-15T10:00:00.000000Z' updatedAt: '2024-01-15T10:00:00.000000Z' status: 'in stock' relationships: user: data: id: 1 type: user links: self: 'https://api.dokio.local/api/v1/products/1' properties: status: type: integer example: 200 message: type: string example: 'Products retrieved successfully' data: type: array example: - type: product id: 1 attributes: name: 'Classic Tee' slug: classic-tee createdAt: '2024-01-15T10:00:00.000000Z' updatedAt: '2024-01-15T10:00:00.000000Z' status: 'in stock' relationships: user: data: id: 1 type: user links: self: 'https://api.dokio.local/api/v1/products/1' items: type: object properties: type: type: string example: product id: type: integer example: 1 attributes: type: object properties: name: type: string example: 'Classic Tee' slug: type: string example: classic-tee createdAt: type: string example: '2024-01-15T10:00:00.000000Z' updatedAt: type: string example: '2024-01-15T10:00:00.000000Z' status: type: string example: 'in stock' relationships: type: object properties: user: type: object properties: data: type: object properties: id: type: integer example: 1 type: type: string example: user links: type: object properties: self: type: string example: 'https://api.dokio.local/api/v1/products/1' tags: - Products post: summary: 'Create product' operationId: createProduct description: "Create a product for the authenticated user's store. Send required `description_html` (max 100,000 UTF-8 bytes). HTML is sanitized. Mutation responses omit it; fetch a detail route to read it." parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: status: 201 message: 'Product created successfully' data: type: product id: 1 attributes: name: 'Classic Tee' slug: classic-tee createdAt: '2024-01-15T10:00:00.000000Z' updatedAt: '2024-01-15T10:00:00.000000Z' status: null properties: status: type: integer example: 201 message: type: string example: 'Product created successfully' data: type: object properties: type: type: string example: product id: type: integer example: 1 attributes: type: object properties: name: type: string example: 'Classic Tee' slug: type: string example: classic-tee createdAt: type: string example: '2024-01-15T10:00:00.000000Z' updatedAt: type: string example: '2024-01-15T10:00:00.000000Z' status: type: string example: null nullable: true 422: description: '' content: application/json: schema: type: object example: errors: - status: '422' code: validation.max_utf8_bytes detail: 'This field exceeds the maximum allowed value.' source: pointer: /description_html meta: field: description_html properties: errors: type: array example: - status: '422' code: validation.max_utf8_bytes detail: 'This field exceeds the maximum allowed value.' source: pointer: /description_html meta: field: description_html items: type: object properties: status: type: string example: '422' code: type: string example: validation.max_utf8_bytes detail: type: string example: 'This field exceeds the maximum allowed value.' source: type: object properties: pointer: type: string example: /description_html meta: type: object properties: field: type: string example: description_html tags: - Products requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'The product name. Must not be greater than 255 characters.' example: 'Classic Tee' description_html: type: string description: 'Required canonical HTML description. Sanitized against the product allowlist. Maximum 100,000 UTF-8 bytes.' example: '

تفاصيل المنتج

قطن ناعم — Soft cotton.

' category_id: type: string description: "ID of a category owned by the authenticated user's store." example: 1 required: - name - description_html - category_id '/api/v1/products/{slug}': get: summary: 'Get product' operationId: getProduct description: 'Return a single product. Pass `include=author` to embed the owning user.' parameters: - in: query name: include description: 'Optional related resources to include. Use `author` for the product owner.' example: author required: false schema: type: string description: 'Optional related resources to include. Use `author` for the product owner.' example: author responses: 200: description: '' content: application/json: schema: type: object example: type: product id: 1 attributes: name: 'Classic Tee' slug: classic-tee description_html: '

Soft cotton t-shirt in classic fit.

' createdAt: '2024-01-15T10:00:00.000000Z' updatedAt: '2024-01-15T10:00:00.000000Z' relationships: user: data: id: 1 type: user category: data: id: 1 type: category variants: data: - id: 1 type: variant - id: 2 type: variant links: self: 'https://api.dokio.local/api/v1/products/1' properties: type: type: string example: product id: type: integer example: 1 attributes: type: object properties: name: type: string example: 'Classic Tee' slug: type: string example: classic-tee description_html: type: string example: '

Soft cotton t-shirt in classic fit.

' createdAt: type: string example: '2024-01-15T10:00:00.000000Z' updatedAt: type: string example: '2024-01-15T10:00:00.000000Z' relationships: type: object properties: user: type: object properties: data: type: object properties: id: type: integer example: 1 type: type: string example: user category: type: object properties: data: type: object properties: id: type: integer example: 1 type: type: string example: category variants: type: object properties: data: type: array example: - id: 1 type: variant - id: 2 type: variant items: type: object properties: id: type: integer example: 1 type: type: string example: variant links: type: object properties: self: type: string example: 'https://api.dokio.local/api/v1/products/1' tags: - Products put: summary: 'Update product' operationId: updateProduct description: 'Replace name, HTML description, and category of an owned product identified by slug. Send required `description_html` (max 100,000 UTF-8 bytes). HTML is sanitized. Mutation responses omit it; fetch a detail route to read it. Dashboard clients may POST multipart with `_method=PUT`.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Product updated successfully' data: type: product id: 1 attributes: name: 'Classic Tee' slug: classic-tee createdAt: '2024-01-15T10:00:00.000000Z' updatedAt: '2024-01-15T10:00:00.000000Z' status: null properties: status: type: integer example: 200 message: type: string example: 'Product updated successfully' data: type: object properties: type: type: string example: product id: type: integer example: 1 attributes: type: object properties: name: type: string example: 'Classic Tee' slug: type: string example: classic-tee createdAt: type: string example: '2024-01-15T10:00:00.000000Z' updatedAt: type: string example: '2024-01-15T10:00:00.000000Z' status: type: string example: null nullable: true 422: description: '' content: application/json: schema: type: object example: errors: - status: '422' code: validation.max_utf8_bytes detail: 'This field exceeds the maximum allowed value.' source: pointer: /description_html meta: field: description_html properties: errors: type: array example: - status: '422' code: validation.max_utf8_bytes detail: 'This field exceeds the maximum allowed value.' source: pointer: /description_html meta: field: description_html items: type: object properties: status: type: string example: '422' code: type: string example: validation.max_utf8_bytes detail: type: string example: 'This field exceeds the maximum allowed value.' source: type: object properties: pointer: type: string example: /description_html meta: type: object properties: field: type: string example: description_html tags: - Products requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'The product name. Must not be greater than 255 characters.' example: 'Classic Tee' description_html: type: string description: 'Required canonical HTML description. Sanitized against the product allowlist. Maximum 100,000 UTF-8 bytes.' example: '

تفاصيل المنتج

قطن ناعم — Soft cotton.

' category_id: type: string description: "ID of a category owned by the authenticated user's store." example: 1 required: - name - description_html - category_id delete: summary: 'Delete product' operationId: deleteProduct description: 'Remove a product when it has no related variants.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Product deleted successfully' data: type: product id: 1 attributes: name: 'Classic Tee' slug: classic-tee properties: status: type: integer example: 200 message: type: string example: 'Product deleted successfully' data: type: object properties: type: type: string example: product id: type: integer example: 1 attributes: type: object properties: name: type: string example: 'Classic Tee' slug: type: string example: classic-tee 422: description: '' content: application/json: schema: type: object example: errors: - status: '422' code: product.has_variants detail: 'Cannot delete product because it has related variants' properties: errors: type: array example: - status: '422' code: product.has_variants detail: 'Cannot delete product because it has related variants' items: type: object properties: status: type: string example: '422' code: type: string example: product.has_variants detail: type: string example: 'Cannot delete product because it has related variants' tags: - Products parameters: - in: path name: slug description: 'The slug of the product.' example: sds required: true schema: type: string - in: path name: product description: 'The product slug.' example: classic-tee required: true schema: type: string '/api/v1/stores/{store_slug}/categories': get: summary: 'List store categories' operationId: listStoreCategories description: 'Return all categories belonging to a store. This endpoint is public.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Categories retrieved successfully' data: - type: category id: 1 attributes: name: Apparel slug: apparel image: 'https://api.dokio.local/storage/category/apparel.jpg' status: active relationships: store: data: id: 1 type: store links: self: 'https://api.dokio.local/api/v1/stores/1' properties: status: type: integer example: 200 message: type: string example: 'Categories retrieved successfully' data: type: array example: - type: category id: 1 attributes: name: Apparel slug: apparel image: 'https://api.dokio.local/storage/category/apparel.jpg' status: active items: type: object properties: type: type: string example: category id: type: integer example: 1 attributes: type: object properties: name: type: string example: Apparel slug: type: string example: apparel image: type: string example: 'https://api.dokio.local/storage/category/apparel.jpg' status: type: string example: active relationships: type: object properties: store: type: object properties: data: type: object properties: id: type: integer example: 1 type: type: string example: store links: type: object properties: self: type: string example: 'https://api.dokio.local/api/v1/stores/1' tags: - Categories security: [] parameters: - in: path name: store_slug description: 'The slug of the store.' example: backs required: true schema: type: string - in: path name: store description: 'The store slug.' example: acme-fashion required: true schema: type: string '/api/v1/stores/{store_slug}/categories/{slug}': get: summary: 'Get a store category' operationId: getAStoreCategory description: 'Return a single category belonging to a store. This endpoint is public.' parameters: - in: query name: include description: 'Optional related resources to include. Use `products` for the category products to be included.' example: products required: false schema: type: string description: 'Optional related resources to include. Use `products` for the category products to be included.' example: products responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Category retrieved successfully' data: type: category id: 1 attributes: name: Apparel slug: apparel image: 'https://api.dokio.local/storage/category/apparel.jpg' status: active relationships: store: data: id: 1 type: store links: self: 'https://api.dokio.local/api/v1/stores/1/categories/1' properties: status: type: integer example: 200 message: type: string example: 'Category retrieved successfully' data: type: object properties: type: type: string example: category id: type: integer example: 1 attributes: type: object properties: name: type: string example: Apparel slug: type: string example: apparel image: type: string example: 'https://api.dokio.local/storage/category/apparel.jpg' status: type: string example: active relationships: type: object properties: store: type: object properties: data: type: object properties: id: type: integer example: 1 type: type: string example: store links: type: object properties: self: type: string example: 'https://api.dokio.local/api/v1/stores/1/categories/1' tags: - Categories security: [] parameters: - in: path name: store_slug description: 'The slug of the store.' example: backs required: true schema: type: string - in: path name: slug description: 'The slug of the category.' example: backpacks required: true schema: type: string - in: path name: 'The store slug.' description: '' example: acme-fashion required: true schema: type: string - in: path name: 'The category slug.' description: '' example: apparel required: true schema: type: string /api/v1/categories: get: summary: 'List categories' operationId: listCategories description: 'Return all categories.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Categories retrieved successfully' data: - type: category id: 1 attributes: name: Apparel slug: apparel image: 'https://api.dokio.local/storage/category/apparel.jpg' status: active properties: status: type: integer example: 200 message: type: string example: 'Categories retrieved successfully' data: type: array example: - type: category id: 1 attributes: name: Apparel slug: apparel image: 'https://api.dokio.local/storage/category/apparel.jpg' status: active items: type: object properties: type: type: string example: category id: type: integer example: 1 attributes: type: object properties: name: type: string example: Apparel slug: type: string example: apparel image: type: string example: 'https://api.dokio.local/storage/category/apparel.jpg' status: type: string example: active tags: - Categories post: summary: 'Create category' operationId: createCategory description: 'Create a new category. Accepts `multipart/form-data` when uploading an image.' parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: status: 201 message: 'Category created successfully' data: type: category id: 1 attributes: name: Apparel slug: apparel status: active properties: status: type: integer example: 201 message: type: string example: 'Category created successfully' data: type: object properties: type: type: string example: category id: type: integer example: 1 attributes: type: object properties: name: type: string example: Apparel slug: type: string example: apparel status: type: string example: active 403: description: '' content: application/json: schema: type: object example: errors: - status: '403' code: store.required detail: 'User must have a store to create a category.' properties: errors: type: array example: - status: '403' code: store.required detail: 'User must have a store to create a category.' items: type: object properties: status: type: string example: '403' code: type: string example: store.required detail: type: string example: 'User must have a store to create a category.' tags: - Categories requestBody: required: true content: multipart/form-data: schema: type: object properties: name: type: string description: 'The category name. Must not be greater than 255 characters.' example: Apparel image: type: string format: binary description: 'Category image file (jpeg, png, jpg, gif, or svg; max 2MB). SVG uploads are sanitized before storage. Must be an image. Must not be greater than 2048 kilobytes.' required: - name - image '/api/v1/categories/{slug}': get: summary: 'Get category' operationId: getCategory description: 'Return a single category by ID.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Category retrieved successfully' data: type: category id: 1 attributes: name: Apparel slug: apparel status: active properties: status: type: integer example: 200 message: type: string example: 'Category retrieved successfully' data: type: object properties: type: type: string example: category id: type: integer example: 1 attributes: type: object properties: name: type: string example: Apparel slug: type: string example: apparel status: type: string example: active tags: - Categories put: summary: 'Update category' operationId: updateCategory description: 'Update an existing category. Accepts `multipart/form-data` when uploading a new image.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Category updated successfully' data: type: category id: 1 attributes: name: Apparel slug: apparel status: active properties: status: type: integer example: 200 message: type: string example: 'Category updated successfully' data: type: object properties: type: type: string example: category id: type: integer example: 1 attributes: type: object properties: name: type: string example: Apparel slug: type: string example: apparel status: type: string example: active tags: - Categories requestBody: required: true content: multipart/form-data: schema: type: object properties: name: type: string description: 'The category name. Must not be greater than 255 characters.' example: Apparel image: type: string format: binary description: 'Optional new category image (jpeg, png, jpg, gif, or svg; max 2MB). SVG uploads are sanitized before storage. Must be an image. Must not be greater than 2048 kilobytes.' status: type: string description: 'Category status.' example: active enum: - active - inactive required: - name - status delete: summary: 'Delete category' operationId: deleteCategory description: 'Remove a category when it has no related products.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Category deleted successfully' data: type: category id: 1 attributes: name: Apparel slug: apparel status: active properties: status: type: integer example: 200 message: type: string example: 'Category deleted successfully' data: type: object properties: type: type: string example: category id: type: integer example: 1 attributes: type: object properties: name: type: string example: Apparel slug: type: string example: apparel status: type: string example: active 422: description: '' content: application/json: schema: type: object example: errors: - status: '422' code: category.has_products detail: 'Cannot delete category because it has related products' properties: errors: type: array example: - status: '422' code: category.has_products detail: 'Cannot delete category because it has related products' items: type: object properties: status: type: string example: '422' code: type: string example: category.has_products detail: type: string example: 'Cannot delete category because it has related products' tags: - Categories parameters: - in: path name: slug description: 'The slug of the category.' example: backpacks required: true schema: type: string - in: path name: category description: 'The category ID.' example: 1 required: true schema: type: integer /api/v1/enums/industries: get: summary: 'List industries' operationId: listIndustries description: 'Return the allowed industry values for store creation and updates. This endpoint is public.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: - fashion - electronics - food - beauty - home - other properties: data: type: array example: - fashion - electronics - food - beauty - home - other items: type: string tags: - Enums security: [] '/api/v1/stores/{store_slug}/checkout': post: summary: 'Place COD order' operationId: placeCODOrder description: 'Create a cash-on-delivery order from line items. No cart is stored on the server.' parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: status: 201 message: 'Order placed successfully' data: type: order id: 1 attributes: number: 01HXYZ guest_name: 'Jane Doe' guest_email: jane@example.com guest_phone: '01001234567' status: pending subtotal: 200 shipping_amount: 0 total: 200 properties: status: type: integer example: 201 message: type: string example: 'Order placed successfully' data: type: object properties: type: type: string example: order id: type: integer example: 1 attributes: type: object properties: number: type: string example: 01HXYZ guest_name: type: string example: 'Jane Doe' guest_email: type: string example: jane@example.com guest_phone: type: string example: '01001234567' status: type: string example: pending subtotal: type: integer example: 200 shipping_amount: type: integer example: 0 total: type: integer example: 200 422: description: 'Validation failed (empty items, inactive variant, insufficient stock, or foreign-store variant).' content: application/json: schema: type: object example: errors: - status: '422' code: validation.insufficient_stock detail: 'The submitted value is invalid.' meta: field: items properties: errors: type: array example: - status: '422' code: validation.insufficient_stock detail: 'The submitted value is invalid.' meta: field: items items: type: object properties: status: type: string example: '422' code: type: string example: validation.insufficient_stock detail: type: string example: 'The submitted value is invalid.' meta: type: object properties: field: type: string example: items tags: - Checkout requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Guest full name. Must not be greater than 255 characters.' example: 'Jane Doe' email: type: string description: 'Guest email used for order tracking. Must be a valid email address. Must not be greater than 255 characters.' example: jane@example.com phone: type: string description: 'Guest phone number for COD delivery. Must not be greater than 50 characters.' example: '01001234567' line1: type: string description: 'Shipping street address. Must not be greater than 255 characters.' example: '12 Nile Street' city: type: string description: 'Shipping city. Must not be greater than 100 characters.' example: Cairo governorate: type: string description: 'Shipping governorate / state. Must not be greater than 100 characters.' example: Cairo postal_code: type: string description: 'Optional postal code. Must not be greater than 20 characters.' example: '11511' nullable: true country: type: string description: 'ISO 3166-1 alpha-2 country code. Defaults to EG. Must not be greater than 255 characters.' example: EG nullable: true items: type: array description: 'Line items to purchase. Duplicate variant ids are merged by summing quantities. Must have at least 1 items.' example: - product_variant_id: 1 quantity: 2 items: type: object properties: product_variant_id: type: integer description: 'Sellable product variant id. The id of an existing record in the product_variants table.' example: 1 quantity: type: integer description: 'Quantity to order for this variant. Must be at least 1.' example: 2 required: - product_variant_id - quantity required: - name - email - phone - line1 - city - governorate - items security: [] parameters: - in: path name: store_slug description: 'The slug of the store.' example: backs required: true schema: type: string - in: path name: store description: 'Store slug or id.' example: my-store required: true schema: type: string '/api/v1/product-options/{productOption_id}/product-option-values': get: summary: 'List product option values' operationId: listProductOptionValues description: 'Return all product option values.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Product option values retrieved successfully' data: - type: product_option_value id: 1 attributes: value: Small product_option_id: 1 relationships: product_option: data: id: 1 type: product_option links: self: 'https://api.example.com/product-option-values/1' properties: status: type: integer example: 200 message: type: string example: 'Product option values retrieved successfully' data: type: array example: - type: product_option_value id: 1 attributes: value: Small product_option_id: 1 relationships: product_option: data: id: 1 type: product_option links: self: 'https://api.example.com/product-option-values/1' items: type: object properties: type: type: string example: product_option_value id: type: integer example: 1 attributes: type: object properties: value: type: string example: Small product_option_id: type: integer example: 1 relationships: type: object properties: product_option: type: object properties: data: type: object properties: id: type: integer example: 1 type: type: string example: product_option links: type: object properties: self: type: string example: 'https://api.example.com/product-option-values/1' tags: - Endpoints parameters: - in: path name: productOption_id description: 'The ID of the productOption.' example: 1 required: true schema: type: integer /api/v1/product-option-values: post: summary: 'Create product option values' operationId: createProductOptionValues description: 'Create multiple product option values for the same option.' parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: status: 201 message: 'Product option values created successfully' data: - type: product_option_value id: 1 attributes: value: Small product_option_id: 1 relationships: product_option: type: product_option id: 1 links: self: 'https://api.dokio.local/api/v1/product-option-values/1' properties: status: type: integer example: 201 message: type: string example: 'Product option values created successfully' data: type: array example: - type: product_option_value id: 1 attributes: value: Small product_option_id: 1 relationships: product_option: type: product_option id: 1 links: self: 'https://api.dokio.local/api/v1/product-option-values/1' items: type: object properties: type: type: string example: product_option_value id: type: integer example: 1 attributes: type: object properties: value: type: string example: Small product_option_id: type: integer example: 1 relationships: type: object properties: product_option: type: object properties: type: type: string example: product_option id: type: integer example: 1 links: type: object properties: self: type: string example: 'https://api.dokio.local/api/v1/product-option-values/1' tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: product_option_id: type: string description: 'The id of an existing record in the product_options table.' example: architecto values: type: array description: 'Must not be greater than 255 characters.' example: - 'n' items: type: string required: - product_option_id - values '/api/v1/product-option-values/{id}': get: summary: 'Display the specified resource.' operationId: displayTheSpecifiedResource description: '' parameters: [] responses: { } tags: - Endpoints put: summary: 'Update the specified resource in storage.' operationId: updateTheSpecifiedResourceInStorage description: '' parameters: [] responses: { } tags: - Endpoints delete: summary: 'Delete product option value' operationId: deleteProductOptionValue description: 'Remove a product option value when it has no related variants.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Product option value deleted successfully' data: type: product_option_value id: 1 attributes: value: Small product_option_id: 1 properties: status: type: integer example: 200 message: type: string example: 'Product option value deleted successfully' data: type: object properties: type: type: string example: product_option_value id: type: integer example: 1 attributes: type: object properties: value: type: string example: Small product_option_id: type: integer example: 1 403: description: '' content: application/json: schema: type: object example: errors: - status: '403' code: product_option_value.forbidden detail: 'You are not authorized to delete this product option value' properties: errors: type: array example: - status: '403' code: product_option_value.forbidden detail: 'You are not authorized to delete this product option value' items: type: object properties: status: type: string example: '403' code: type: string example: product_option_value.forbidden detail: type: string example: 'You are not authorized to delete this product option value' 422: description: '' content: application/json: schema: type: object example: errors: - status: '422' code: product_option_value.has_variants detail: 'Cannot delete product option value because it has related variants' properties: errors: type: array example: - status: '422' code: product_option_value.has_variants detail: 'Cannot delete product option value because it has related variants' items: type: object properties: status: type: string example: '422' code: type: string example: product_option_value.has_variants detail: type: string example: 'Cannot delete product option value because it has related variants' tags: - Endpoints parameters: - in: path name: id description: 'The ID of the product option value.' example: 1 required: true schema: type: integer /api/v1/orders: get: summary: 'List orders' operationId: listOrders description: 'Return orders for the authenticated vendor store.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Orders retrieved successfully' data: - type: order id: 1 attributes: number: 01HXYZ status: pending total: 200 properties: status: type: integer example: 200 message: type: string example: 'Orders retrieved successfully' data: type: array example: - type: order id: 1 attributes: number: 01HXYZ status: pending total: 200 items: type: object properties: type: type: string example: order id: type: integer example: 1 attributes: type: object properties: number: type: string example: 01HXYZ status: type: string example: pending total: type: integer example: 200 tags: - Orders '/api/v1/orders/{id}': get: summary: 'Show order' operationId: showOrder description: 'Return a single store order.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Order retrieved successfully' data: type: order id: 1 attributes: number: 01HXYZ status: pending total: 200 properties: status: type: integer example: 200 message: type: string example: 'Order retrieved successfully' data: type: object properties: type: type: string example: order id: type: integer example: 1 attributes: type: object properties: number: type: string example: 01HXYZ status: type: string example: pending total: type: integer example: 200 tags: - Orders patch: summary: 'Update order' operationId: updateOrder description: 'Update order, payment, and/or shipment status.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Order updated successfully' data: type: order id: 1 attributes: number: 01HXYZ status: confirmed total: 200 properties: status: type: integer example: 200 message: type: string example: 'Order updated successfully' data: type: object properties: type: type: string example: order id: type: integer example: 1 attributes: type: object properties: number: type: string example: 01HXYZ status: type: string example: confirmed total: type: integer example: 200 tags: - Orders requestBody: required: false content: application/json: schema: type: object properties: status: type: string description: 'Order status. Allowed: pending, confirmed, cancelled, completed.' example: confirmed enum: - pending - confirmed - cancelled - completed payment_status: type: string description: 'Payment status. Allowed: unpaid, paid.' example: paid enum: - unpaid - paid shipment_status: type: string description: 'Shipment status. Allowed: pending, shipped, delivered, cancelled.' example: shipped enum: - pending - shipped - delivered - cancelled parameters: - in: path name: id description: 'The ID of the order.' example: 1 required: true schema: type: integer - in: path name: order description: 'Order id.' example: 1 required: true schema: type: integer '/api/v1/orders/{number}': get: summary: 'Track guest order' operationId: trackGuestOrder description: 'Look up an order by public number and guest email.' parameters: - in: query name: email description: 'Guest email used when placing the order.' example: jane@example.com required: true schema: type: string description: 'Guest email used when placing the order.' example: jane@example.com responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Order retrieved successfully' data: type: order id: 1 attributes: number: 01HXYZ status: pending total: 200 properties: status: type: integer example: 200 message: type: string example: 'Order retrieved successfully' data: type: object properties: type: type: string example: order id: type: integer example: 1 attributes: type: object properties: number: type: string example: 01HXYZ status: type: string example: pending total: type: integer example: 200 404: description: '' content: application/json: schema: type: object example: status: 404 message: 'Order not found' properties: status: type: integer example: 404 message: type: string example: 'Order not found' tags: - Orders requestBody: required: true content: application/json: schema: type: object properties: email: type: string description: 'Must be a valid email address.' example: gbailey@example.net required: - email security: [] parameters: - in: path name: number description: 'Public order number returned at checkout.' example: 01HXYZ required: true schema: type: string /api/v1/product-options: get: summary: 'List product options' operationId: listProductOptions description: 'Return all product options.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Product options retrieved successfully' data: - type: product_option id: 1 attributes: name: Apparel properties: status: type: integer example: 200 message: type: string example: 'Product options retrieved successfully' data: type: array example: - type: product_option id: 1 attributes: name: Apparel items: type: object properties: type: type: string example: product_option id: type: integer example: 1 attributes: type: object properties: name: type: string example: Apparel tags: - 'Product Options' post: summary: 'Create product option' operationId: createProductOption description: 'Create a new product option.' parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: status: 201 message: 'Product option created successfully' data: type: product_option id: 1 attributes: name: Size properties: status: type: integer example: 201 message: type: string example: 'Product option created successfully' data: type: object properties: type: type: string example: product_option id: type: integer example: 1 attributes: type: object properties: name: type: string example: Size tags: - 'Product Options' requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Must not be greater than 255 characters.' example: b required: - name '/api/v1/product-options/{id}': get: summary: 'Display the specified resource.' operationId: displayTheSpecifiedResource description: '' parameters: [] responses: { } tags: - 'Product Options' put: summary: 'Update product option' operationId: updateProductOption description: 'Rename an owned product option. JSON PUT/PATCH and multipart POST with `_method=PUT` are supported. Identity, store, and related values stay unchanged.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Product option updated successfully' data: type: product_option id: 1 attributes: name: Size properties: status: type: integer example: 200 message: type: string example: 'Product option updated successfully' data: type: object properties: type: type: string example: product_option id: type: integer example: 1 attributes: type: object properties: name: type: string example: Size 403: description: '' content: application/json: schema: type: object example: message: 'This action is unauthorized.' properties: message: type: string example: 'This action is unauthorized.' 422: description: '' content: application/json: schema: type: object example: errors: - status: '422' code: validation.required detail: 'This field is required.' meta: field: request properties: errors: type: array example: - status: '422' code: validation.required detail: 'This field is required.' meta: field: request items: type: object properties: status: type: string example: '422' code: type: string example: validation.required detail: type: string example: 'This field is required.' meta: type: object properties: field: type: string example: request tags: - 'Product Options' requestBody: required: false content: application/json: schema: type: object properties: name: type: string description: 'Non-blank option name, max 255 characters. Must not be greater than 255 characters.' example: Size store_id: type: string description: '' example: null delete: summary: 'Delete product option' operationId: deleteProductOption description: 'Remove a product option when it has no related values.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Product option deleted successfully' data: type: product_option id: 1 attributes: name: Size properties: status: type: integer example: 200 message: type: string example: 'Product option deleted successfully' data: type: object properties: type: type: string example: product_option id: type: integer example: 1 attributes: type: object properties: name: type: string example: Size 422: description: '' content: application/json: schema: type: object example: errors: - status: '422' code: product_option.has_values detail: 'Cannot delete product option because it has related values' properties: errors: type: array example: - status: '422' code: product_option.has_values detail: 'Cannot delete product option because it has related values' items: type: object properties: status: type: string example: '422' code: type: string example: product_option.has_values detail: type: string example: 'Cannot delete product option because it has related values' tags: - 'Product Options' parameters: - in: path name: id description: 'The ID of the product option.' example: 1 required: true schema: type: integer /api/v1/product-variants: get: summary: 'List product variants' operationId: listProductVariants description: 'Return all product variants.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Product variants retrieved successfully' data: - type: product_variant id: 1 attributes: name: Small price: 100 stock: 100 product_id: 1 product_option_id: 1 relationships: product: data: id: 1 type: product product_option_value: data: id: 1 type: product_option_value links: self: 'https://api.example.com/product-variants/1' properties: status: type: integer example: 200 message: type: string example: 'Product variants retrieved successfully' data: type: array example: - type: product_variant id: 1 attributes: name: Small price: 100 stock: 100 product_id: 1 product_option_id: 1 relationships: product: data: id: 1 type: product product_option_value: data: id: 1 type: product_option_value links: self: 'https://api.example.com/product-variants/1' items: type: object properties: type: type: string example: product_variant id: type: integer example: 1 attributes: type: object properties: name: type: string example: Small price: type: integer example: 100 stock: type: integer example: 100 product_id: type: integer example: 1 product_option_id: type: integer example: 1 relationships: type: object properties: product: type: object properties: data: type: object properties: id: type: integer example: 1 type: type: string example: product product_option_value: type: object properties: data: type: object properties: id: type: integer example: 1 type: type: string example: product_option_value links: type: object properties: self: type: string example: 'https://api.example.com/product-variants/1' tags: - 'Product Variants' post: summary: 'Create product variant' operationId: createProductVariant description: 'Create a new product variant.' parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: status: 201 message: 'Product variant created successfully' data: type: product_variant id: 1 attributes: name: Small price: 100 stock: 100 product_id: 1 product_option_id: 1 properties: status: type: integer example: 201 message: type: string example: 'Product variant created successfully' data: type: object properties: type: type: string example: product_variant id: type: integer example: 1 attributes: type: object properties: name: type: string example: Small price: type: integer example: 100 stock: type: integer example: 100 product_id: type: integer example: 1 product_option_id: type: integer example: 1 tags: - 'Product Variants' requestBody: required: true content: multipart/form-data: schema: type: object properties: price: type: number description: 'The price of the product variant. Must be at least 0.' example: 100.0 stock: type: integer description: 'The stock of the product variant. Must be at least 0.' example: 100 product_id: type: string description: 'The product ID of the product variant. The id of an existing record in the products table.' example: 1 product_option_value_id: type: string description: 'The product option value ID of the product variant. The id of an existing record in the product_option_values table.' example: 1 sku: type: string description: 'The SKU of the product variant. Must not be greater than 255 characters.' example: '1234567890' image: type: string format: binary description: 'The image of the product variant (multipart file upload). Must be an image. Must not be greater than 2048 kilobytes.' status: type: string description: 'The status of the product variant.' example: active enum: - active - inactive required: - price - stock - product_id - product_option_value_id - sku - image - status '/api/v1/product-variants/{id}': get: summary: 'Display the specified resource.' operationId: displayTheSpecifiedResource description: '' parameters: [] responses: { } tags: - 'Product Variants' put: summary: 'Update product variant' operationId: updateProductVariant description: 'Update supplied fields on an owned product variant. JSON PUT/PATCH and multipart POST with `_method=PUT` are supported. Omitted fields, including the image, remain unchanged.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Product variant updated successfully' data: type: product_variant id: 1 attributes: price: 12500 stock: 8 sku: TEE-M status: active product_id: 1 product_option_value_id: 1 properties: status: type: integer example: 200 message: type: string example: 'Product variant updated successfully' data: type: object properties: type: type: string example: product_variant id: type: integer example: 1 attributes: type: object properties: price: type: integer example: 12500 stock: type: integer example: 8 sku: type: string example: TEE-M status: type: string example: active product_id: type: integer example: 1 product_option_value_id: type: integer example: 1 401: description: '' content: application/json: schema: type: object example: errors: - status: '401' code: unauthenticated detail: Unauthenticated. properties: errors: type: array example: - status: '401' code: unauthenticated detail: Unauthenticated. items: type: object properties: status: type: string example: '401' code: type: string example: unauthenticated detail: type: string example: Unauthenticated. 403: description: '' content: application/json: schema: type: object example: message: 'This action is unauthorized.' properties: message: type: string example: 'This action is unauthorized.' 422: description: '' content: application/json: schema: type: object example: errors: - status: '422' code: validation.required detail: 'This field is required.' meta: field: request properties: errors: type: array example: - status: '422' code: validation.required detail: 'This field is required.' meta: field: request items: type: object properties: status: type: string example: '422' code: type: string example: validation.required detail: type: string example: 'This field is required.' meta: type: object properties: field: type: string example: request tags: - 'Product Variants' requestBody: required: false content: multipart/form-data: schema: type: object properties: price: type: integer description: 'Integer minor-unit price from 0 to 2147483647. Omitted value is unchanged. Must be at least 0. Must not be greater than 2147483647.' example: 12500 stock: type: integer description: 'Absolute integer stock from 0 to 2147483647. Omitted value is unchanged. Must be at least 0. Must not be greater than 2147483647.' example: 8 sku: type: string description: 'Non-blank SKU, max 255 characters. No uniqueness rule is applied. Must not be greater than 255 characters.' example: TEE-M status: type: string description: 'Availability status. Allowed: active, inactive.' example: active enum: - active - inactive image: type: string format: binary description: 'Replacement image upload (jpeg, png, jpg, gif, or svg; max 2MB). SVG uploads are sanitized. Omitted image is retained; URLs and null are rejected. Must be an image. Must not be greater than 2048 kilobytes.' product_option_value_id: type: integer description: 'Replacement option value that belongs to the authenticated store. The id of an existing record in the product_option_values table.' example: 1 product_id: type: integer description: 'Optional compatibility echo. Must equal the current product ID and is not persisted as a change.' example: 1 store_id: type: string description: '' example: null delete: summary: 'Delete product variant' operationId: deleteProductVariant description: 'Remove a product variant when it is not used in any orders.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Product variant deleted successfully' data: type: product_variant id: 1 attributes: stock: 100 price: 100 properties: status: type: integer example: 200 message: type: string example: 'Product variant deleted successfully' data: type: object properties: type: type: string example: product_variant id: type: integer example: 1 attributes: type: object properties: stock: type: integer example: 100 price: type: integer example: 100 403: description: '' content: application/json: schema: type: object example: errors: - status: '403' code: product_variant.forbidden detail: 'You are not authorized to delete this product variant' properties: errors: type: array example: - status: '403' code: product_variant.forbidden detail: 'You are not authorized to delete this product variant' items: type: object properties: status: type: string example: '403' code: type: string example: product_variant.forbidden detail: type: string example: 'You are not authorized to delete this product variant' 422: description: '' content: application/json: schema: type: object example: errors: - status: '422' code: product_variant.used_in_orders detail: 'Cannot delete product variant because it is used in orders' properties: errors: type: array example: - status: '422' code: product_variant.used_in_orders detail: 'Cannot delete product variant because it is used in orders' items: type: object properties: status: type: string example: '422' code: type: string example: product_variant.used_in_orders detail: type: string example: 'Cannot delete product variant because it is used in orders' tags: - 'Product Variants' parameters: - in: path name: id description: 'The ID of the product variant.' example: 3 required: true schema: type: integer '/api/v1/stores/{store_slug}/rows': get: summary: 'List public store rows' operationId: listPublicStoreRows description: 'Return visible homepage sections for a store slug, with slides. Hidden and unknown types are omitted.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Rows retrieved successfully' data: - type: store_row id: 12 attributes: type: hero_slider position: 1 isVisible: true settings: autoplay: true interval: 5000 height: large overlay: dark includes: slides: [] properties: status: type: integer example: 200 message: type: string example: 'Rows retrieved successfully' data: type: array example: - type: store_row id: 12 attributes: type: hero_slider position: 1 isVisible: true settings: autoplay: true interval: 5000 height: large overlay: dark includes: slides: [] items: type: object properties: type: type: string example: store_row id: type: integer example: 12 attributes: type: object properties: type: type: string example: hero_slider position: type: integer example: 1 isVisible: type: boolean example: true settings: type: object properties: autoplay: type: boolean example: true interval: type: integer example: 5000 height: type: string example: large overlay: type: string example: dark includes: type: object properties: slides: type: array example: [] tags: - 'Storefront rows' security: [] parameters: - in: path name: store_slug description: 'The slug of the store.' example: backs required: true schema: type: string - in: path name: store description: 'The store slug.' example: acme-fashion required: true schema: type: string /api/v1/store/rows: get: summary: 'List store rows' operationId: listStoreRows description: 'Return every homepage section for the authenticated merchant store, including hidden rows and slides.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Rows retrieved successfully' data: - type: store_row id: 12 attributes: type: hero_slider position: 1 isVisible: true settings: autoplay: true interval: 5000 height: large overlay: dark createdAt: '2026-09-10T00:00:00.000000Z' updatedAt: '2026-09-10T00:00:00.000000Z' relationships: store: data: type: store id: 1 slides: data: [] includes: slides: [] properties: status: type: integer example: 200 message: type: string example: 'Rows retrieved successfully' data: type: array example: - type: store_row id: 12 attributes: type: hero_slider position: 1 isVisible: true settings: autoplay: true interval: 5000 height: large overlay: dark createdAt: '2026-09-10T00:00:00.000000Z' updatedAt: '2026-09-10T00:00:00.000000Z' relationships: store: data: type: store id: 1 slides: data: [] includes: slides: [] items: type: object properties: type: type: string example: store_row id: type: integer example: 12 attributes: type: object properties: type: type: string example: hero_slider position: type: integer example: 1 isVisible: type: boolean example: true settings: type: object properties: autoplay: type: boolean example: true interval: type: integer example: 5000 height: type: string example: large overlay: type: string example: dark createdAt: type: string example: '2026-09-10T00:00:00.000000Z' updatedAt: type: string example: '2026-09-10T00:00:00.000000Z' relationships: type: object properties: store: type: object properties: data: type: object properties: type: type: string example: store id: type: integer example: 1 slides: type: object properties: data: type: array example: [] includes: type: object properties: slides: type: array example: [] tags: - 'Storefront rows' post: summary: 'Create store row' operationId: createStoreRow description: 'Append a supported homepage section to the authenticated merchant store.' parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: status: 201 message: 'Row created successfully' data: type: store_row id: 19 attributes: type: banner position: 5 isVisible: false settings: heading: Summer alt: '' link: /products image: null properties: status: type: integer example: 201 message: type: string example: 'Row created successfully' data: type: object properties: type: type: string example: store_row id: type: integer example: 19 attributes: type: object properties: type: type: string example: banner position: type: integer example: 5 isVisible: type: boolean example: false settings: type: object properties: heading: type: string example: Summer alt: type: string example: '' link: type: string example: /products image: type: string example: null nullable: true tags: - 'Storefront rows' requestBody: required: true content: application/json: schema: type: object properties: type: type: string description: 'Supported homepage section type.' example: banner enum: - hero_slider - featured_products - categories - welcome_marquee - banner settings: type: object description: 'Optional type-specific settings. Omitted keys use defaults.' example: heading: Summer properties: autoplay: type: boolean description: '' example: false interval: type: integer description: 'Must be at least 2000. Must not be greater than 15000.' example: 1 height: type: string description: '' example: medium enum: - small - medium - large overlay: type: string description: '' example: dark enum: - none - light - dark limit: type: integer description: 'Must be at least 4. Must not be greater than 12.' example: 4 heading: type: string description: 'Must not be greater than 80 characters.' example: Summer nullable: true message: type: string description: 'Must not be greater than 120 characters.' example: g nullable: true alt: type: string description: 'Must not be greater than 120 characters.' example: z nullable: true link: type: string description: 'Must not be greater than 2048 characters.' example: m nullable: true productIds: type: array description: '' example: - 16 items: type: integer categoryIds: type: array description: '' example: - 16 items: type: integer image: type: string description: '' example: null required: - type /api/v1/store/rows/reorder: patch: summary: 'Reorder store rows' operationId: reorderStoreRows description: 'Rewrite every homepage section position for the authenticated store. The body must list every current row exactly once.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Rows reordered successfully' data: [] properties: status: type: integer example: 200 message: type: string example: 'Rows reordered successfully' data: type: array example: [] tags: - 'Storefront rows' requestBody: required: true content: application/json: schema: type: object properties: rows: type: array description: 'Every current section exactly once. Positions may be any unique integers; the server stores dense 1..n in array order. Must have at least 1 items.' example: - id: 12 position: 1 - id: 8 position: 2 items: type: object properties: id: type: integer description: '' example: 16 position: type: integer description: '' example: 16 required: - id - position required: - rows '/api/v1/store/rows/{id}': patch: summary: 'Update store row' operationId: updateStoreRow description: 'Update visibility or settings for one homepage section. Type cannot change. Banner images are uploaded as `image`, not as `settings.image`.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Row updated successfully' data: type: store_row id: 12 attributes: type: featured_products isVisible: true settings: limit: 8 heading: 'Our picks' productIds: - 14 - 9 properties: status: type: integer example: 200 message: type: string example: 'Row updated successfully' data: type: object properties: type: type: string example: store_row id: type: integer example: 12 attributes: type: object properties: type: type: string example: featured_products isVisible: type: boolean example: true settings: type: object properties: limit: type: integer example: 8 heading: type: string example: 'Our picks' productIds: type: array example: - 14 - 9 items: type: integer tags: - 'Storefront rows' requestBody: required: false content: multipart/form-data: schema: type: object properties: type: type: string description: 'Rejected on update. Section type is immutable.' example: null is_visible: type: boolean description: 'Whether customers should see this section.' example: true image: type: string format: binary description: 'Banner image upload. Ignored for other section types. Must be an image. Must not be greater than 2048 kilobytes.' settings: type: object description: 'Type-specific settings. Unknown keys are rejected. Do not send `settings.image`; upload `image` for banners.' example: heading: 'Our picks' limit: 8 productIds: - 14 - 9 properties: autoplay: type: boolean description: '' example: true interval: type: integer description: 'Must be at least 2000. Must not be greater than 15000.' example: 1 height: type: string description: '' example: large enum: - small - medium - large overlay: type: string description: '' example: dark enum: - none - light - dark limit: type: integer description: 'Must be at least 4. Must not be greater than 12.' example: 8 heading: type: string description: 'Must not be greater than 80 characters.' example: 'Our picks' nullable: true message: type: string description: 'Must not be greater than 120 characters.' example: 'n' nullable: true alt: type: string description: 'Must not be greater than 120 characters.' example: g nullable: true link: type: string description: 'Must not be greater than 2048 characters.' example: z nullable: true productIds: type: array description: '' example: - 14 - 9 items: type: integer categoryIds: type: array description: '' example: - 16 items: type: integer image: type: string description: '' example: null delete: summary: 'Delete store row' operationId: deleteStoreRow description: 'Delete a homepage section, its slides, and unused uploaded images.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Row deleted successfully' data: [] properties: status: type: integer example: 200 message: type: string example: 'Row deleted successfully' data: type: array example: [] tags: - 'Storefront rows' parameters: - in: path name: id description: 'The ID of the row.' example: 1 required: true schema: type: integer '/api/v1/store/rows/{row_id}/slides': get: summary: 'List slides' operationId: listSlides description: 'Return slides for one homepage section, ordered by position then id.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Slides retrieved successfully' data: - type: store_slide id: 3 attributes: position: 1 title: Summer subtitle: Sale image: 'https://api.example.com/storage/store_row/slide.jpg' link: /products settings: cta_text: Shop properties: status: type: integer example: 200 message: type: string example: 'Slides retrieved successfully' data: type: array example: - type: store_slide id: 3 attributes: position: 1 title: Summer subtitle: Sale image: 'https://api.example.com/storage/store_row/slide.jpg' link: /products settings: cta_text: Shop items: type: object properties: type: type: string example: store_slide id: type: integer example: 3 attributes: type: object properties: position: type: integer example: 1 title: type: string example: Summer subtitle: type: string example: Sale image: type: string example: 'https://api.example.com/storage/store_row/slide.jpg' link: type: string example: /products settings: type: object properties: cta_text: type: string example: Shop tags: - 'Storefront slides' post: summary: 'Create slide' operationId: createSlide description: 'Add a slide to a slider section. Image is required. Non-slider types return 422.' parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: status: 201 message: 'Slide created successfully' data: type: store_slide id: 3 attributes: position: 1 title: Summer image: 'https://api.example.com/storage/store_row/slide.jpg' link: /products settings: cta_text: Shop properties: status: type: integer example: 201 message: type: string example: 'Slide created successfully' data: type: object properties: type: type: string example: store_slide id: type: integer example: 3 attributes: type: object properties: position: type: integer example: 1 title: type: string example: Summer image: type: string example: 'https://api.example.com/storage/store_row/slide.jpg' link: type: string example: /products settings: type: object properties: cta_text: type: string example: Shop tags: - 'Storefront slides' requestBody: required: true content: multipart/form-data: schema: type: object properties: title: type: string description: 'Optional slide title, max 80 characters. Must not be greater than 80 characters.' example: Summer nullable: true subtitle: type: string description: 'Optional slide subtitle, max 160 characters. Must not be greater than 160 characters.' example: Sale nullable: true link: type: string description: 'http(s) URL or a single-leading-slash in-store path. Must not be greater than 2048 characters.' example: /products nullable: true cta_text: type: string description: 'Optional button label, max 40 characters. Must not be greater than 40 characters.' example: Shop nullable: true settings: type: object description: '' example: null properties: cta_text: type: string description: 'Must not be greater than 40 characters.' example: b nullable: true image: type: string format: binary description: 'Required image upload, max 2MB. SVG is sanitized. Must be an image. Must not be greater than 2048 kilobytes.' required: - image parameters: - in: path name: row_id description: 'The ID of the row.' example: 1 required: true schema: type: integer - in: path name: row description: 'The homepage section id.' example: 12 required: true schema: type: integer '/api/v1/store/rows/{row_id}/slides/reorder': patch: summary: 'Reorder slides' operationId: reorderSlides description: 'Rewrite every slide position for one section. The body must list every current slide of that row exactly once.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Slides reordered successfully' data: [] properties: status: type: integer example: 200 message: type: string example: 'Slides reordered successfully' data: type: array example: [] tags: - 'Storefront slides' requestBody: required: true content: application/json: schema: type: object properties: slides: type: array description: 'Every current slide of this section exactly once. The server stores dense 1..n in array order. Must have at least 1 items.' example: - id: 3 position: 1 - id: 4 position: 2 items: type: object properties: id: type: integer description: '' example: 16 position: type: integer description: '' example: 16 required: - id - position required: - slides parameters: - in: path name: row_id description: 'The ID of the row.' example: 1 required: true schema: type: integer - in: path name: row description: 'The homepage section id.' example: 12 required: true schema: type: integer '/api/v1/store/slides/{id}': patch: summary: 'Update slide' operationId: updateSlide description: 'Update slide copy or replace its image. Omitted image is kept. Null may clear title, subtitle, link, and cta_text.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Slide updated successfully' data: type: store_slide id: 3 attributes: title: Updated image: 'https://api.example.com/storage/store_row/slide.jpg' properties: status: type: integer example: 200 message: type: string example: 'Slide updated successfully' data: type: object properties: type: type: string example: store_slide id: type: integer example: 3 attributes: type: object properties: title: type: string example: Updated image: type: string example: 'https://api.example.com/storage/store_row/slide.jpg' tags: - 'Storefront slides' requestBody: required: false content: multipart/form-data: schema: type: object properties: title: type: string description: 'Slide title. Null clears it. Must not be greater than 80 characters.' example: Updated nullable: true subtitle: type: string description: 'Slide subtitle. Null clears it. Must not be greater than 160 characters.' example: Sale nullable: true link: type: string description: 'http(s) URL or a single-leading-slash in-store path. Null clears it. Must not be greater than 2048 characters.' example: /products nullable: true cta_text: type: string description: 'Button label. Null clears it. Must not be greater than 40 characters.' example: Shop nullable: true settings: type: object description: '' example: null properties: cta_text: type: string description: 'Must not be greater than 40 characters.' example: b nullable: true image: type: string format: binary description: 'Replacement image. Omit to keep the current file. Null cannot clear image. Must be an image. Must not be greater than 2048 kilobytes.' delete: summary: 'Delete slide' operationId: deleteSlide description: 'Delete a slide and its unused image after the record is removed.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: 200 message: 'Slide deleted successfully' data: [] properties: status: type: integer example: 200 message: type: string example: 'Slide deleted successfully' data: type: array example: [] tags: - 'Storefront slides' parameters: - in: path name: id description: 'The ID of the slide.' example: 1 required: true schema: type: integer - in: path name: slide description: 'The slide id.' example: 3 required: true schema: type: integer