API and webhook documentation
Build integrations with NavyFlame: read and write orders, stock, invoices and shipments over a REST API, and receive signed webhooks about events in close to real time.
Introduction
The public NavyFlame API is a server-to-server interface. You authorise every call with an API key, and the responses have a stable, versioned shape. The base address sits on your own tenant domain:
https://{your-slug}.navyflame.com/api/public/v1The full machine-readable specification (OpenAPI 3.1) is at /openapi-public-v1.yaml and imports into Postman, Insomnia or any OpenAPI client.
Quick start
Start by creating a key in the panel (Account - API) and checking the connection with GET /me - it needs no scope, a valid key is enough. It returns your plan, your limits and the key's scopes.
curl https://{your-slug}.navyflame.com/api/public/v1/me \
-H "Authorization: Bearer nf_live_your_key"An example response:
{
"tenant": "your-slug",
"keyPrefix": "nf_live_abc123",
"scopes": ["orders:read", "orders:write"],
"plan": "Professional",
"rateLimitPerMin": 3000,
"apiVersion": "v1"
}Authentication
You pass the key in the Authorization header as a Bearer token (recommended). For tools without Bearer support, the X-API-Key alias works:
Authorization: Bearer nf_live_your_key
# or
X-API-Key: nf_live_your_keyYou create, rotate and revoke keys in the panel. We store only a hash of the key (SHA-256) - you see the full value once, at creation. The key is redacted from every log. Treat it like a password and never put it in the browser.
Scopes
Every key carries scopes. A :write scope includes the matching :read. A missing required scope returns 403 missing_scope.
| Scope | Grants access to |
|---|---|
| orders:read | Read orders (list, details, statistics, status history). |
| orders:write | Change a status, create orders, trigger invoicing. |
| catalog:read | Read the catalogue: products, warehouses, locations, reservations, warehouse documents, bundles. |
| catalog:write | Full catalogue write: products (CRUD), stock and locations, warehouses, reservations, warehouse documents, bundles. |
| invoices:read | Read invoices and invoice statistics. |
| shipments:read | Read shipments and shipping statistics, plus supporting carrier reads (label PDF, pick-up points, services, dispatch times). |
| shipments:write | Shipment lifecycle: create, buy a carrier label (chargeable at the carrier), cancel, refresh tracking, push a tracking number to Allegro. |
| shipping:read | Read the shipping configuration: rules, zones with rates, delivery options, per-product overrides, plus the cost calculator. |
| shipping:write | Write the shipping configuration: rules, zones and per-zone rates, delivery options, per-product shipping overrides. |
| integrations:read | Read the account's connector list (no secrets): key, configuration id, name, enabled and connected state. Never returns credentials. |
| offers:read | Read marketplace offers: per-product product-to-channel mappings, category mappings and live reads from the connector (products, categories, parameters, GPSR entities). Live reads are chargeable (they count towards the connector's API limit) but read-only. |
| offers:write | Write marketplace offers: category mappings (create, bulk, redirect, delete, cache sync) plus publishing, synchronising, pulling, refreshing status and importing real offers. Publishing and sync are chargeable (a real call to the marketplace). |
| reports:read | Sales, product and operations reports plus CSV/XLSX export. Commercially sensitive data. |
| analytics:read | AI analytics: stock forecasts, price suggestions, anomalies, sales forecast, insights. |
| monitoring:read | Integration monitoring (webhooks, invoices, error queue) and account usage. The details contain personal data. |
| monitoring:write | Reprocess an entry from the error queue - it fires the original process (for invoices it issues a real invoice; chargeable). |
| customers:read | Customers (aggregated from orders), statistics, history and CSV export. Full personal data. |
| email:read | E-mail templates and logs. The logs contain recipient addresses and subjects. |
| email:write | Write e-mail templates (plan limit) and send messages (chargeable at the provider; a dedicated limit of about 10 an hour). |
| notifications:read | In-app notifications and preferences. |
| notifications:write | Create in-app notifications (the recipient must be a member of the account) and mark them read. |
| alerts:read | Alert rules and the history of firings. |
| alerts:write | Write alert rules (an e-mail channel indirectly generates sends). |
| postsale:read | Post-sale handling: messages, disputes, returns, claims (read-only). Buyers' personal data. |
| postsale:write | Post-sale writes with no movement of money: replies in threads and disputes, a dispute decision, accepting or rejecting a return (capability-gated per connector). |
| postsale:refunds:write | A separate monetary scope: a real refund to the buyer and a commission refund. Split out so that a leaked key without it cannot move money. |
| templates:read | Read description templates and product templates (with variants). |
| templates:write | Write description and product templates; applying a template creates a product (also requires catalog:write). |
| ai:read | Read the AI settings and the content generation history. |
| ai:write | Generate and translate content with AI (chargeable at your own provider). Writing to a product also requires catalog:write. |
Limits per plan
Limits are aggregated per account, not per key. The X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers on every response tell you the state of the window. Once the limit is exceeded you get 429 with a Retry-After header.
| Plan | API keys | Requests | Webhooks |
|---|---|---|---|
| Basic | 5 | 1000 / min | 15 |
| Professional | 30 | 3000 / min | 50 |
| Enterprise | No limit | No limit | No limit |
Resources and operations
Reads (cursor pagination, filters, sorting):
GET /orders GET /orders/{id} GET /orders/stats
GET /orders/{id}/status-history
GET /warehouse-items GET /warehouse-items/{id}
GET /invoices GET /invoices/{id} GET /invoices/stats
GET /shipments GET /shipments/{id} GET /shipments/statsWrites:
PUT /orders/{id}/status # change the status (transition matrix)
POST /orders # create an order (source: api)
POST /orders/{id}/invoice # trigger invoicing
PATCH /warehouse-items/{id}/stock # stock correction (set / adjust)An example: the most recent orders.
curl "https://{your-slug}.navyflame.com/api/public/v1/orders?limit=25" \
-H "Authorization: Bearer nf_live_your_key"An example: adjusting stock by -3 pieces (atomically).
curl -X PATCH \
"https://{your-slug}.navyflame.com/api/public/v1/warehouse-items/{id}/stock" \
-H "Authorization: Bearer nf_live_your_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f9a...-unikat" \
-d '{"operation":"adjust","quantity":-3}'Analytics, reports and monitoring
Read-only analytical resources (the reports:read, analytics:read and monitoring:read scopes). Amounts in reports and analytics are numbers (aggregates), unlike the transactional resources, where an amount is text with two decimal places.
GET /reports/sales GET /reports/sales/channels
GET /reports/sales/countries GET /reports/products/bestsellers
GET /reports/products/high-returns GET /reports/products/low-rotation
GET /reports/operations/sla GET /reports/operations/fulfillment
GET /reports/export/csv GET /reports/export/xlsx # binary files
GET /ai-ops/status GET /ai-ops/stock-predictions
GET /ai-ops/price-optimizations GET /ai-ops/anomalies
GET /ai-ops/sales-forecast GET /ai-ops/insights # billable (LLM)
GET /monitoring/webhooks GET /monitoring/webhooks/{id}
GET /monitoring/invoice-links GET /monitoring/dlq
GET /monitoring/dlq/{id} GET /monitoring/stats
GET /usage # counters, storage used, plan limitsThe /reports/export/csv and /xlsx exports are the binary exception to JSON - they return a file with a Content-Disposition: attachment header (CSV in UTF-8 with a BOM, semicolon separator). Errors still come back as application/problem+json.
The details at /monitoring/webhooks/{id} and /monitoring/dlq/{id} contain the connector's raw payload (the buyer's personal data), so the monitoring:read scope is marked sensitive. The list endpoints for those resources return no payload.
An example: a summary of the last 30 days of sales.
curl "https://{your-slug}.navyflame.com/api/public/v1/reports/sales?periodDays=30" \
-H "Authorization: Bearer nf_live_your_key"Customers, e-mail, notifications and post-sale handling
Resources containing personal data (the customers:read, email:read, notifications:read, alerts:read and postsale:read scopes). Read-only.
GET /customers GET /customers/{email}
GET /customers/stats GET /customers/{email}/orders
GET /customers/growth GET /customers/export # CSV
GET /email-templates(/{id}) GET /email-logs
GET /notifications(/{id}) GET /notifications/unread-count
GET /notification-preferences # needs ?userId=
GET /alert-rules(/{id}) GET /alert-rules/history
GET /post-sale/capabilities GET /post-sale/inbox
GET /post-sale/conversations(/{id})(/messages)
GET /post-sale/disputes(/{id})(/messages)
GET /post-sale/returns(/{id}) GET /post-sale/refundsA customer is a virtual entity (aggregated from orders), addressed by e-mail. Notifications are tenant-wide (an API key does not represent one user); the optional ?userId= narrows them to a recipient, and the preferences require it. Post-sale handling reads only what is stored in the system (no calls to a marketplace at your expense); check availability in /post-sale/capabilities.
The customers:read, email:read and postsale:read scopes expose personal data - grant them only to integrations you trust and process the data in line with the GDPR.
Catalogue, templates and AI
Full catalogue writes (the catalog:read / catalog:write scopes). Creating and irreversible operations require an Idempotency-Key header - repeating with the same key replays the stored result instead of carrying the operation out again.
# Products
POST/PUT/DELETE /warehouse-items(/{id}) POST /warehouse-items/{id}/backorder
GET /warehouse-items/by-barcode GET /warehouse-items/{id}/stock-history
# Warehouses and locations
GET/POST/PUT/DELETE /warehouses(/{id})
GET /warehouse-items/{id}/locations PUT /warehouse-items/{id}/locations/{warehouseId}
GET/PUT /warehouse-settings
# Reservations and stock documents
GET/POST /reservations DELETE /reservations/{id}
GET/POST /stock-documents(/{id}) POST /stock-documents/{id}/lines
POST /stock-documents/{id}/commit POST /stock-documents/{id}/cancel
# Bundles
GET /bundles(/{id}) PUT /bundles/{id} DELETE /bundles/{id}Description and product templates (the templates:read / templates:write scopes). Applying a template creates a product, so POST /product-templates/{id}/apply additionally requires catalog:write.
GET/POST/PUT/DELETE /description-templates(/{id})
GET/POST/PUT/DELETE /product-templates(/{id})
PUT /product-templates/{id}/variants # replace the whole set of variants
POST /product-templates/{id}/variants # add a variant
PATCH/DELETE /product-templates/{id}/variants/{variantId}
POST /product-templates/{id}/apply # + catalog:writeAI content (the ai:read / ai:write scopes). Generating and translating are chargeable operations at your own AI provider (you configure the key under integrations), which is why Idempotency-Key is required - a repeat replays the result rather than charging for it again. Writing the result into a product (applyToProduct / createNewProducts) additionally requires catalog:write. Bulk operations return a shortened preview per item - the full text comes from /ai/generation-logs or from the product.
GET/PUT /ai/settings GET /ai/generation-logs
POST /ai/generate-description POST /ai/translate
POST /ai/batch-generate POST /ai/batch-translate # up to 50 items
POST /ai/preview-template # no AI; templates:read + catalog:readAn example: creating a description template (idempotently).
curl -X POST \
"https://{your-slug}.navyflame.com/api/public/v1/description-templates" \
-H "Authorization: Bearer nf_live_your_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1e-tpl-001" \
-d '{"name":"Premium description","templateBody":"{{productName}} - {{price}} {{currency}}"}'Shipping configuration
Shipping configuration (the shipping:read / shipping:write scopes): shipping rules, zones with rates, delivery options and per-product overrides, plus the cost calculator. Creating operations require an Idempotency-Key header. In writes, you pass amounts and weight as numbers (not text). This is configuration, not carrier handling - dispatching shipments and labels appear in a separate phase.
# Shipping rules
GET/POST/PUT/DELETE /shipping-rules(/{id})
# Zones and rates (a rate belongs to its owning zone)
GET/POST/PUT/DELETE /shipping-zones(/{id})
POST /shipping-zones/{id}/rates PUT/DELETE /shipping-zones/{id}/rates/{rateId}
# Delivery options
GET/POST/PUT/DELETE /delivery-options(/{id})
# Per-product shipping overrides
GET/POST /products/{productId}/shipping-overrides
PUT/DELETE /product-shipping-overrides/{id}
# Cost calculator (a POST, but it only needs shipping:read)
POST /shipping/calculateNames are not unique, so there is no duplicate conflict (409). The deliveryOptionId on a rate or an override must point at your own delivery option (otherwise 422), and a rate outside your zone returns 404. The calculator returns the options sorted by cost, ascending.
An example: working out the shipping cost to Poland.
curl -X POST \
"https://{your-slug}.navyflame.com/api/public/v1/shipping/calculate" \
-H "Authorization: Bearer nf_live_your_key" \
-H "Content-Type: application/json" \
-d '{"countryCode":"PL","orderValue":199.99,"totalWeightKg":1.5,"itemCount":2}'Shipments, labels and carrier
Shipment lifecycle (the shipments:write scope) and supporting carrier reads (the shipments:read scope): you create a shipment, order a label at the carrier, cancel, refresh tracking and push the tracking number to Allegro. You pass weight, dimensions and amounts as numbers.
# Creating a shipment (status Draft; costs nothing)
POST /shipments # one at a time
POST /shipments/bulk # in bulk, up to 100 (partial success)
# The carrier label
POST /shipments/{id}/label # BILLABLE - buying a label (Idempotency-Key required)
GET /shipments/{id}/label # fetch the label (PDF, binary)
# The rest of the lifecycle
DELETE /shipments/{id} # delete a draft or cancel with the carrier
POST /shipments/{id}/refresh-tracking
POST /shipments/{id}/push-tracking
# Helper reads (shipments:read)
GET /shipments/points # pickup points (InPost, Poczta Polska, Furgonetka)
GET /couriers/{provider}/services # carrier services (Apaczka, Furgonetka)
GET /shipments/allegro/delivery-services # Allegro Delivery services
POST /shipments/available-dates # pickup dates (DHL; a POST, but shipments:read)Ordering a label is chargeable - it buys a real shipment at the carrier. That is why POST /shipments/{id}/label requires an Idempotency-Key header: repeating with the same key replays the stored result and never buys a second label (the protection sits on the server). You can order a label only for a shipment in Draft. Downloading the label (GET .../label) is the binary exception - it returns application/pdf, not JSON.
A carrier or Allegro connector that is not connected returns 403 not_supported (never a 500), and an error on the carrier's or Allegro's side returns 502 bad_gateway. Supporting reads for an unsupported provider return { "supported": false } rather than an error. A cancellation does not change the local status if the carrier rejects the operation.
An example: ordering a label (idempotently, chargeable).
curl -X POST \
"https://{your-slug}.navyflame.com/api/public/v1/shipments/{id}/label" \
-H "Authorization: Bearer nf_live_your_key" \
-H "Idempotency-Key: 6f9a...-label-001"Connectors and marketplace offers
Connector discovery (the integrations:read scope) plus reads and writes of marketplace offers (the offers:read / offers:write scopes). Some operations are local (from your own data, at no cost) and some are live, chargeable calls to the connector - marked below.
Discovery and local reads (at no cost):
GET /connectors # the list of connectors (no secrets)
GET /warehouse-items/{id}/listings # the product's marketplace offers (keyset)
GET /category-mappings # category mappings (keyset)GET /connectors returns a strictly limited set of fields - key, configId, displayName, enabled and connected - and never returns credentials (no API keys, tokens or connector passwords). The connected field is a computed state: the connector is enabled and has credentials stored. You pass the configId value as the ?sourceConfigId= or ?destConfigId= filter in category mappings.
A product's offers (/warehouse-items/{id}/listings) tie a stock item to an offer in a channel (the external id, the price and status on the marketplace side, and any per-channel price override). Every offer object carries an instanceId, the durable id of one particular connection, and an instanceName to show the user. That way the same product can have independent offers, prices and synchronisation state on, say, ebay.de and ebay.com. The lastSyncError field is scrubbed of credential patterns a connector error might contain.
An example: the account's connectors.
curl "https://{your-slug}.navyflame.com/api/public/v1/connectors" \
-H "Authorization: Bearer nf_live_your_key"Live marketplace reads (the offers:read scope) make a real call to the connector - they are chargeable (they count towards the connector's API limit) but strictly read-only (they neither publish nor change an offer). A connector that is not connected returns 403 not_supported (never 500), an error on the marketplace side returns 502 bad_gateway. The shape of the data depends on the connector.
GET /marketplace/{connectorKey}/products
GET /marketplace/{connectorKey}/products/{externalProductId}
GET /marketplace/{connectorKey}/categories?q=fraza
GET /marketplace/{connectorKey}/categories/{categoryId}/parameters
GET /marketplace/{connectorKey}/responsible-producers
GET /marketplace/{connectorKey}/responsible-personsWriting category mappings (the offers:write scope) are local writes - none of them publishes or changes a real offer visible to a buyer (publishing a real offer is described below). POST /category-mappings requires an Idempotency-Key header (a duplicate gives 409); sync refreshes the local category cache from the connector (chargeable, but idempotent).
POST /category-mappings # create (Idempotency-Key required; a duplicate -> 409)
POST /category-mappings/bulk # in bulk (up to 1000; duplicates skipped)
PUT /category-mappings/{id} # repoint the mapping at another target
DELETE /category-mappings/{id} # delete the mapping
POST /category-mappings/sync # refresh the local category cache (billable, idempotent)Publishing and synchronising real offers (the offers:write scope) creates or updates an offer visible to buyers in the channel - chargeable operations (a real call to the marketplace). publish and sync require an Idempotency-Key header (a retry with the same key replays the stored response instead of repeating a chargeable call). publish returns 409 when the product is already published on that particular connection (or its publication is in progress) - an offer on a different instance of the same connector causes no conflict. Use sync for that same configId instead. A connector that is not connected gives 403 not_supported, an error on the marketplace side gives 502 bad_gateway.
In the body of publish, sync and PUT .../pricing, pass the configId you got from GET /connectors. If a connector has several enabled connections, omitting the field ends in 422 validation_failed. The API does not pick a default account blindly. Where exactly one enabled connection exists, it can resolve it automatically.
Important on a 502 during publication: a timeout or a 5xx from the marketplace is ambiguous - the offer may have been created. A retry with the same Idempotency-Key replays the same 502 (it does not publish again). Verify the offer in the channel and only retry with a new Idempotency-Key once you know the offer was not created.
POST /warehouse-items/{id}/publish/{connectorKey} # publish the offer (billable; Idempotency-Key required)
POST /warehouse-items/{id}/sync/{connectorKey} # update the offer (billable; Idempotency-Key required)
POST /warehouse-items/{id}/pull/{connectorKey} # pull the offer from the channel into the catalogue
POST /warehouse-items/{id}/refresh-status/{connectorKey} # refresh the status of one offer
POST /warehouse-items/refresh-status # in bulk (up to 100 products)
PUT /warehouse-items/{id}/listings/{connectorKey}/pricing # price per instance (local, no outbound call)
# Body for publish / sync:
{ "configId": "8d1ec28d-..." }
# Body for pricing:
{ "configId": "8d1ec28d-...", "mode": "markup", "value": 12.5 }Importing an offer into stock ( POST /catalog/import) creates a stock item from a marketplace product. It requires two scopes at once - offers:write and catalog:write (missing either gives 403 missing_scope) - plus an Idempotency-Key header. A duplicate SKU or barcode gives 409 conflict.
POST /catalog/import # import a marketplace product -> a stock item (offers:write + catalog:write)Writes: notifications, alerts, e-mail and post-sale handling
Writes for communication, reprocessing and post-sale handling (the notifications:write, alerts:write, email:write, monitoring:write, postsale:write and postsale:refunds:write scopes). Creating and irreversible operations require an Idempotency-Key header; naturally idempotent operations (marking read, toggling) honour it optionally.
# Notifications (notifications:write)
POST /notifications # create (the recipient must be a member of the account; Idempotency-Key required)
POST /notifications/{id}/read # mark one as read
POST /notifications/read-all # mark all (needs ?userId=)
# Alerts (alerts:write)
POST /alert-rules # create a rule (Idempotency-Key required)
PUT/DELETE /alert-rules/{id} # update / delete
POST /alert-rules/{id}/toggle # enable / disable
# E-mail (email:write)
POST/PUT/DELETE /email-templates(/{id}) # templates (plan limit; a duplicate name -> 409)
POST /emails/send # BILLABLE - a real send (Idempotency-Key required)Creating a notification checks that recipientUserId is a member of the account (a non-member gives 422, an uncertain check gives 503) - an API key must not be able to flood any user's inbox. E-mail templates are subject to the plan limit (Basic 10, Professional 24, Enterprise unlimited), giving 403 plan_limit, and a duplicate name gives 409. Sending e-mail (`POST /emails/send`) is chargeable and covered by a dedicated limit of about 10 an hour per account ( 429); a provider with no configuration gives 403 not_supported, a transport error gives 502 bad_gateway. An alert rule with the email or both channel indirectly generates sends when it fires.
Reprocessing the error queue (the monitoring:write scope) fires the original process of a queue entry - for invoicing sources it issues a real invoice, so it is chargeable and destructive, and Idempotency-Key is required. Only three sources are retriable (shopify_to_wfirma_pipeline, shopify, wfirma_invoice) - others give 422; an unavailable Temporal gives 503. A repeat is idempotent by a deterministic process identifier (it returns alreadyRunning).
POST /monitoring/dlq/{id}/retry # BILLABLE - runs the original process (Idempotency-Key required)Post-sale handling (the postsale:write scope): you reply in threads and disputes, change a dispute's status, and accept or reject a return. Ownership of the thread, dispute or return is checked before the channel is called (someone else's or a non-existent identifier gives 404), and the operations are capability-gated per connector - a missing capability or method returns 403 not_supported (never 500). A channel error gives 502 bad_gateway.
# Post-sale handling with no movement of money (postsale:write)
POST /post-sale/conversations/{id}/messages # reply to the buyer (Idempotency-Key required)
POST /post-sale/conversations/{id}/read # mark the thread read
POST /post-sale/disputes/{id}/messages # reply in a dispute (Idempotency-Key required)
POST /post-sale/disputes/{id}/status # a decision in a dispute (see money isolation)
POST /post-sale/returns/{id}/accept # accept the return
POST /post-sale/returns/{id}/reject # reject the return
# Refunds - a SEPARATE money scope (postsale:refunds:write)
POST /post-sale/refunds/issue # a REAL refund to the buyer
POST /post-sale/refunds/commission-claim # a commission refundMoney is isolated. A refund has its own `postsale:refunds:write` scope, split off from postsale:write, so that a leaked key without it cannot move money. The POST /post-sale/disputes/{id}/status route belongs to postsale:write, but if the decision moves money to the buyer (a partialRefund present, or the ACCEPTED_REFUND / ACCEPTED_PARTIAL_REFUND status), the key must additionally carry `postsale:refunds:write` - otherwise the request returns 403 missing_scope and nothing is carried out.
Retrying refunds. refunds/issue and commission-claim are idempotent on the channel side (your Idempotency-Key becomes the operation's identifier at the channel) - on a 502, retry with the same key and the channel will deduplicate. A dispute decision that moves money, however, has no idempotency at the channel - on an uncertain error, verify the outcome in the sales channel and only retry with a new Idempotency-Key (the same 502 is returned for the same key).
An example: a real refund (idempotently, chargeable).
curl -X POST \
"https://{your-slug}.navyflame.com/api/public/v1/post-sale/refunds/issue" \
-H "Authorization: Bearer nf_live_your_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f9a...-refund-001" \
-d '{"connectorKey":"shopify","instanceId":"shop-1","orderExternalId":"1234","amount":49.99,"currency":"PLN"}'The full list of operations (per resource)
A complete, navigable index of every public v1 endpoint grouped by resource. This is the layer for a human - the full field-level request and response schemas (179 of them) are in the OpenAPI specification . Every living endpoint is listed here.
- Scope - the scope the key needs. A
:writescope includes the matching:read. The notationa + bmeans cross-scope (both are needed at once). - Idem. -
K!= theIdempotency-Keyheader is required;K?= honoured (optional); blank = not applicable (a read). - Errors - the key codes beyond the universal
401,403and429(plus500/503, which can occur anywhere).
Meta
| Operation | Scope | Idem. | Purpose | Errors |
|---|---|---|---|---|
GET /me | (brak) | - | Key introspection: plan, limits, scopes. Needs no scope. | - |
Orders
| Operation | Scope | Idem. | Purpose | Errors |
|---|---|---|---|---|
GET /orders | orders:read | - | List orders (keyset; filters for status / source / date / search). | 422 |
POST /orders | orders:write | K! | Create an order (source=api); the server computes the amounts. | 409,413,415,422 |
GET /orders/{id} | orders:read | - | Order details. | 404 |
PUT /orders/{id}/status | orders:write | K? | Change the status (transition matrix; writeback to Allegro). | 404,415,422 |
POST /orders/{id}/invoice | orders:write | K! | Trigger invoicing (asynchronous). | 404,409,422 |
GET /orders/stats | orders:read | - | Order statistics (counters per status plus Paid revenue). | 422 |
GET /orders/{id}/status-history | orders:read | - | History of an order's real status transitions. | 404,422 |
Catalogue: products
| Operation | Scope | Idem. | Purpose | Errors |
|---|---|---|---|---|
GET /warehouse-items | catalog:read | - | List products (keyset; filters for status / sku / barcode). | 422 |
POST /warehouse-items | catalog:write | K! | Create a warehouse product. | 409,413,415,422 |
GET /warehouse-items/{id} | catalog:read | - | Product details (with variants). | 404 |
PUT /warehouse-items/{id} | catalog:write | K! | Update a product. | 404,409,413,415,422 |
DELETE /warehouse-items/{id} | catalog:write | K! | Archive a product (soft delete). | 404,409,422 |
PATCH /warehouse-items/{id}/stock | catalog:write | K? | Set or adjust stock (set / adjust, atomically). | 404,415,422 |
GET /warehouse-items/by-barcode | catalog:read | - | Find a product or variant by barcode. | 404,422 |
GET /warehouse-items/{id}/stock-history | catalog:read | - | History of a product's stock changes (keyset). | 404,422 |
POST /warehouse-items/{id}/backorder | catalog:write | K! | Turn a product's backorder on or off. | 404,409,415,422 |
Catalogue: warehouses and locations
| Operation | Scope | Idem. | Purpose | Errors |
|---|---|---|---|---|
GET /warehouses | catalog:read | - | List warehouses / locations. | - |
POST /warehouses | catalog:write | K! | Create a warehouse / location. | 409,415,422 |
PUT /warehouses/{id} | catalog:write | K! | Update a warehouse. | 404,409,415,422 |
DELETE /warehouses/{id} | catalog:write | K! | Delete a warehouse. | 404,409,422 |
GET /warehouse-items/{id}/locations | catalog:read | - | A product's stock broken down by location. | 404 |
PUT /warehouse-items/{id}/locations/{warehouseId} | catalog:write | K! | Set a product's stock at a given location. | 404,409,415,422 |
GET /warehouse-settings | catalog:read | - | Warehouse automation settings. | - |
PUT /warehouse-settings | catalog:write | K! | Change the warehouse automation settings. | 409,415,422 |
Catalogue: reservations and warehouse documents
| Operation | Scope | Idem. | Purpose | Errors |
|---|---|---|---|---|
GET /reservations | catalog:read | - | List stock reservations (keyset). | 422 |
POST /reservations | catalog:write | K! | Reserve a product's stock. | 404,409,415,422 |
DELETE /reservations/{id} | catalog:write | K! | Release a reservation. | 404,409 |
GET /stock-documents | catalog:read | - | List warehouse documents (keyset). | 422 |
POST /stock-documents | catalog:write | K! | Create a warehouse document (draft). | 409,415,422 |
GET /stock-documents/{id} | catalog:read | - | Document details (with lines). | 404 |
POST /stock-documents/{id}/lines | catalog:write | K! | Add a line to a document. | 404,409,415,422 |
DELETE /stock-documents/{id}/lines/{lineId} | catalog:write | K! | Remove a line from a document. | 404,409,422 |
POST /stock-documents/{id}/commit | catalog:write | K! | Commit a document (apply the stock change). | 404,409,422 |
POST /stock-documents/{id}/cancel | catalog:write | K! | Cancel a warehouse document. | 404,409,422 |
Catalogue: bundles
| Operation | Scope | Idem. | Purpose | Errors |
|---|---|---|---|---|
GET /bundles | catalog:read | - | List bundles (keyset). | 422 |
GET /bundles/{id} | catalog:read | - | Bundle details (components plus computed stock). | 404 |
PUT /bundles/{id} | catalog:write | K! | Set a bundle's components. | 404,409,415,422 |
DELETE /bundles/{id} | catalog:write | K! | Delete a bundle (clear its components). | 404,409,422 |
Templates (descriptions and products)
| Operation | Scope | Idem. | Purpose | Errors |
|---|---|---|---|---|
GET /description-templates | templates:read | - | List description templates (keyset). | 422 |
POST /description-templates | templates:write | K! | Create a description template. | 409,413,415,422 |
GET /description-templates/{id} | templates:read | - | Description template details. | 404 |
PUT /description-templates/{id} | templates:write | K? | Update a description template. | 404,409,413,415,422 |
DELETE /description-templates/{id} | templates:write | K? | Delete a description template. | 404 |
GET /product-templates | templates:read | - | List product templates (keyset). | 422 |
POST /product-templates | templates:write | K! | Create a product template. | 409,413,415,422 |
GET /product-templates/{id} | templates:read | - | Product template details (with variants). | 404 |
PUT /product-templates/{id} | templates:write | K? | Update a product template. | 404,409,413,415,422 |
DELETE /product-templates/{id} | templates:write | K? | Delete a product template. | 404 |
PUT /product-templates/{id}/variants | templates:write | K? | Replace a template's whole set of variants (bulk). | 404,409,413,415,422 |
POST /product-templates/{id}/variants | templates:write | K! | Add a variant to a template. | 404,409,415,422 |
PATCH /product-templates/{id}/variants/{variantId} | templates:write | K? | Update a template variant. | 404,409,415,422 |
DELETE /product-templates/{id}/variants/{variantId} | templates:write | K? | Delete a template variant. | 404 |
POST /product-templates/{id}/apply | templates:write + catalog:write | K! | Create a product from a template (cross-scope). | 404,409,422 |
AI: content generation
| Operation | Scope | Idem. | Purpose | Errors |
|---|---|---|---|---|
GET /ai/settings | ai:read | - | Global AI generation settings. | - |
PUT /ai/settings | ai:write | K? | Change the global AI settings. | 409,415,422 |
GET /ai/generation-logs | ai:read | - | History of AI calls (keyset). | 422 |
POST /ai/generate-description | ai:write | K! | Generate a product description (billable). | 404,409,415,422 |
POST /ai/translate | ai:write | K! | Translate a product (billable). | 404,409,415,422 |
POST /ai/batch-generate | ai:write | K! | Generate descriptions in bulk (billable; max 50). | 409,413,415,422 |
POST /ai/batch-translate | ai:write | K! | Translate in bulk (billable; max 50). | 409,413,415,422 |
POST /ai/preview-template | templates:read + catalog:read | - | Preview a template with product data (no LLM; cross-scope). | 404,413,415,422 |
AI analytics (AI-Ops)
| Operation | Scope | Idem. | Purpose | Errors |
|---|---|---|---|---|
GET /ai-ops/status | analytics:read | - | AI configuration status plus data coverage. | - |
GET /ai-ops/stock-predictions | analytics:read | - | Stock run-out forecast per SKU (cached 5 min). | 422 |
GET /ai-ops/price-optimizations | analytics:read | - | Price suggestions from the price change history (cached 5 min). | 422 |
GET /ai-ops/anomalies | analytics:read | - | Anomalies against the rolling average (cached 5 min). | 422 |
GET /ai-ops/sales-forecast | analytics:read | - | Sales forecast (regression; days = horizon). | 422 |
GET /ai-ops/insights | analytics:read | - | Insights (billable, uses the tenant's LLM; cached 30 min; rule-based fallback). | - |
Invoices
| Operation | Scope | Idem. | Purpose | Errors |
|---|---|---|---|---|
GET /invoices | invoices:read | - | List invoices (keyset; issueDate desc). | 422 |
GET /invoices/{id} | invoices:read | - | Invoice details (with lines and the tax-authority record). | 404 |
GET /invoices/stats | invoices:read | - | Invoice statistics (lifetime KPIs). | - |
Shipments and carrier
| Operation | Scope | Idem. | Purpose | Errors |
|---|---|---|---|---|
GET /shipments | shipments:read | - | List shipments (keyset). | 422 |
GET /shipments/{id} | shipments:read | - | Shipment details (with tracking history). | 404 |
GET /shipments/stats | shipments:read | - | Shipment statistics (counters per status). | - |
POST /shipments | shipments:write | K! | Create a shipment (Draft status; no cost). | 409,415,422 |
POST /shipments/bulk | shipments:write | K! | Create shipments in bulk (max 100, partial success). | 409,413,415,422 |
POST /shipments/{id}/label | shipments:write | K! | Order a carrier label (billable - a real cost). | 404,409 |
GET /shipments/{id}/label | shipments:read | - | Download the carrier label (PDF, binary). | 404,409,502 |
DELETE /shipments/{id} | shipments:write | K! | Delete an unsent draft or cancel a shipment at the carrier. | 404,409,502 |
POST /shipments/{id}/refresh-tracking | shipments:write | K? | Refresh the status and tracking at the carrier. | 404,409,502 |
POST /shipments/{id}/push-tracking | shipments:write | K! | Push the tracking number to the order in the marketplace it came from (Allegro, TikTok Shop). | 404,400,502 |
POST /shipments/{id}/push-to-allegro | shipments:write | K! | The same, but for Allegro only. Kept for backwards compatibility - use push-tracking in new integrations. | 404,409,422,502 |
POST /shipments/available-dates | shipments:read | - | Available dispatch dates (DHL; POST-but-read). | 415,422,502 |
GET /shipments/points | shipments:read | - | Pick-up points / parcel lockers (InPost, Poczta Polska, Furgonetka; the service parameter narrows Furgonetka results to that service's carrier). | 502 |
GET /couriers/{provider}/services | shipments:read | - | List a carrier's services (Apaczka, Furgonetka). | 502 |
GET /shipments/allegro/delivery-services | shipments:read | - | List Allegro Delivery services. | 502 |
Shipping configuration
| Operation | Scope | Idem. | Purpose | Errors |
|---|---|---|---|---|
GET /shipping-rules | shipping:read | - | List shipping rules (keyset). | 422 |
POST /shipping-rules | shipping:write | K! | Create a shipping rule. | 409,415,422 |
GET /shipping-rules/{id} | shipping:read | - | Shipping rule details. | 404 |
PUT /shipping-rules/{id} | shipping:write | K? | Update a shipping rule. | 404,409,415,422 |
DELETE /shipping-rules/{id} | shipping:write | K? | Delete a shipping rule. | 404 |
GET /shipping-zones | shipping:read | - | List shipping zones (with nested rates). | 422 |
POST /shipping-zones | shipping:write | K! | Create a shipping zone. | 409,415,422 |
GET /shipping-zones/{id} | shipping:read | - | Zone details (with nested rates). | 404 |
PUT /shipping-zones/{id} | shipping:write | K? | Update a shipping zone. | 404,409,415,422 |
DELETE /shipping-zones/{id} | shipping:write | K? | Delete a shipping zone. | 404 |
POST /shipping-zones/{id}/rates | shipping:write | K! | Add a rate to a zone. | 404,409,415,422 |
PUT /shipping-zones/{id}/rates/{rateId} | shipping:write | K? | Update a rate in a zone. | 404,409,415,422 |
DELETE /shipping-zones/{id}/rates/{rateId} | shipping:write | K? | Delete a rate from a zone. | 404 |
GET /delivery-options | shipping:read | - | List delivery options (keyset). | 422 |
POST /delivery-options | shipping:write | K! | Create a delivery option. | 409,415,422 |
GET /delivery-options/{id} | shipping:read | - | Delivery option details. | 404 |
PUT /delivery-options/{id} | shipping:write | K? | Update a delivery option. | 404,409,415,422 |
DELETE /delivery-options/{id} | shipping:write | K? | Delete a delivery option. | 404 |
GET /products/{productId}/shipping-overrides | shipping:read | - | Shipping overrides for a product (keyset). | 404,422 |
POST /products/{productId}/shipping-overrides | shipping:write | K! | Create a shipping override for a product. | 404,409,415,422 |
PUT /product-shipping-overrides/{id} | shipping:write | K? | Update a product's shipping override. | 404,409,415,422 |
DELETE /product-shipping-overrides/{id} | shipping:write | K? | Delete a product's shipping override. | 404 |
POST /shipping/calculate | shipping:read | - | Work out the shipping options and cost (POST-but-read). | 415,422 |
Customers
| Operation | Scope | Idem. | Purpose | Errors |
|---|---|---|---|---|
GET /customers | customers:read | - | List customers (aggregated by e-mail; keyset). | 422 |
GET /customers/stats | customers:read | - | Customer statistics (KPIs). | - |
GET /customers/growth | customers:read | - | Customer growth over time (a monthly series). | 422 |
GET /customers/export | customers:read | - | Export customers to CSV (binary). | 422 |
GET /customers/{email} | customers:read | - | Customer details (aggregate plus addresses). | 404 |
GET /customers/{email}/orders | customers:read | - | A customer's orders (keyset). | 404,422 |
| Operation | Scope | Idem. | Purpose | Errors |
|---|---|---|---|---|
GET /email-templates | email:read | - | List e-mail templates (keyset). | 422 |
GET /email-templates/{id} | email:read | - | E-mail template details. | 404 |
GET /email-logs | email:read | - | List the logs of e-mails sent (keyset; personal data). | 422 |
POST /email-templates | email:write | K! | Create an e-mail template (plan limit). | 409,413,415,422 |
PUT /email-templates/{id} | email:write | K? | Update an e-mail template. | 404,409,413,415,422 |
DELETE /email-templates/{id} | email:write | K? | Delete an e-mail template. | 404,409 |
POST /emails/send | email:write | K! | Send an e-mail message (billable; about 10 an hour). | 409,413,415,422,502 |
Notifications
| Operation | Scope | Idem. | Purpose | Errors |
|---|---|---|---|---|
GET /notifications | notifications:read | - | List tenant-wide notifications (keyset; ?userId narrows it). | 422 |
GET /notifications/{id} | notifications:read | - | Notification details. | 404 |
GET /notifications/unread-count | notifications:read | - | The number of unread notifications. | 422 |
GET /notification-preferences | notifications:read | - | Notification preferences (requires ?userId=). | 422 |
POST /notifications | notifications:write | K! | Create a notification (the recipient must be a member of the account). | 413,415,422 |
POST /notifications/{id}/read | notifications:write | K? | Mark a notification read. | 404,409 |
POST /notifications/read-all | notifications:write | K? | Mark all read (requires ?userId=). | 409,422 |
Alerts
| Operation | Scope | Idem. | Purpose | Errors |
|---|---|---|---|---|
GET /alert-rules | alerts:read | - | List alert rules (keyset). | 422 |
GET /alert-rules/history | alerts:read | - | History of alert firings (keyset). | 422 |
GET /alert-rules/{id} | alerts:read | - | Alert rule details. | 404 |
POST /alert-rules | alerts:write | K! | Create an alert rule. | 413,415,422 |
PUT /alert-rules/{id} | alerts:write | K? | Update an alert rule. | 404,413,415,422 |
DELETE /alert-rules/{id} | alerts:write | K? | Delete an alert rule. | 404,409 |
POST /alert-rules/{id}/toggle | alerts:write | K? | Turn an alert rule on or off. | 404,409,422 |
Post-sale handling
| Operation | Scope | Idem. | Purpose | Errors |
|---|---|---|---|---|
GET /post-sale/capabilities | postsale:read | - | A map of post-sale capabilities per connector instance. | - |
GET /post-sale/inbox | postsale:read | - | The aggregate inbox (a summary of threads and disputes). | - |
GET /post-sale/conversations | postsale:read | - | List threads (keyset). | 422 |
GET /post-sale/conversations/{id} | postsale:read | - | Thread details. | 404 |
GET /post-sale/conversations/{id}/messages | postsale:read | - | Messages in a thread (keyset). | 404,422 |
GET /post-sale/disputes | postsale:read | - | List disputes / complaints (keyset). | 422 |
GET /post-sale/disputes/{id} | postsale:read | - | Dispute details. | 404 |
GET /post-sale/disputes/{id}/messages | postsale:read | - | Messages in a dispute (keyset). | 404,422 |
GET /post-sale/returns | postsale:read | - | List returns (keyset). | 422 |
GET /post-sale/returns/{id} | postsale:read | - | Return details. | 404 |
GET /post-sale/refunds | postsale:read | - | List refunds (keyset). | 422 |
POST /post-sale/conversations/{id}/messages | postsale:write | K! | Reply to the buyer in a thread. | 404,413,415,422,502 |
POST /post-sale/conversations/{id}/read | postsale:write | K? | Mark a thread read. | 404,409 |
POST /post-sale/disputes/{id}/messages | postsale:write | K! | Reply in a dispute. | 404,413,415,422,502 |
POST /post-sale/disputes/{id}/status | postsale:write | K! | Change a dispute's status (moving money requires refunds:write). | 404,413,415,422,502 |
POST /post-sale/returns/{id}/accept | postsale:write | K! | Accept a return. | 404,409,502 |
POST /post-sale/returns/{id}/reject | postsale:write | K! | Reject a return. | 404,413,415,422,502 |
Refunds (an isolated monetary scope)
| Operation | Scope | Idem. | Purpose | Errors |
|---|---|---|---|---|
POST /post-sale/refunds/issue | postsale:refunds:write | K! | A real refund to the buyer (idempotent per channel). | 413,415,422,502 |
POST /post-sale/refunds/commission-claim | postsale:refunds:write | K! | Claim a commission refund. | 413,415,422,502 |
Reports
| Operation | Scope | Idem. | Purpose | Errors |
|---|---|---|---|---|
GET /reports/sales | reports:read | - | Sales report (a time series plus aggregates and margin). | 422 |
GET /reports/sales/channels | reports:read | - | Sales split by channel. | 422 |
GET /reports/sales/countries | reports:read | - | Sales split by country. | 422 |
GET /reports/products/bestsellers | reports:read | - | Bestsellers (sales plus gross margin). | 422 |
GET /reports/products/high-returns | reports:read | - | Products with a high return rate. | 422 |
GET /reports/products/low-rotation | reports:read | - | Products with low rotation. | 422 |
GET /reports/operations/sla | reports:read | - | Fulfilment SLA metrics. | 422 |
GET /reports/operations/fulfillment | reports:read | - | Fulfilment time per channel. | 422 |
GET /reports/export/csv | reports:read | - | Export a report to CSV (binary). | 422 |
GET /reports/export/xlsx | reports:read | - | Export a report to XLSX (binary). | 422 |
Monitoring and usage
| Operation | Scope | Idem. | Purpose | Errors |
|---|---|---|---|---|
GET /monitoring/webhooks | monitoring:read | - | List incoming webhooks (no payload). | 422 |
GET /monitoring/webhooks/{id} | monitoring:read | - | Webhook detail (raw payload - personal data). | 404 |
GET /monitoring/invoice-links | monitoring:read | - | Invoicing links (order to invoice). | 422 |
GET /monitoring/dlq | monitoring:read | - | The error queue (dead-letter, no payload). | 422 |
GET /monitoring/dlq/{id} | monitoring:read | - | An error queue entry (the original payload - personal data). | 404 |
GET /monitoring/stats | monitoring:read | - | Aggregate integration statistics. | - |
GET /usage | monitoring:read | - | Account usage (counters, storage, plan limits). | - |
POST /monitoring/dlq/{id}/retry | monitoring:write | K! | Retry an error queue entry (billable; fires the original workflow). | 404,409,422 |
Connectors and marketplace offers
| Operation | Scope | Idem. | Purpose | Errors |
|---|---|---|---|---|
GET /connectors | integrations:read | - | List the tenant's connectors (a projection with no secrets). | - |
GET /warehouse-items/{id}/listings | offers:read | - | One product's marketplace offers, separately for each instance (keyset). | 404,422 |
GET /category-mappings | offers:read | - | List category mappings (keyset). | 422 |
POST /category-mappings | offers:write | K! | Create a category mapping (locally). | 409,413,415,422 |
POST /category-mappings/bulk | offers:write | K? | Create mappings in bulk (max 1000). | 409,413,415,422 |
PUT /category-mappings/{id} | offers:write | K? | Redirect a mapping to a different target. | 404,409,413,415,422 |
DELETE /category-mappings/{id} | offers:write | K? | Delete a category mapping. | 404,409 |
POST /category-mappings/sync | offers:write | K? | Refresh the local category cache (billable). | 409,413,415,422,502 |
GET /marketplace/{connectorKey}/products | offers:read | - | A live list of products from the connector (billable). | 422,502 |
GET /marketplace/{connectorKey}/products/{externalProductId} | offers:read | - | A live single product from the connector (billable). | 502 |
GET /marketplace/{connectorKey}/categories | offers:read | - | A live search of the connector's categories (billable). | 502 |
GET /marketplace/{connectorKey}/categories/{categoryId}/parameters | offers:read | - | Live category parameters (billable). | 502 |
GET /marketplace/{connectorKey}/responsible-producers | offers:read | - | A live list of GPSR manufacturers (billable). | 502 |
GET /marketplace/{connectorKey}/responsible-persons | offers:read | - | A live list of GPSR responsible persons (billable). | 502 |
POST /warehouse-items/{id}/publish/{connectorKey} | offers:write | K! | Publish an offer on the instance named by configId (billable, a real offer). | 404,409,413,415,422,502 |
POST /warehouse-items/{id}/sync/{connectorKey} | offers:write | K! | Update an offer on the instance named by configId (billable). | 404,409,413,415,422,502 |
POST /warehouse-items/{id}/pull/{connectorKey} | offers:write | K? | Pull an offer from the connector into the catalogue (billable). | 404,409,502 |
POST /warehouse-items/{id}/refresh-status/{connectorKey} | offers:write | K? | Refresh the status of one offer (billable). | 404,409,502 |
POST /warehouse-items/refresh-status | offers:write | K? | Refresh the status of offers in bulk (max 100). | 409,413,415,422 |
PUT /warehouse-items/{id}/listings/{connectorKey}/pricing | offers:write | K? | Set the price for a specific instance from configId (locally, with no call). | 404,409,413,415,422 |
POST /catalog/import | offers:write + catalog:write | K! | Import a marketplace product into stock (cross-scope). | 409,413,415,422 |
Pagination and filters
Lists are paginated by cursor (no offset). The response carries pagination.nextCursor - pass it in the cursor parameter to fetch the next page. The updatedSince parameter (an ISO date-time with a zone) returns records changed since that moment. For pull synchronisation, poll with an overlapping window and deduplicate by id.
GET /orders?limit=50&cursor=eyJjIjoi...&updatedSince=2026-07-01T00:00:00ZIdempotency
Write operations are protected by the Idempotency-Key header (any unique value, a UUID is recommended, up to 255 characters). It is required on POST /orders and POST /orders/{id}/invoice, and honoured on PUT and PATCH.
- A repeat with the same key and the same body returns the stored result (the
Idempotency-Replayed: trueheader) with no double effect. - The same key with a different body returns
409 idempotency_conflict. - A parallel duplicate still being processed returns
409 idempotency_in_progresswithRetry-After. - Keys expire after 24 hours.
Code examples
Ready-made snippets in three flavours: curl, JavaScript (fetch, Node 20+) and Python (the requests library). The JS and Python variants assume the BASE (base address) and KEY (the key from secure storage) constants set in the authentication example.
Authentication (Bearer and X-API-Key)
curl
# Bearer (recommended)
curl https://{your-slug}.navyflame.com/api/public/v1/me \
-H "Authorization: Bearer nf_live_your_key"
# The X-API-Key alias (tools without Bearer support)
curl https://{your-slug}.navyflame.com/api/public/v1/me \
-H "X-API-Key: nf_live_your_key"JavaScript
const BASE = "https://my-shop.navyflame.com/api/public/v1";
const KEY = process.env.NAVYFLAME_API_KEY;
const res = await fetch(BASE + "/me", {
headers: { Authorization: "Bearer " + KEY },
});
if (!res.ok) throw new Error("HTTP " + res.status);
const me = await res.json();
console.log(me.plan, me.scopes);Python
import os, requests
BASE = "https://my-shop.navyflame.com/api/public/v1"
KEY = os.environ["NAVYFLAME_API_KEY"]
r = requests.get(BASE + "/me", headers={"Authorization": "Bearer " + KEY})
r.raise_for_status()
me = r.json()
print(me["plan"], me["scopes"])Cursor pagination (a loop plus deduplication)
curl
curl "https://{your-slug}.navyflame.com/api/public/v1/orders?limit=100&updatedSince=2026-07-01T00:00:00Z" \
-H "Authorization: Bearer nf_live_your_key"
# the response carries pagination.nextCursor -> pass it as &cursor=... on the next callJavaScript
async function* paginate(path) {
let cursor = null;
do {
const url = new URL(BASE + path);
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, {
headers: { Authorization: "Bearer " + KEY },
});
if (res.status === 429) {
const wait = Number(res.headers.get("Retry-After") || 1);
await new Promise((r) => setTimeout(r, wait * 1000));
continue;
}
if (!res.ok) throw new Error("HTTP " + res.status);
const page = await res.json();
for (const row of page.data) yield row;
cursor = page.pagination.nextCursor;
} while (cursor);
}
const seen = new Set();
for await (const order of paginate("/orders?updatedSince=2026-07-01T00:00:00Z")) {
if (seen.has(order.id)) continue; // deduplicate by id
seen.add(order.id);
// ... process the order
}Python
import time
def paginate(path, params=None):
params = dict(params or {})
params["limit"] = 100
while True:
r = requests.get(BASE + path, params=params,
headers={"Authorization": "Bearer " + KEY})
if r.status_code == 429:
time.sleep(int(r.headers.get("Retry-After", "1")))
continue
r.raise_for_status()
page = r.json()
for row in page["data"]:
yield row
cursor = page["pagination"]["nextCursor"]
if not cursor:
break
params["cursor"] = cursor
seen = set()
for order in paginate("/orders", {"updatedSince": "2026-07-01T00:00:00Z"}):
if order["id"] in seen: # deduplicate by id
continue
seen.add(order["id"])
# ... process the orderAn idempotent write (Idempotency-Key plus backoff)
curl
curl -X POST "https://{your-slug}.navyflame.com/api/public/v1/orders" \
-H "Authorization: Bearer nf_live_your_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3f1c9a20-7b2e-4c6d-9f10-000000000001" \
-d '{"customer":{"name":"Jan Kowalski"},"lineItems":[{"title":"Kubek 300 ml","sku":"KUB-300-BIA","quantity":2,"unitPrice":"39.99","taxRate":"23"}],"currency":"PLN"}'JavaScript
import { randomUUID } from "node:crypto";
async function createOrder(body) {
const key = randomUUID(); // the same key on every retry
for (let attempt = 0; attempt < 5; attempt++) {
const res = await fetch(BASE + "/orders", {
method: "POST",
headers: {
Authorization: "Bearer " + KEY,
"Content-Type": "application/json",
"Idempotency-Key": key,
},
body: JSON.stringify(body),
});
if (res.status === 429 || res.status === 502 || res.status === 503) {
const wait = Number(res.headers.get("Retry-After") || 2 ** attempt);
await new Promise((r) => setTimeout(r, wait * 1000));
continue; // transient - retry with the same key
}
if (res.status === 409) {
const problem = await res.json();
if (problem.code === "idempotency_in_progress") {
await new Promise((r) => setTimeout(r, 1000));
continue;
}
throw new Error("idempotency conflict: " + problem.code); // a different body
}
if (!res.ok) throw new Error("HTTP " + res.status);
return res.json(); // 201 or replayed (Idempotency-Replayed: true)
}
throw new Error("wyczerpano proby ponowienia");
}Python
import time
from uuid import uuid4
def create_order(body):
key = str(uuid4()) # the same key on every retry
for attempt in range(5):
r = requests.post(BASE + "/orders", json=body, headers={
"Authorization": "Bearer " + KEY,
"Idempotency-Key": key,
})
if r.status_code in (429, 502, 503):
time.sleep(int(r.headers.get("Retry-After", 2 ** attempt)))
continue # transient - retry with the same key
if r.status_code == 409:
code = r.json().get("code")
if code == "idempotency_in_progress":
time.sleep(int(r.headers.get("Retry-After", "1")))
continue
raise RuntimeError("idempotency conflict: " + str(code))
r.raise_for_status()
return r.json() # 201 or replayed
raise RuntimeError("wyczerpano proby ponowienia")Webhook signature verification (JavaScript and Python) is in the Outgoing webhooks section.
Error format
We return errors following RFC 9457 (application/problem+json). Each carries a stable code and a requestId for correlating with support.
{
"type": "https://navyflame.com/dokumentacja/api/bledy#validation_failed",
"title": "Validation failed",
"status": 422,
"detail": "Invalid order input.",
"code": "validation_failed",
"requestId": "req_abc123",
"errors": [{ "field": "lineItems[0].unitPrice", "message": "Kwota >= 0 (jako string)." }]
}| Code | HTTP | When | What to do |
|---|---|---|---|
| unauthorized | 401 | A missing, invalid, expired or revoked API key. | Check the Authorization / X-API-Key header. If the key was revoked or has expired, create a new one in the panel. |
| missing_scope | 403 | The key lacks a required scope (for example orders:write), including cross-scope cases where an operation needs two. | Grant the missing scope by rotating the key with the full set of scopes. An existing key's scopes cannot be changed in place. |
| plan_limit | 403 | The plan does not include the API, the plan limit is exhausted, or the subscription has expired or been cancelled. | Renew or upgrade the plan. The API returns automatically after activation (the limit propagates within about 5 minutes). |
| not_supported | 403 | The operation needs a carrier or marketplace connector that is not connected (capability gate; never a 500). | Connect and enable the right connector in the panel (Integrations). Check availability in /post-sale/capabilities or /connectors. |
| not_found | 404 | The resource does not exist in your account (or belongs to another tenant, which is indistinguishable, anti-IDOR). | Verify the identifier. Someone else's identifier always looks non-existent. |
| conflict | 409 | A resource state conflict: the offer is already published, a duplicate SKU, barcode or name, no stock, or a document not in Draft. | Resolve the conflict: use sync instead of publish; change the SKU or name; check the resource's status. |
| idempotency_conflict | 409 | The same Idempotency-Key used with a different request body. | Use a new Idempotency-Key for a new body. The same key must carry an identical body. |
| idempotency_in_progress | 409 | A request with this key is being processed right now (a parallel duplicate). | Wait as the Retry-After header says and repeat with the same key. |
| payload_too_large | 413 | The request body exceeds 2 MB. | Reduce the body (for example smaller batches in bulk operations). |
| unsupported_media_type | 415 | A write without the Content-Type: application/json header. | Set Content-Type: application/json on write operations. |
| validation_failed | 422 | Bad input: a wrong value, a forbidden status transition, a bad cursor, a naive datetime with no time zone. | Correct the data following the errors field in the response (field plus message). |
| rate_limited | 429 | A limit was exceeded (the plan's per-account limit, per-IP, in-flight, or the dedicated e-mail sending limit). | Slow down, respect Retry-After and apply exponential backoff. Check the X-RateLimit-* headers. |
| internal | 500 | An error on the NavyFlame side. | Retry with backoff. If it keeps happening, report the requestId to support. |
| bad_gateway | 502 | An error at an external service (a carrier, Allegro or another marketplace) - treat it as transient. | Retry with backoff. On a write, retry with the same Idempotency-Key (the channel will deduplicate). See the notes on publishing and refunds. |
| service_unavailable | 503 | A restart, a deployment, or a configuration not ready yet (continuous deployment). | Retry with backoff, this is a transient state. |
The 500 (internal) and 503 (service_unavailable) codes can occur on any operation and are not declared per endpoint. Deployments are continuous - a client should treat occasional 502/503 as transient (retry with backoff) and protect writes with an Idempotency-Key header. The type field in problem+json points at this section's anchor (for example #validation_failed).
Outgoing webhooks
You subscribe to an event and give an HTTPS address, and we send a signed notification there. The v1 events are order.synced, order.status_changed, invoice.issued, shipment.status_updated and catalog.updated. Every delivery carries the X-NavyFlame-Event, X-NavyFlame-Delivery-Id and X-NavyFlame-Signature headers. Success is a 2xx response within 5 seconds; on failure we retry with a growing interval for about 24 hours, and you can see the delivery history in the panel.
The shape of the payload (the common envelope):
{
"id": "evt_9f4c2f6a-...",
"type": "order.synced",
"apiVersion": "v1",
"occurredAt": "2026-07-07T12:00:00Z",
"tenant": "your-slug",
"origin": { "channel": "api", "apiKeyPrefix": "nf_live_abc123" },
"data": { "order": { }, "isNew": true }
}The origin.channel field (api | panel | sync | automation) lets you break an echo loop - if you made the change yourself with an API key, you will recognise it by apiKeyPrefix and skip the event. The receiver deduplicates by the event id and by the X-NavyFlame-Delivery-Id header (at-least-once delivery, with no ordering guarantee).
The event catalogue (v1)
A closed list of v1 events - new types are an additive change, so a receiver must ignore unknown fields and types. Every event carries the common envelope (above); the type-specific content is in the data field:
order.synced - After an order is saved, only on a real change of data (a new order or a status transition). The isNew field distinguishes new from updated.
{
"id": "evt_9f4c2f6a-1b2c-4d3e-8a9b-000000000001",
"type": "order.synced",
"apiVersion": "v1",
"occurredAt": "2026-07-09T12:00:00Z",
"tenant": "my-shop",
"origin": { "channel": "sync" },
"data": {
"order": { "id": "0e6b7c...", "orderNumber": "2026/07/000123",
"status": "Processing", "totalPrice": "129.99", "currency": "PLN" },
"isNew": false
}
}order.status_changed - After a confirmed save of a real status change, whatever the source. Setting the same status again, or a rejected transition, sends no event. The order field holds the state after the save, previousStatus the status before it, and origin points at the source of the change.
{
"id": "evt_2b3c4d5e-6f70-4a8b-9c0d-000000000005",
"type": "order.status_changed",
"apiVersion": "v1",
"occurredAt": "2026-08-30T10:30:00Z",
"tenant": "my-shop",
"origin": { "channel": "api", "apiKeyPrefix": "nf_live_abc123" },
"data": {
"order": { "id": "0e6b7c...", "orderNumber": "2026/08/000321",
"status": "Processing", "totalPrice": "129.99", "currency": "PLN" },
"previousStatus": "New"
}
}invoice.issued - After an invoice is issued in the tenant's accounting system.
{
"id": "evt_1a2b3c4d-5e6f-4a7b-8c9d-000000000002",
"type": "invoice.issued",
"apiVersion": "v1",
"occurredAt": "2026-07-09T12:03:00Z",
"tenant": "my-shop",
"origin": { "channel": "automation" },
"data": {
"invoice": { "id": "7a1c9e...", "number": "FV/2026/07/45",
"status": "Issued", "grossAmount": "129.99", "currency": "PLN" }
}
}shipment.status_updated - After a shipment's status changes at the carrier. The previousStatus field carries the earlier status (or null).
{
"id": "evt_2b3c4d5e-6f7a-4b8c-9d0e-000000000003",
"type": "shipment.status_updated",
"apiVersion": "v1",
"occurredAt": "2026-07-09T12:10:00Z",
"tenant": "my-shop",
"origin": { "channel": "sync" },
"data": {
"shipment": { "id": "b2d9f1...", "courierProvider": "inpost",
"status": "InTransit", "trackingNumber": "6800000000001" },
"previousStatus": "PickedUp"
}
}catalog.updated - After a product or a stock level changes. Single changes come per item; explicitly bulk operations (a wholesaler import, a bulk call) come as one aggregate event (bulk: true).
{
"id": "evt_3c4d5e6f-7a8b-4c9d-0e1f-000000000004",
"type": "catalog.updated",
"apiVersion": "v1",
"occurredAt": "2026-07-09T12:15:00Z",
"tenant": "my-shop",
"origin": { "channel": "api", "apiKeyPrefix": "nf_live_abc123" },
"data": {
"warehouseItemId": "c4e1a2...", "sku": "KUB-300-BIA",
"changeType": "updated", "stockQuantity": 42
}
}
// The bulk variant (data):
// { "bulk": true, "source": "wholesale-import", "itemsChanged": 128 }Signature verification
The X-NavyFlame-Signature header has the form t=<unix>,v1=<hex>, where v1 = HMAC-SHA256(secret, "<t>." + raw_body). The header may carry several v1= pairs (a secret rotation window - the old and the new secret sign in parallel for 24 h) - accept any that matches. Check that the timestamp agrees (a tolerance of 300 s) to protect yourself against a replay.
JavaScript (Node)
import crypto from "node:crypto";
function verify(signatureHeader, rawBody, secret) {
const parts = Object.create(null);
const sigs = [];
for (const p of String(signatureHeader).split(",")) {
const [k, v] = p.split("=");
if (k === "t") parts.t = v;
if (k === "v1" && v) sigs.push(v);
}
if (!parts.t || sigs.length === 0) return false;
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const computed = crypto
.createHmac("sha256", secret)
.update(parts.t + "." + rawBody)
.digest("hex");
return sigs.some(
(s) =>
s.length === computed.length &&
crypto.timingSafeEqual(
Buffer.from(computed, "hex"),
Buffer.from(s, "hex"),
),
);
}Python
import hashlib, hmac, time
def verify(signature_header, raw_body, secret):
t = None
sigs = []
for part in signature_header.split(","):
k, _, v = part.partition("=")
if k == "t":
t = v
elif k == "v1" and v:
sigs.append(v)
if not t or not sigs:
return False
if abs(time.time() - int(t)) > 300: # replay protection
return False
computed = hmac.new(
secret.encode(), (t + "." + raw_body).encode(), hashlib.sha256
).hexdigest()
return any(hmac.compare_digest(computed, s) for s in sigs)Verify the signature against the raw request body (the bytes before JSON parsing) - serialising again changes the bytes and the signature will not match. raw_body / rawBody are exactly the bytes received in the POST body, before JSON decoding.
A recipe for Zapier and Make
- In Zapier, create a Zap with the Webhooks by Zapier - Catch Hook trigger (in Make: the Custom Webhook module). Copy the address it generates.
- In the NavyFlame panel (Account - API - Webhooks) add a subscription: choose the event and paste the address as the target. You see the signing secret once, so save it.
- Use the Send test button in the panel to confirm your scenario receives the payload.
- (Recommended) Add a signature verification step using the function above before you trust the data.
GDPR
Webhook payloads carry buyers' personal data. When you configure a subscription (choosing the destination address, for example Zapier, usually a transfer outside the EEA) you act as the controller of that data, and the subscription is your documented processing instruction. Propagating a buyer's deletion or anonymisation into systems fed by the API and webhooks is on your side.
Guides
Quick start (from a key to the first call)
- In the panel (Account - API) create a key with the scopes you need. You see the key only once, so save it in a secure secret store.
- Confirm the connection:
GET /me(needs no scope). It returns the plan, the limits and the key's scopes. - Make the first call against the resource you want, for example
GET /orders?limit=25.
Pull synchronisation (updatedSince plus an overlap window plus dedupe)
- Poll with
updatedSince = last_sync - overlap_window(5 minutes, say). The semantics are inclusive (>=), with millisecond precision. - Page through by cursor until
pagination.nextCursorisnull. - Deduplicate by
id->=alone does not protect you from a record whose transaction committed after your read (hence the overlap window). - Save the highest
updatedAtfrom the page as the starting point for the next run. - After an account is restored from a backup, do a full resync - do not rely on
updatedSince(and create new keys).
Zapier and Make (a webhook into an action)
- In Zapier, create the Webhooks by Zapier - Catch Hook trigger (in Make: Custom Webhook) and copy the address it generates.
- In the panel (Account - API - Webhooks) add a subscription: choose the event, paste the address as the target, save the secret (shown once).
- Click Send test in the panel to confirm the payload arrives.
- Add a signature verification step (see the Webhooks section) before you trust the data.
- For calls back into the API, use the HTTP / Custom Request module with an
Authorization: Bearerheader.
Rotating a key and a webhook secret (a 24 h window)
- API key: rotating in the panel returns a new token once; the old one keeps working for 24 h (no downtime) - swap it in your integration within that window. A revoke takes effect immediately.
- Webhook secret: the rotation has an overlap window - the old and the new secret sign in parallel for 24 h. The signature header can carry several
v1=pairs, so accepting any that matches makes rotation a non-breaking change. - After a restore from a backup the API keys are revoked automatically - create new ones and do a full resync.
Handling limits (Retry-After plus backoff)
- Read the plan limit from
GET /me(rateLimitPerMin) orGET /usage. Limits are aggregated per account, not per key. - Every response carries
X-RateLimit-Limit,X-RateLimit-RemainingandX-RateLimit-Reset- steer your pace as you go. - On a
429, respectRetry-After(seconds) and apply exponential backoff. Keep concurrency below the in-flight limit (20 parallel requests per account). - Enterprise has the per-minute limit switched off; the per-IP flood guard and the concurrency cap always apply (they protect the infrastructure, they are not a plan feature).
Versioning and changes
The version is in the path (/api/public/v1). Additive changes (new fields, new endpoints, new event types) do not break compatibility - your client must ignore unknown fields. We announce breaking changes in advance: a changelog entry, an e-mail to the owners of active keys, and the Deprecation and Sunset headers.
Changelog
The v1 version history (the version number is the next phase of the build-out). Every change is additive - we have removed nothing and changed the meaning of no endpoint or field. The current OpenAPI specification version: v1.10.0.
| Version | Phase | What was added (additively) |
|---|---|---|
| v1.0 | GA | The API core: /me, orders (read, write and invoicing), stock (read plus a stock PATCH), invoices (read), shipments (read). Webhooks: order.synced, invoice.issued, shipment.status_updated, catalog.updated. Keyset pagination, idempotency, RFC 9457 errors. |
| v1.1 | Phase A | Read-only analytics: sales, product and operations reports (plus CSV/XLSX export), AI-Ops, monitoring, usage, and order statistics and status history. |
| v1.2 | Phase B | Read-only personal data: customers, e-mail templates and logs, notifications and preferences, alert rules, post-sale handling. |
| v1.3 | Phase C | Catalogue writes: products (CRUD), warehouses and locations, reservations, warehouse documents, bundles, templates (descriptions and products) and AI content (billable). |
| v1.4 | Phase D1 | Shipping configuration writes: rules, zones with rates, delivery options, per-product overrides and the cost calculator. |
| v1.5 | Phase D2 | Shipment lifecycle and carrier: creation (single and bulk), buying a label (billable), cancellation, tracking, push to Allegro, and supporting carrier reads (label PDF, points, services, times). |
| v1.6 | Phase E1 | Connector discovery (no secrets) plus local reads of marketplace offers and category mappings. |
| v1.7 | Phase E2 | Live, chargeable marketplace reads (products, categories, GPSR) plus writing category mappings (and cache synchronisation). |
| v1.8 | Phase E3 | Publishing, synchronising, pulling, importing and per-channel pricing of real marketplace offers (billable). |
| v1.9 | Phase F | Communication and post-sale writes: notifications, alerts, e-mail (billable sending), reprocessing the error queue, and post-sale handling and refunds (the isolated postsale:refunds:write scope). |
| v1.10 | Webhooks | A new order.status_changed event after a confirmed real change of an order's status, with previousStatus and origin. No event for a no-op or a rejected transition. |
Versioning is in the path: a breaking change would go to /v2, with v1 supported alongside it for at least 6 months from the announcement (a changelog entry, an e-mail to the owners of active keys, and the Deprecation and Sunset headers).
Good practice
- Test the connection against
GET /mebefore your first integration. - Always attach an
Idempotency-Keyto write operations and retry transient errors with backoff. - After an account is restored from a backup, do a full resynchronisation (do not rely on
updatedSince) and create new keys - the old ones are invalidated by the restore. - For pull synchronisation, use
updatedSincewith an overlap window and deduplicate byid.
Frequently asked questions
Sign in to the panel, go to Account - API and create a key with the scopes you need. You see the key only once, so save it somewhere safe. Make your first test against GET /me, to confirm the connection and see your plan and limits.
An API key is for incoming calls: your system asks NavyFlame for data or carries out an operation. A webhook is an outgoing notification: NavyFlame sends you a signal about an event (a new order, say) as soon as it happens.
To receive webhooks, use the Webhooks module (Zapier) or Custom Webhook (Make) and give the address as the subscription target in the panel. For API calls, use the HTTP / Custom Request module with an Authorization: Bearer header. Signature verification is described in the Webhooks section.
Not if you use the Idempotency-Key header. Repeating a request with the same key returns the same result with no double effect. The header is required when creating orders and triggering invoicing.
The API returns 403 with the plan_limit code until it is renewed. After an account is restored from a backup, API keys are invalidated automatically (create new ones) and we recommend a full resynchronisation rather than relying on updatedSince.
Ready to connect your own system?
Create an account, make an API key in the panel and start with GET /me.
Create an account