# Apimio Public API > RESTful JSON API to manage an organization's product catalog — products, variants, pricing across markets, inventory, media, custom attributes, localization & store-specific overrides, and outbound webhooks. Path-versioned at `/v1`. **Base URL:** `https://app.apimio.com/api/v1` — this is the base URL of the environment serving this document, so use it as-is. Every path below is relative to it. **Authentication:** Bearer token — `Authorization: Bearer apk_live_...`. Generate a key in the Apimio app under Settings → API & Tokens (requires the Advanced plan or admin-enabled API access, plus an active subscription). Each key is bound to one organization; all data is auto-scoped to it. Keys carry scopes (e.g. `products:read`, `products:write`, `webhooks:read`, `webhooks:write`); calling outside a key's scopes returns 403. **Conventions:** - Responses are JSON; success bodies wrap the payload in `data`. - List endpoints are cursor-paginated: `?limit=` (1–100, default 20), follow `meta.next_cursor` as `?cursor=`. - Errors: `{ "error": { "type", "code", "message", "errors"? } }` with a matching HTTP status (401 invalid_api_key, 403 insufficient_scope/plan_not_entitled/subscription_inactive, 404 not_found, 409 idempotency_conflict, 422 validation_error, 429 rate_limit_exceeded/quota_exceeded). - `403 subscription_inactive` means the plan includes the API but billing has lapsed; keys stay valid and resume working once payment is up to date. - `POST` accepts an `Idempotency-Key` header (safe-retry, cached 24h; reuse with a different body → 409). - Rate limited per-minute (`X-RateLimit-Limit`/`X-RateLimit-Remaining`, 429 + `Retry-After`) and per-month quota (`X-Quota-Limit`/`X-Quota-Remaining`). ## Docs - [OpenAPI 3.1 specification](https://app.apimio.com/developers/openapi.yaml): complete machine-readable contract — import into any OpenAPI tool, SDK generator, or MCP server. - [API reference portal](https://app.apimio.com/developers): human-readable docs with per-language code samples. ## Integrate Everything derives from the one OpenAPI spec — pick what fits: - Postman / Insomnia: Import → Link → `https://app.apimio.com/developers/openapi.yaml` (set a bearer-token variable to your key). - Generate an SDK: `npx @openapitools/openapi-generator-cli generate -i https://app.apimio.com/developers/openapi.yaml -g -o ./apimio-sdk`. - AI tools (Claude Code, Cursor): use the `@apimio/mcp` server — `npx -y @apimio/mcp` with `APIMIO_API_KEY` set, so your assistant calls the API directly. It exposes one tool per endpoint below. Defaults to the production API; set `APIMIO_BASE_URL=https://app.apimio.com/api/v1` to target the environment serving this document. ## Products - `GET /products`: list. Query: `limit`, `cursor`, `locale`, `channel`, `group`, `include=variants,media`, `filter[status]` (active|draft), `filter[sku]`, `filter[search]`, `filter[updated_since]`, advanced `filters`. - `POST /products`: create. Body: `title`* , `description`, `sku` (defaults to slug of title), `status` (active|draft), `product_type`, `brand`, `vendor`, `categories[]` (names or ids), `seo{title,description,slug}`, `attributes{handle:value}`, `price` (default variant). Custom attributes go in `attributes` keyed by handle; native/category/variant handles are rejected (422). - `GET /products/{id}`: retrieve (embeds `variants` + `media`). Query: `locale`, `channel`, `group`. - `PATCH /products/{id}`: partial update — only sent fields change; send `null` to clear a relation. Query: `locale` to write a specific locale's localizable values. - `DELETE /products/{id}`: delete. - `POST /products/bulk`: async bulk-create up to 500 products → `202 { job_id }`; poll `GET /jobs/{id}`. ## Variants - `POST /products/{id}/variants`: add. Body: `sku`, `title`, `price`, `compare_at_price`, `cost_price`, `barcode`, `weight`, `weight_unit`, `quantity`, `options[]`. - `PATCH /variants/{id}`: update. - `DELETE /variants/{id}`: delete. ## Pricing & inventory - `GET|PUT /variants/{id}/prices`: per-market prices. PUT `{ prices: [{ market_id, price }] }`; price `0`/`null` deletes the override so that market inherits the canonical price. - `GET|PUT /variants/{id}/inventory`: per-location stock + tracking. PUT `{ track_quantity, continue_selling, levels: [{ location_id, quantity }] }` (`location_id` is a channel-location id from GET inventory). ## Media & store overrides - `POST /products/{id}/media`: attach by URL `{ url*, alt }`. `DELETE /products/{id}/media/{mediaId}`. - `PUT|DELETE /products/{id}/overrides`: store-specific overrides for native fields (title/description/seo). Scope `{ channel|group, locale? }`. PUT `{ scope, fields{title,description,seo_title,seo_description} }`; DELETE `{ scope, fields:[names] }`. ## Catalog - `GET|POST /categories`, `PATCH|DELETE /categories/{id}`. - `GET|POST /brands`, `PATCH|DELETE /brands/{id}`. - `GET|POST /vendors`, `PATCH|DELETE /vendors/{id}`. - `GET|POST /attributes` (custom product attributes only), `DELETE /attributes/{id}`. ## Localization & stores - `GET /locales`: locale codes (+ primary flag). - `GET /channels`: stores. - `GET /channels/{id}/markets`: a channel's markets (id, name, currency, primary). - `GET /channel-groups`: channel groups. ## Webhooks (outbound) - Events: `product.created`, `product.updated`, `product.deleted`, `variant.created`, `variant.updated`, `variant.deleted`. - Payload: `{ id (uuid), type, api_version, created_at, organization_id, data }` — `data` is the resource, or `{ id, object, deleted: true }` for deletes. Dedupe on `id`. - Signature: `Apimio-Signature: t=,v1=` = HMAC-SHA256 of `"."` using the endpoint's signing secret. Also sends `Apimio-Event`, `Apimio-Event-Id`, `Apimio-Delivery` headers. Failed deliveries retry with backoff; an endpoint auto-disables after repeated failures. - `GET|POST /webhook-endpoints`: list / create. Create returns the signing `secret` ONCE. Create body `{ url*, events[], description }`. - `GET|PATCH|DELETE /webhook-endpoints/{id}`. `POST /webhook-endpoints/{id}/test`: send a `ping` event. - `GET /webhook-deliveries` (filter `endpoint_id`/`status`/`event_type`), `GET /webhook-deliveries/{id}`, `POST /webhook-deliveries/{id}/redeliver`. ## Bulk & jobs - `GET /jobs/{id}`: async job status `{ status (processing|completed), total, processed, created, updated, errors, progress_percent, completed_at }`. ## Meta - `GET /ping`: verify a key and see the resolved organization.