# API ## Base REST API under `/api/v1`. Responses use: ```json { "data": {} } ``` Collections include pagination metadata. ## Public endpoints ```text GET /api/v1/public/search GET /api/v1/public/products/:slug GET /api/v1/public/inventory GET /api/v1/public/inventory/:id GET /api/v1/public/listings GET /api/v1/public/listings/:id GET /api/v1/public/sellers GET /api/v1/public/lookups/categories GET /api/v1/public/lookups/conditions GET /api/v1/public/currencies GET /api/v1/public/exchange-rates ``` ## Authentication ```text POST /api/v1/auth/login POST /api/v1/auth/logout POST /api/v1/auth/forgot-password POST /api/v1/auth/reset-password GET /api/v1/auth/me POST /api/v1/auth/mfa/enroll POST /api/v1/auth/mfa/verify POST /api/v1/auth/mfa/disable GET /api/v1/auth/sessions DELETE /api/v1/auth/sessions/:id DELETE /api/v1/auth/sessions ``` `POST /api/v1/auth/forgot-password` always returns the same accepted response after validating an email-shaped request, whether or not an account exists. It sends an opaque, one-time reset token only through the configured mail adapter. `POST /api/v1/auth/reset-password` consumes that token, updates the password, and revokes all active sessions. `GET /api/v1/auth/sessions` returns only the authenticated user's active sessions and marks the current session. It never returns session or CSRF token material. `DELETE /api/v1/auth/sessions/:id` revokes one session owned by the authenticated user. `DELETE /api/v1/auth/sessions` revokes all other active sessions while retaining the current session. Session revocation endpoints require CSRF validation. ## Admin endpoints ```text GET /api/v1/admin/products POST /api/v1/admin/products GET /api/v1/admin/products/:id PATCH /api/v1/admin/products/:id GET /api/v1/admin/inventory POST /api/v1/admin/inventory GET /api/v1/admin/inventory/:id PATCH /api/v1/admin/inventory/:id POST /api/v1/admin/inventory/:id/list POST /api/v1/admin/inventory/:id/archive POST /api/v1/admin/inventory/:id/relist POST /api/v1/admin/inventory/:id/pricing/refresh GET /api/v1/admin/jobs/:id GET /api/v1/admin/products/:id/provider-mappings POST /api/v1/admin/products/:id/provider-mappings POST /api/v1/admin/provider-mappings/:id/disable GET /api/v1/admin/listings GET /api/v1/admin/listings/:id PATCH /api/v1/admin/listings/:id POST /api/v1/admin/listings/:id/publish POST /api/v1/admin/listings/:id/unpublish POST /api/v1/admin/listings/:id/pending POST /api/v1/admin/listings/:id/archive POST /api/v1/admin/pricing/refresh GET /api/v1/admin/jobs/:id POST /api/v1/admin/inventory/:id/images PATCH /api/v1/admin/inventory/:id/images/order POST /api/v1/admin/images/:id/primary DELETE /api/v1/admin/images/:id GET /api/v1/admin/acquisitions POST /api/v1/admin/acquisitions GET /api/v1/admin/sales POST /api/v1/admin/sales POST /api/v1/admin/sales/:id/void POST /api/v1/admin/pricing/observations POST /api/v1/admin/inventory/:id/valuation POST /api/v1/admin/inventory/:id/valuation/override POST /api/v1/admin/inventory/:id/pricing/refresh POST /api/v1/admin/bulk `POST /api/v1/admin/bulk` supports `ARCHIVE`, `PRICING_REFRESH`, and `TAGS` actions. `TAGS` accepts `add` and/or `remove` arrays and applies labels to the selected items' Products. GET /api/v1/admin/dashboard GET /api/v1/admin/exports/inventory.json GET /api/v1/admin/exports/inventory.csv GET /api/v1/admin/exports/inventory.xls GET /api/v1/admin/saved-views POST /api/v1/admin/saved-views PATCH /api/v1/admin/saved-views/:id DELETE /api/v1/admin/saved-views/:id ``` Listing creation and lifecycle writes require an authenticated admin session and the session-bound CSRF header. Publishing is seller-scoped and checks the persisted product, category, condition, asking price, physical inventory, and image prerequisites before changing a draft to `AVAILABLE`; sold and archived physical inventory cannot be published. Unpublish changes an available listing to `CLOSED`; pending changes are available from draft or available listings. `POST /api/v1/admin/inventory/:id/images` stores validated image metadata for an owned physical inventory item. File bytes are handled by the future `ImageStorage` adapter; JSON metadata and multipart image uploads are accepted. Multipart uploads require `width` and `height` fields and validate the binary signature before storage. Publication counts persisted inventory images rather than trusting a listing request field. Acquisition, sale, pricing, and dashboard routes are seller-scoped through the authenticated admin session. All state-changing routes require the session-bound CSRF header. Sale creation marks owned active inventory items as `SOLD`; voiding retains the sale record, restores the items to `ACTIVE`, and records an audit event. Pricing observations remain private; public listing detail exposes only the latest calculated or manually overridden market value, condition label, and public image URLs. Pricing refresh returns `202 Accepted` with a durable job identifier and `PENDING` status. Poll the job endpoint for `PROCESSING`, `COMPLETED`, or `FAILED`; job status reads are seller-scoped and expose only safe status/error metadata. Refresh enqueue resolves the provider from an active Product-level mapping and returns `409` when no mapping exists. ## Provider matching The API must expose Product-level operations for: - searching configured providers for candidate matches - confirming a provider mapping - manually searching/refining a provider query - marking a provider not applicable - disabling/replacing a mapping Exact route naming may be finalized during API implementation, but these operations must remain Product-scoped. ## Validation Validate body, query, path, and response schemas. Business validation occurs after request-shape validation. Allowlist sortable fields. ## Errors Use machine-readable codes, including: - `PRODUCT_NOT_FOUND` - `INVENTORY_NOT_FOUND` - `INVENTORY_EDIT_LOCKED` - `INVALID_STATUS_TRANSITION` - `LISTING_REQUIREMENTS_NOT_MET` - `SALE_PRICE_REQUIRED` - `SALE_ITEM_NOT_AVAILABLE` - `PRICING_PROVIDER_FAILED` - `IMAGE_LIMIT_EXCEEDED` - `UNAUTHORIZED` - `FORBIDDEN` ## Privacy Public DTOs must never expose private acquisition, sale, buyer, internal, provider-debugging, pricing-history, or audit information. ## Pagination and query state Large collections use pagination. Public/admin filters and searches should be represented in URL query state where useful for shareability, refresh, and back/forward navigation.