# Acenda API Public developer documentation for catalog, orders, inventory, and fulfillment. Downloads relative to this documentation directory: - [OpenAPI](./openapi.json) - [Postman](./postman.json) - [Full text](./llms-full.txt) ## Authentication Use OAuth 2.0 client_credentials with client_id, client_secret and scope=openid. Token URL: https://login.acenda.io/auth/realms/acenda/protocol/openid-connect/token Send Authorization: Bearer and X-Astur-Organization: . An API-key profile limits which permissions are granted. The server resolves those permissions; clients do not request a separate UMA token. API base URL: https://api.acenda.io/v1. ## Request conventions List parameters: limit (1–1000, default 100), page (starts at 1), sort (comma-separated field:direction entries), and query (a URL-encoded JSON object filtering supported top-level record fields). Sort direction 1 is ascending and -1 is descending. Entries apply from left to right: updated_at:-1,id:1 sorts newest updates first, then lowest ID when timestamps are equal. Add a unique ID as the final tie-breaker and keep query, sort, and limit unchanged across pages. Stop pagination on an empty result array or null result. A response wraps data in result with status, num_results, errors and execution_time. Do not assume num_results equals the number of returned records. Use finite timeouts and bounded retries with backoff for reads. Do not blindly retry creates: a timeout does not prove a failed write. Reconcile order writes using external_order_id + sales_channel_id and fulfillment writes using your own reference. A reference is not a universal idempotency key. Never put credentials in a browser app or share populated Postman environments. ## Errors 400: invalid request; inspect errors. 401: missing, invalid or expired token. 403: permission or organization access denied. 404: record not found (some inventory reads return 200 with result:null). 412: a request precondition failed; inspect the response and reconcile state. 429: respect Retry-After if supplied by the gateway. 5xx: transient or server failure; reconcile writes before retrying. Some infrastructure errors are not JSON. ## Quickstart A client ID, a client secret, and your organization slug. That’s all you need to start reading data from Acenda. ### Before you start# Ask your organization administrator for an API credential with product:read access and your organization slug. The examples use the public Acenda API. Keep secrets on your server: Load credentials from environment variables or your secret manager. Do not put them in client-side JavaScript, source control, or screenshots. Set ACENDA_CLIENT_ID, ACENDA_CLIENT_SECRET, and ACENDA_ORGANIZATION in your environment. Use the organization slug alone, such as example-store; do not add an org_ prefix. ### 1. Get an access token# Exchange your credentials for a bearer token. No user sign-in is needed for this server-to-server flow. Token request · cURL ``` curl --fail-with-body --silent --show-error \ --request POST 'https://login.acenda.io/auth/realms/acenda/protocol/openid-connect/token' \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'grant_type=client_credentials' \ --data-urlencode "client_id=$ACENDA_CLIENT_ID" \ --data-urlencode "client_secret=$ACENDA_CLIENT_SECRET" \ --data-urlencode 'scope=openid' ``` Read access_token from the JSON response and set it as ACENDA_ACCESS_TOKEN in your current process. Use expires_in from the response to plan token renewal. Illustrative token response ``` { "access_token": "", "token_type": "Bearer", "expires_in": 300 } ``` ### 2. Make a request# Request a small page of catalog items. Both headers are required: the token identifies your integration and the organization selects its data. Your first API request · cURL ``` curl --fail-with-body --silent --show-error \ 'https://api.acenda.io/v1/catalog?limit=5&page=1' \ -H "Authorization: Bearer $ACENDA_ACCESS_TOKEN" \ -H "X-Astur-Organization: $ACENDA_ORGANIZATION" ``` This website displays examples and does not send API requests. ### 3. Read the response# API data is inside result. List operations return an array; some empty results are null. An organization with no catalog data will not return the fictional item below. Illustrative response · 200 OK ``` { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": [ { "id": 101, "sku": "TRAIL-MUG", "name": "Trail mug", "group": "product" } ] } ``` Next, choose a resource in the API reference. Every endpoint includes its required permission, parameters, and copyable request examples. Continue readingUnderstand authentication & permissions ## Authentication Use OAuth 2.0 client credentials for server-to-server access. Each API credential is limited to the permissions assigned by your organization. ### Your credentials# An administrator creates an API credential and assigns its API-key profile. Keep the client ID, client secret, and organization slug together for your organization. | Endpoint | URL | API | https://api.acenda.io/v1 | Token exchange | https://login.acenda.io/auth/realms/acenda/protocol/openid-connect/token ### Token exchange# Send a form-encoded request to the token endpoint. Use scope=openid for the token exchange; business permissions such as order:read are assigned to the credential, rather than requested in this field. Client credentials · cURL ``` curl --fail-with-body --silent --show-error \ --request POST 'https://login.acenda.io/auth/realms/acenda/protocol/openid-connect/token' \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'grant_type=client_credentials' \ --data-urlencode "client_id=$ACENDA_CLIENT_ID" \ --data-urlencode "client_secret=$ACENDA_CLIENT_SECRET" \ --data-urlencode 'scope=openid' ``` ### Request headers# | Header | Value | Purpose | Authorization | Bearer | Identifies the integration. | X-Astur-Organization | example-store | Selects the organization whose records you access. | Content-Type | application/json | Required when sending a JSON body. Send the access token returned by the token exchange. Acenda performs its permission-token exchange on the server. Integrations do not need to acquire a separate UMA token. ### Permissions are specific to your API key# For example, product:read allows the documented catalog reads, while order:create allows order creation and acknowledgment. Your credential may have a narrower profile than the entire API documented here. An HTTP 403 can mean the key lacks the action or cannot access the selected organization. Confirm both with your administrator. Refreshing a valid token does not add permissions. Authentication and authorization are separate: A successful token exchange confirms the credential. It does not prove that every documented resource is available to that credential. ### Refresh before expiry# Cache the token in your server process and renew it before its reported expiry. For client credentials, obtain a new token with the same grant. Do not exchange credentials before every API call. On a 401, renew once and retry a safe read. If it still fails, stop and check the credential and organization. Keep access tokens out of logs. Continue readingFilter, sort, and paginate results ## Filtering & pagination List endpoints share a small set of query parameters. Filter on the server, use bounded pages, and choose a stable sort order. ### Query parameters# | Parameter | Format | Example | limit | Maximum records per page: 1–1000; defaults to 100. | limit=20 | page | Page number: 1 is the first page, 2 is the next. | page=1 | sort | Comma-separated field:direction entries; 1 is ascending and -1 is descending. | sort=updated_at:-1,id:1 | query | A JSON object filtering supported top-level fields on the selected resource; URL-encode the complete value. | {"sku":"TRAIL-MUG"} ### Sort order# Write each sort entry as field:direction. Use 1 for ascending order (smallest to largest, oldest to newest) and -1 for descending order (largest to smallest, newest to oldest). For example, id:1 returns lower IDs first and id:-1 returns higher IDs first. Separate multiple entries with commas. They are applied from left to right: the first field determines the primary order, and each later field breaks ties in the preceding fields. Newest updates first, with a stable ID tie-breaker ``` sort=updated_at:-1,id:1 ``` This sorts by updated_at descending, then by id ascending when timestamps are equal. Add a unique ID as the final tie-breaker when paging, and keep the same sort for every page. A stable sort does not freeze records while they are being changed. ### JSON filters# The query parameter is a JSON object. Its keys are supported top-level fields on the selected resource, such as sku on catalog items or status on orders. A direct value means an exact match. Match the field's JSON type: quote text such as "pending", leave numeric IDs such as 30 unquoted, and use true or false for booleans, such as a warehouse's active field. Find a catalog item by SKU ``` curl --fail-with-body --silent --show-error \ 'https://api.acenda.io/v1/catalog' \ -H "Authorization: Bearer $ACENDA_ACCESS_TOKEN" \ -H "X-Astur-Organization: $ACENDA_ORGANIZATION" \ --get \ --data-urlencode 'query={"sku":"TRAIL-MUG"}' \ --data-urlencode 'sort=updated_at:-1,id:1' \ --data-urlencode 'limit=20' ``` Use --data-urlencode or your HTTP library's query-parameter encoder for the complete JSON value. Put filters inside query; a separate URL parameter such as status=pending does not automatically filter records. Combine exact matches Multiple fields in one object mean AND: every condition must match. This example selects pending orders from sales channel 30. Pending orders from one sales channel ``` { "status": "pending", "sales_channel_id": 30 } ``` Comparison and logical operators | Operator | Meaning | $eq, $ne | Equal to, or different from, a value. | $gt, $gte, $lt, $lte | Greater than, at least, less than, or at most a value. | $in, $nin | Value is in, or is not in, the supplied array. Example: {"id":{"$in":[10042,10043]}}. | $and, $or | All, or at least one, of an array of filter objects must match. This range includes ID 10000 and excludes ID 11000. The exclusion list removes the two listed IDs. Order IDs in a range, excluding selected IDs ``` { "id": { "$gte": 10000, "$lt": 11000, "$nin": [ 10042, 10043 ] } } ``` Match either condition Use $or when either source reference can identify the order. Each array entry is a complete filter object. Find an order by either source reference ``` { "$or": [ { "external_order_id": "WEB-10042" }, { "purchase_order": "PO-2048" } ] } ``` ### Pagination# Read page 1, then increase the page number for a stable collection. Inspect the actual result array, not just num_results, and stop when it is empty or null. Do not assume every endpoint reports a total count in the same way. Selected resources expose a separate /count endpoint. A stable page of orders ``` curl --fail-with-body --silent --show-error \ 'https://api.acenda.io/v1/order?limit=100&page=1&sort=id:1' \ -H "Authorization: Bearer $ACENDA_ACCESS_TOKEN" \ -H "X-Astur-Organization: $ACENDA_ORGANIZATION" ``` For consuming orders and ship advice, follow the acknowledgment workflow and its pagination rules. ### Incremental sync# For ongoing synchronization, query a bounded time range using a supported timestamp field such as updated_at. Sort with an ID tie-breaker. Persist your checkpoint only after processing succeeds, overlap polling windows, and deduplicate by record ID and update version. Page-based reads are not a snapshot. Records can change while you read, and read replicas can lag a write. Reconcile periodically instead of assuming a single scan proves perfect delivery. Example updated-at filter ``` { "updated_at": { "$gte": "2026-10-01T00:00:00Z", "$lt": "2026-10-02T00:00:00Z" } } ``` ### Conditional single-record reads# For a single-record GET that documents If-None-Match, save the result and the ETag response header from a 200 OK. With cURL, add --include to view response headers. On a later GET to the same URL and query, with the same organization and API identity, send that exact single tag, including its quotes. Keep cached results separate for each of those request contexts. Recheck an order with a saved ETag ``` curl --fail-with-body --silent --show-error \ 'https://api.acenda.io/v1/order/10042' \ -H "Authorization: Bearer $ACENDA_ACCESS_TOKEN" \ -H "X-Astur-Organization: $ACENDA_ORGANIZATION" \ --include \ -H 'If-None-Match: ""' ``` Replace the entire "" placeholder with the quoted ETag copied from the earlier response. A 304 Not Modified has no body: reuse the cached result instead of parsing JSON. A 200 OK supplies the current result and ETag; replace both cached values. The tag validates result, not the entire response envelope. A 304 does not refresh counts or execution metadata. This example is for a server-side client. Order read reference → ### Response envelope# | Field | Meaning | result | Resource object, array, or null. | status | HTTP status represented in the response body. Check HTTP status first. | errors | Error details or null; the keys and value types vary. | num_results | Endpoint-specific metadata. It may differ from the array length. | execution_time | Server execution time in seconds. Continue readingBuild a reliable order consumer ## Errors & retries Use the HTTP status to decide what to do next, then inspect the response. A timeout is an unknown outcome, especially for a write. ### Status codes# | Status | Meaning | What to do | 400 | Invalid request, domain validation, or a duplicate record. | Fix the data or reconcile existing state. Do not blindly retry. | 401 | Missing, invalid, or expired access token. | Renew once; check credentials and the token endpoint if it continues. | 403 | Permission or organization access denied. | Check the organization header and API-key profile. | 404 | Record not found. | Check the record ID and organization. Some inventory reads return 200 with a null result instead. | 412 | A request precondition failed. | Inspect the response and reconcile current state before retrying. | 429 | The gateway is limiting requests. | Respect Retry-After when present and reduce concurrency. | 5xx | A server or upstream error. | Retry reads with bounded backoff. Reconcile writes before retrying. ### Inspect the response safely# Application errors often use the same envelope as successful requests. Error keys are not fixed, so retain a sanitized diagnostic summary without depending on exact message text. Gateways can return a non-JSON error body. Illustrative authorization error ``` { "status": 403, "execution_time": 0.012, "num_results": 0, "errors": { "auth": "Forbidden" }, "result": null } ``` Record the timestamp, method, route template, HTTP status, organization reference, and any returned correlation identifier. Redact credentials, access tokens, customer addresses, and order details. ### Retry deliberately# Use finite timeouts, exponential backoff with jitter, and a retry limit. Retry safe reads on temporary failures. Do not retry 400 or 403 without changing the cause. After a write timeout, first check whether the desired state exists. Order uniqueness uses external_order_id together with sales_channel_id. A fulfillment external_id is useful for reconciliation but is not an enforced idempotency key. An acknowledgment is also a write: If an acknowledgment response is lost, repeating the POST may produce a duplicate error. Query acknowledgment state for the same API identity before retrying. Do not reprocess the order just because the acknowledgment response was lost. ### Common integration pitfalls# - Authentication mismatch: use the documented token endpoint and the credential assigned to your organization. - Wrong identifier: use a ship advice item ID when confirming shipment, not an order item ID or SKU. - Excess shipment quantities: re-read remaining fulfillable quantities and existing fulfillment records before retrying a shipment. - Incomplete shipment data: include at least one fulfillment_items entry. Every supplied tracking_info entry needs a non-empty number; omit an unavailable tracking entry instead of sending an empty object. - Setting calculated fields: write inventory quantity, not on_hand; let fulfillment update shipment status. - Missing required order fields: include both addresses, channel reference, source order IDs, and monetary totals. - Acknowledging twice: retain durable consumer state and check acknowledgment records after ambiguous failures. Continue readingDesign for recovery ## Integration workflows Start with catalog and reference data, then connect order ingestion, warehouse work, and shipment confirmation. 01Discover Catalog, sales channels, warehouses. 02Connect Orders and warehouse inventory. 03Fulfill Ship advice and shipment confirmation. ### Read the catalog once# /catalog brings products, variants, and kits into one API. Use group to distinguish them, and filter by sku when matching your own records. You do not need separate product, variant, and kit list endpoints. Catalog IDs, warehouse IDs, and sales-channel IDs are different identifiers. Discover and store their mappings explicitly. Explore catalog requests → ### Synchronize inventory by SKU and warehouse# Read /warehouse to find your existing warehouse. Use fulfillment providers to look up its provider ID. Read /inventory_detail with a SKU and warehouse filter. Set the absolute quantity using the inventory write endpoints. Do not send increments when the endpoint expects a total. Availability fields such as on_hand are calculated; kit quantities are derived from components and cannot be set manually. Inventory summaries are grouped after pagination: /inventory reads active-warehouse rows, applies pagination, then groups those rows by product. A product can span pages, giving partial totals. Use /inventory_detail for full inventory synchronization and aggregate after reading all pages. Use /pricing_item filtered by product_id to read stored base prices, MSRP, and minimum advertised prices. These are separate from the final order price after channel pricing or promotions. Both bulk inventory and bulk pricing take a JSON array. Every row requires a positive product_id or a non-empty sku. Either is sufficient; if both are supplied, product_id takes precedence. Inventory rows also require warehouse_id. For a batch, use bulk inventory updates. Include the intended allocated total: omission resets it to zero. Cost and receipt/expiration dates are also replaced, so retain metadata you need and coordinate concurrent stock writers. Bulk pricing updates set stored base prices and retain omitted price columns. Inspect both bulk responses for errors and re-read affected records after partial results. Set inventory for a SKU → · Read pricing → ### Create and consume orders# Find the correct sales_channel_id, then create an order using /order. Include all items in order_item. Read /order/{order} to get the full order and its items. The creation example sets create_routings=false so ingestion can be tested before your organization configures routing. With automatic routing enabled and configured, Acenda creates ship advice for warehouse fulfillment. If your integration consumes existing orders, use unacked=true and follow the acknowledgment workflow. Create an order → ### Correct an order# Orders and ship advice are snapshots. Their core details cannot be edited through the supported API. Ship-advice custom-field values remain editable. Routing, acknowledgment, fulfillment, and cancellation update lifecycle state. Before canceling anything, confirm that the source integration can accept a replacement with a new external_order_id. If it must keep the original ID, the order has no ship advice, or the correction needs deletion, contact Acenda first. Cancellation keeps the original order ID: The pair external_order_id + sales_channel_id remains unique after cancellation. A replacement order needs a new external_order_id. When a replacement is appropriate, cancel the remaining unfulfilled units on the existing ship advice, verify the cancellation, then create the replacement order. Quantities already fulfilled cannot be canceled. Choose the replacement ID once, for example WEB-10042-R1. Keep that ID when retrying the same creation request. After a timeout, look up the replacement by its ID and sales channel before trying again. Cancel eligible units → · Create a replacement → ### Update ship-advice custom fields# Use PATCH /ship_advice/{shipadvice} with ship_advice:update to update custom-field values on one ship advice. This operation only changes the fields object; it cannot change addresses, items, quantities, shipping details, or routing. Other top-level properties are ignored. Use values that match the custom-field types configured for your organization. This example assumes an existing boolean field named exported_to_erp: PATCH /ship_advice/5001 · 200 OK ``` { "fields": { "exported_to_erp": true } } ``` Send only the custom fields you want to change. Omitted fields retain their values; null or an empty string removes a stored value. Read the ship advice after saving to verify its current values. Custom-field definitions are separate from the values on a ship advice. Update custom-field values → ### Route and reroute items# Use this workflow only for non-kit order items. Contact Acenda before releasing or reassigning kit items. To move unfulfilled units to another warehouse, release them from the existing routing and create a new assignment for the same order. This does not change the order's addresses, prices, shipping method, or requested dates. Both requests require ship_advice:create. First read the existing ship advice. Choose only units that have not been fulfilled, canceled, or already rerouted. Record the release with POST /ship_advice_item_reroute, using the ship advice item ID: Release one routed unit · 201 Created ``` { "ship_advice_item_id": 6001, "quantity_rerouted": 1 } ``` Re-read the order and ship advice to verify the released quantity. Then call POST /order/10042/ship_advice with the destination warehouse and the order item ID: Create the replacement assignment · 200 OK ``` { "warehouse_id": 8, "ship_advice_item": [ { "order_item_id": 20081, "quantity": 1 } ] } ``` The new ship advice inherits the existing order's shipping details. Read the new assignment before processing it. If either response is lost, reconcile released quantities and existing assignments before retrying; repeating a POST can apply the action twice. Release a routing assignment → · Create a routing assignment → ### Create fulfillment# Query /ship_advice by order_id or warehouse_id. Read its ship_advice_item array, then send a shipment to /ship_advice/{shipadvice}/fulfillment. Use the routed item ID: fulfillment_items[].ship_advice_item_id is the ID from ship_advice_item. Each shipment quantity must fit the remaining fulfillable quantity. A repeated shipment can exceed the remainder and fail. Include tracking data when available. Every supplied tracking entry needs a non-empty number. A shipment confirmation updates fulfillment state. An order acknowledgment only records receipt by the integration; it does not confirm shipping. If the warehouse cannot fulfill some units, create a cancellation at /ship_advice/{shipadvice}/cancellation with the routed item IDs and quantities. Cancel only the remaining units and reconcile after timeouts. A canonical retry for an already fully canceled item may return 200 with result: null; a new record returns 201. Create fulfillment → · Cancel unfulfilled units → ### Read returns and original-order context# Use /return for return records and tracking, or /return_extended for the same records with product, SKU, and original-order details. Both use the Acenda return ID. These read operations do not create returns or issue refunds. The extended list reports the current page length in num_results. For an extended single-record read, check that result.id matches the requested ID: an unknown ID currently returns a default-valued record with HTTP 200. Read returns → · Read extended returns → Continue readingConsume and acknowledge records reliably ## Order acknowledgments Acknowledgment records that your integration received an order or ship advice. It is separate from order creation and shipment confirmation. ### 1. Fetch unacknowledged records# Request ship advice with unacked=true. Use the same API identity for polling, acknowledgment, and recovery checks. Different credentials can represent different consumers. Read the next batch ``` curl --fail-with-body --silent --show-error \ 'https://api.acenda.io/v1/ship_advice?unacked=true&limit=20&page=1&sort=id:1' \ -H "Authorization: Bearer $ACENDA_ACCESS_TOKEN" \ -H "X-Astur-Organization: $ACENDA_ORGANIZATION" ``` For orders, use /order?unacked=true with the same acknowledgment workflow. Fetching records does not reserve them or lock them for your worker. ### 2. Save your work before acknowledging# Persist the ship advice in your system and a processing checkpoint keyed by organization, resource, and Acenda ID. Make that processing safe to repeat. Only acknowledge after the work has been durably accepted. If several workers consume the same credential’s queue, coordinate them in your own system. Otherwise they can fetch and process the same unacknowledged ship advice. ### 3. Acknowledge receipt# Post to /acknowledge/ship_advice/{ref_id}, where ref_id is the Acenda ship advice ID. The optional external_id links to the record in your receiving system. The endpoint requires order:create, even when the resource is ship_advice. Record successful receipt ``` curl --fail-with-body --silent --show-error \ --request POST 'https://api.acenda.io/v1/acknowledge/ship_advice/5001' \ -H "Authorization: Bearer $ACENDA_ACCESS_TOKEN" \ -H "X-Astur-Organization: $ACENDA_ORGANIZATION" \ -H 'Content-Type: application/json' \ --data-raw '{"external_id":"WMS-5001"}' ``` The handler returns 200 OK. Acknowledgment is not an idempotent upsert: submitting the same resource and ID for the same identity can return a duplicate-record error. ### 4. Recover after an ambiguous response# If the acknowledgment times out, do not reprocess the ship advice or repeatedly post the acknowledgment. First query the acknowledgment list by ref_id, using the same token identity and organization. Check whether acknowledgment exists ``` curl --fail-with-body --silent --show-error \ 'https://api.acenda.io/v1/acknowledge/ship_advice' \ -H "Authorization: Bearer $ACENDA_ACCESS_TOKEN" \ -H "X-Astur-Organization: $ACENDA_ORGANIZATION" \ --get \ --data-urlencode 'query={"ref_id":5001}' \ --data-urlencode 'limit=1' ``` If a matching record exists, mark the acknowledgment as complete locally. If it does not, allow for read-replica delay, recheck within a bounded recovery window, then retry once if appropriate. A duplicate on that retry can mean the original write succeeded; verify state again. Treat other errors separately. Keep the two IDs straight: The acknowledgment result has its own id. Its ref_id is the order or ship advice ID. Filter the list by ref_id when recovering a resource acknowledgment. ### 5. Keep polling page 1 while draining# After you acknowledge a batch, those records disappear from the unacked=true result set. Fetch page 1 again. Advancing to page 2 can skip unprocessed records because the earlier records have just been removed from the list. Persist pending acknowledgments separately from processed records so a temporary API failure does not cause repeated business processing. Back off when the queue is empty; avoid a tight polling loop. Acknowledgment endpoint reference → Continue readingHandle errors and retry safely ## Best practices Keep the integration predictable when tokens expire, requests time out, or data changes while you are reading it. ### Protect credentials and limit access# Use a dedicated API credential with the smallest profile that supports the integration. Keep secrets in a server-side secret manager, rotate them through your administrator, and never ship them in a frontend bundle. Begin with read-only requests before sending writes. Examples make real API requests when run; replace the illustrative IDs with your own records. ### Make writes recoverable# - Persist source order IDs and their sales-channel mapping. - Do not assume POST requests, acknowledgments, or external shipment references are universally idempotent. - Reconcile state after a timeout before sending another write. - Coordinate writers for the same record. Send only the fields you intend to change. - Read current state and coordinate changes with other writers. - Keep inventory quantity updates absolute and never write calculated kit inventory. ### Plan for changing data# Use bounded pages and server-side filters. Persist checkpoints only after durable processing, overlap incremental windows, and reconcile on a schedule. Read replicas may lag writes. When consuming orders or ship advice, follow the acknowledgment workflow for polling, pagination, and duplicate handling. Add a polling interval, jitter, and a concurrency limit to avoid retry storms. ### Keep useful, sanitized diagnostics# Retain request timestamps, method, route template, response status, and relevant correlation identifiers when supplied. Record enough context to reproduce the workflow without storing secrets or customer payloads. Use finite timeouts. Honor Retry-After if a gateway provides it. There is no universal rate-limit number promised by this documentation. Continue readingBring the API into your tools ## Tools & downloads Import the same curated API reference into your tools. Downloads are public, versioned with the site, and contain no credentials. OPENAPI 3.0.3 OpenAPI specification Schemas, parameters, permissions, and examples for every documented operation. Download OpenAPI POSTMAN 2.1 Postman collection Grouped requests and a client-credentials token request for the public API. Download Postman PLAIN TEXT Full documentation A portable text reference for AI assistants, search tools, and offline reading. Download text LLMS.TXT Machine-readable index A compact entry point to the public reference and available formats. Download index ### Set up Postman# - Import postman.json. - Create a private environment with base_url, token_url, organization, client_id, and client_secret. - Run Authentication → Get an access token. - Copy access_token into your private environment’s access_token variable. - Run a read request before trying writes. Replace fictional record IDs with your own. Choose environment variables over editing the shared collection. Do not share an environment after filling in secrets. ### Generate a client# The OpenAPI document works with client generators that support OpenAPI 3.0.3. Configure the server URL, bearer token, and X-Astur-Organization header in the generated client. Keep token renewal in your application. The schema describes the curated public surface, not every internal endpoint. Monetary fields are represented as decimal strings. Preserve response envelopes and nullable results when choosing generator settings. Versioned with the documentation: The website, OpenAPI, Postman collection, and code snippets all use the same curated contract. If your integration relies on a behavior outside this reference, confirm it with your Acenda contact. ## List catalog items GET /catalog Permission: product:read Read products, variants, and kits through one catalog endpoint. The group field identifies the item kind. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": [ { "id": 101, "sku": "TRAIL-MUG", "name": "Trail mug", "group": "product", "fields": {}, "updated_at": "2026-10-01T12:00:00Z" } ] } ``` ## Create a catalog item POST /catalog Permission: product:create Create a standalone product with a unique SKU. Use an existing catalog record for variants and kits; advanced composition is outside this guide. Request example: ```json { "sku": "TRAIL-MUG", "name": "Trail mug", "group": "product" } ``` Response example: ```json { "status": 201, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "id": 101, "sku": "TRAIL-MUG", "name": "Trail mug", "group": "product", "fields": {}, "updated_at": "2026-10-01T12:00:00Z" } } ``` ## Get a catalog item GET /catalog/{product} Permission: product:read Retrieve a catalog record using its Acenda ID. Filter the list by sku if you only know the SKU. This single-record GET supports If-None-Match with a previously saved ETag. A 304 response has no body; reuse the cached result. A 200 response supplies the current result and ETag. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "id": 101, "sku": "TRAIL-MUG", "name": "Trail mug", "group": "product", "fields": {}, "updated_at": "2026-10-01T12:00:00Z" } } ``` ## Update a catalog item PATCH /catalog/{product} Permission: product:update Change the documented fields of an existing item. Fields not sent remain on the loaded record. Request example: ```json { "name": "Trail mug \u2014 12 oz" } ``` Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "id": 101, "sku": "TRAIL-MUG", "name": "Trail mug \u2014 12 oz", "group": "product", "fields": {}, "updated_at": "2026-10-01T12:00:00Z" } } ``` ## Count catalog items GET /catalog/count Permission: product:read Count catalog records matching an optional query filter. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "count": 1240 } } ``` ## List orders GET /order Permission: order:read Read orders and their order_item arrays. Use this resource for line items instead of making separate order-item requests. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": [ { "purchase_order": "PO-10042", "external_order_id": "WEB-10042", "sales_channel_id": 7, "create_routings": false, "shipping_information": { "first_name": "Alex", "last_name": "Morgan", "address_1": "123 Example Street", "city": "San Diego", "state": "CA", "postal_code": "92101", "country": "US" }, "billing_information": { "first_name": "Alex", "last_name": "Morgan", "address_1": "123 Example Street", "city": "San Diego", "state": "CA", "postal_code": "92101", "country": "US" }, "order_item": [ { "external_sku": "TRAIL-MUG", "sku": "TRAIL-MUG", "product_id": 101, "line_id": "1", "quantity": 2, "unit_price": "29.00", "id": 20081, "status": "pending", "quantity_fulfilled": 0, "quantity_canceled": 0 } ], "item_tax": "0.00", "ship_tax": "0.00", "shipping": "0.00", "total_item_discount": "0.00", "total_shipping_discount": "0.00", "subtotal": "58.00", "total": "58.00", "id": 10042, "order_number": 10042, "status": "pending" } ] } ``` ## Create an order POST /order Permission: order:create Create an order snapshot using an existing sales channel and catalog items. Order details cannot be updated after creation through the supported API. This example disables automatic routing; an order without ship advice has no cancellation path in this reference. The pair external_order_id + sales_channel_id remains unique after cancellation. Contact Acenda for corrections that require deletion or preserving the original source ID. Request example: ```json { "purchase_order": "PO-10042", "external_order_id": "WEB-10042", "sales_channel_id": 7, "create_routings": false, "shipping_information": { "first_name": "Alex", "last_name": "Morgan", "address_1": "123 Example Street", "city": "San Diego", "state": "CA", "postal_code": "92101", "country": "US" }, "billing_information": { "first_name": "Alex", "last_name": "Morgan", "address_1": "123 Example Street", "city": "San Diego", "state": "CA", "postal_code": "92101", "country": "US" }, "order_item": [ { "external_sku": "TRAIL-MUG", "sku": "TRAIL-MUG", "product_id": 101, "line_id": "1", "quantity": 2, "unit_price": "29.00" } ], "item_tax": "0.00", "ship_tax": "0.00", "shipping": "0.00", "total_item_discount": "0.00", "total_shipping_discount": "0.00", "subtotal": "58.00", "total": "58.00" } ``` Response example: ```json { "status": 201, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "purchase_order": "PO-10042", "external_order_id": "WEB-10042", "sales_channel_id": 7, "create_routings": false, "shipping_information": { "first_name": "Alex", "last_name": "Morgan", "address_1": "123 Example Street", "city": "San Diego", "state": "CA", "postal_code": "92101", "country": "US" }, "billing_information": { "first_name": "Alex", "last_name": "Morgan", "address_1": "123 Example Street", "city": "San Diego", "state": "CA", "postal_code": "92101", "country": "US" }, "order_item": [ { "external_sku": "TRAIL-MUG", "sku": "TRAIL-MUG", "product_id": 101, "line_id": "1", "quantity": 2, "unit_price": "29.00", "id": 20081, "status": "pending", "quantity_fulfilled": 0, "quantity_canceled": 0 } ], "item_tax": "0.00", "ship_tax": "0.00", "shipping": "0.00", "total_item_discount": "0.00", "total_shipping_discount": "0.00", "subtotal": "58.00", "total": "58.00", "id": 10042, "order_number": 10042, "status": "pending" } } ``` ## Get an order GET /order/{order} Permission: order:read Retrieve an order snapshot, including its order_item records. Order details are immutable through the supported API; fulfillment and cancellation still update lifecycle state. Use ship advice for warehouse work generated by routing. This single-record GET supports If-None-Match with a previously saved ETag. A 304 response has no body; reuse the cached result. A 200 response supplies the current result and ETag. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "purchase_order": "PO-10042", "external_order_id": "WEB-10042", "sales_channel_id": 7, "create_routings": false, "shipping_information": { "first_name": "Alex", "last_name": "Morgan", "address_1": "123 Example Street", "city": "San Diego", "state": "CA", "postal_code": "92101", "country": "US" }, "billing_information": { "first_name": "Alex", "last_name": "Morgan", "address_1": "123 Example Street", "city": "San Diego", "state": "CA", "postal_code": "92101", "country": "US" }, "order_item": [ { "external_sku": "TRAIL-MUG", "sku": "TRAIL-MUG", "product_id": 101, "line_id": "1", "quantity": 2, "unit_price": "29.00", "id": 20081, "status": "pending", "quantity_fulfilled": 0, "quantity_canceled": 0 } ], "item_tax": "0.00", "ship_tax": "0.00", "shipping": "0.00", "total_item_discount": "0.00", "total_shipping_discount": "0.00", "subtotal": "58.00", "total": "58.00", "id": 10042, "order_number": 10042, "status": "pending" } } ``` ## Create a routing assignment POST /order/{order}/ship_advice Permission: ship_advice:create Use this reassignment workflow only for non-kit order items. Contact Acenda before releasing or reassigning kit items. Assign unrouted units from an existing order to a warehouse. Use order_item_id from that order. The new ship advice inherits order prices, addresses, shipping method, and requested dates; this request does not change those details. After releasing an old assignment, re-read quantities before creating its replacement. Creation is not generally idempotent: reconcile existing assignments after an ambiguous response before retrying. Request example: ```json { "warehouse_id": 8, "ship_advice_item": [ { "order_item_id": 20081, "quantity": 1 } ] } ``` Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "id": 5002, "order_id": 10042, "warehouse_id": 8, "fulfillment_provider_id": 6, "status": "pending", "ship_advice_item": [ { "id": 6002, "order_item_id": 20081, "sku": "TRAIL-MUG", "quantity": 1, "quantity_fulfilled": 0, "quantity_canceled": 0, "quantity_rerouted": null, "status": "pending" } ], "delivery_information": { "first_name": "Alex", "last_name": "Morgan", "address_1": "123 Example Street", "city": "San Diego", "state": "CA", "postal_code": "92101", "country": "US" } } } ``` ## Count orders GET /order/count Permission: order:read Count orders matching an optional query filter. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "count": 42 } } ``` ## List inventory GET /inventory_detail Permission: inventory:read Read stock by product and warehouse. Filter by sku or warehouse_id. on_hand is calculated by the read view. A missing inventory record can return HTTP 200 with result: null and num_results: 0. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": [ { "id": 301, "product_id": 101, "warehouse_id": 20, "sku": "TRAIL-MUG", "quantity": 50, "allocated": 2, "not_available": 0, "on_hand": 48, "tracking": "basic" } ] } ``` ## Set inventory for a SKU POST /inventory_detail Permission: inventory:create Create inventory for an existing SKU and warehouse. The stored product/warehouse pair is unique; saving can update an existing pair. Send an absolute quantity, not a stock adjustment. Kit inventory is calculated and cannot be manually set. Request example: ```json { "sku": "TRAIL-MUG", "warehouse_id": 20, "quantity": 50, "tracking": "basic" } ``` Response example: ```json { "status": 201, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "id": 301, "product_id": 101, "warehouse_id": 20, "sku": "TRAIL-MUG", "quantity": 50, "allocated": 2, "not_available": 0, "tracking": "basic" } } ``` ## Get an inventory record GET /inventory_detail/{inventorydetail} Permission: inventory:read Look up one inventory record by its Acenda ID. A missing inventory record can return HTTP 200 with result: null and num_results: 0. This single-record GET supports If-None-Match with a previously saved ETag. A 304 response has no body; reuse the cached result. A 200 response supplies the current result and ETag. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "id": 301, "product_id": 101, "warehouse_id": 20, "sku": "TRAIL-MUG", "quantity": 50, "allocated": 2, "not_available": 0, "on_hand": 48, "tracking": "basic" } } ``` ## Update inventory quantity PATCH /inventory_detail/{inventorydetail} Permission: inventory:update Set the absolute stock quantity on an existing inventory record. Only send the fields you intend to change. Kit inventory is calculated and cannot be manually set. Request example: ```json { "quantity": 60 } ``` Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "id": 301, "product_id": 101, "warehouse_id": 20, "sku": "TRAIL-MUG", "quantity": 60, "allocated": 2, "not_available": 0, "tracking": "basic" } } ``` ## Count inventory records GET /inventory_detail/count Permission: inventory:read Count stored inventory records matching an optional query filter. A missing inventory record can return HTTP 200 with result: null and num_results: 0. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "count": 240 } } ``` ## List warehouses GET /warehouse Permission: warehouse:read Find existing warehouse IDs before reading or setting inventory. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": [ { "id": 20, "name": "West coast warehouse", "active": true, "fulfillment_provider_id": 6, "type": "standard" } ] } ``` ## Get a warehouse GET /warehouse/{warehouse} Permission: warehouse:read Read a warehouse and its fulfillment-provider ID. This single-record GET supports If-None-Match with a previously saved ETag. A 304 response has no body; reuse the cached result. A 200 response supplies the current result and ETag. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "id": 20, "name": "West coast warehouse", "active": true, "fulfillment_provider_id": 6, "type": "standard" } } ``` ## List sales channels GET /sales_channel Permission: sales_channel:read Find the existing sales channel ID used when creating orders. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": [ { "id": 7, "name": "Online store", "type": "web", "country": "US" } ] } ``` ## Get a sales channel GET /sales_channel/{saleschannel} Permission: sales_channel:read Read the channel used by an order source. Sales-channel configuration is managed by your organization. This single-record GET supports If-None-Match with a previously saved ETag. A 304 response has no body; reuse the cached result. A 200 response supplies the current result and ETag. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "id": 7, "name": "Online store", "type": "web", "country": "US" } } ``` ## List ship advice GET /ship_advice Permission: ship_advice:read Read warehouse work snapshots created by order routing. Filter by order_id or warehouse_id. Core ship-advice details are immutable; per-record custom-field values can be updated separately. Use explicit routing, rerouting, fulfillment, and cancellation operations for lifecycle changes. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": [ { "id": 5001, "order_id": 10042, "warehouse_id": 20, "fulfillment_provider_id": 6, "status": "routed", "ship_advice_item": [ { "id": 6001, "order_item_id": 20081, "sku": "TRAIL-MUG", "quantity": 2, "quantity_fulfilled": 0, "quantity_canceled": 0, "status": "pending" } ], "delivery_information": { "first_name": "Alex", "last_name": "Morgan", "address_1": "123 Example Street", "city": "San Diego", "state": "CA", "postal_code": "92101", "country": "US" } } ] } ``` ## Get ship advice GET /ship_advice/{shipadvice} Permission: ship_advice:read Retrieve a warehouse work snapshot and its item quantities before fulfillment, cancellation, or rerouting. Use the routed item ID for those operations. Core ship-advice details are immutable; only per-record custom-field values can be edited through the supported PATCH operation. This single-record GET supports If-None-Match with a previously saved ETag. A 304 response has no body; reuse the cached result. A 200 response supplies the current result and ETag. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "id": 5001, "order_id": 10042, "warehouse_id": 20, "fulfillment_provider_id": 6, "status": "routed", "ship_advice_item": [ { "id": 6001, "order_item_id": 20081, "sku": "TRAIL-MUG", "quantity": 2, "quantity_fulfilled": 0, "quantity_canceled": 0, "status": "pending" } ], "delivery_information": { "first_name": "Alex", "last_name": "Morgan", "address_1": "123 Example Street", "city": "San Diego", "state": "CA", "postal_code": "92101", "country": "US" } } } ``` ## Update ship advice custom fields PATCH /ship_advice/{shipadvice} Permission: ship_advice:update Update only per-record custom-field values on an existing ship advice. Send a fields object with values compatible with the custom-field types configured by your organization. Other top-level properties are ignored and do not change the ship advice. Omitted custom fields retain their existing values; null or an empty string removes a stored value. This operation does not create or edit custom-field definitions. Use explicit routing, fulfillment, and cancellation operations for lifecycle changes. Request example: ```json { "fields": { "exported_to_erp": true } } ``` Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "id": 5001, "order_id": 10042, "warehouse_id": 20, "fulfillment_provider_id": 6, "status": "routed", "fields": { "exported_to_erp": true } } } ``` ## Release a routing assignment POST /ship_advice_item_reroute Permission: ship_advice:create Use this reassignment workflow only for non-kit order items. Contact Acenda before releasing or reassigning kit items. Release remaining units from an existing ship advice item. This creates a reroute record and updates routing quantities; it does not select another warehouse or create replacement ship advice. Choose only unfulfilled, uncanceled, not-already-rerouted units. Re-read the order and ship advice after this step, then create the replacement assignment. After an ambiguous response, reconcile quantities before retrying. Request example: ```json { "ship_advice_item_id": 6001, "quantity_rerouted": 1 } ``` Response example: ```json { "status": 201, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "id": 7001, "ship_advice_item_id": 6001, "quantity_rerouted": 1 } } ``` ## List fulfillments GET /fulfillment Permission: fulfillment:read Read shipment confirmations. Filter by ship_advice_id or external_id to reconcile your shipment source. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": [ { "carrier": "UPS", "date_shipped": "2026-10-02T14:30:00Z", "tracking_info": [ { "number": "EXAMPLE-TRACKING-001" } ], "fulfillment_items": [ { "ship_advice_item_id": 6001, "quantity": 2 } ], "external_id": "SHIP-10042-1", "id": 7001, "ship_advice_id": 5001, "status": "shipped", "type": "shipment" } ] } ``` ## Get a fulfillment GET /fulfillment/{fulfillment} Permission: fulfillment:read Retrieve a recorded shipment and tracking information. This single-record GET supports If-None-Match with a previously saved ETag. A 304 response has no body; reuse the cached result. A 200 response supplies the current result and ETag. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "carrier": "UPS", "date_shipped": "2026-10-02T14:30:00Z", "tracking_info": [ { "number": "EXAMPLE-TRACKING-001" } ], "fulfillment_items": [ { "ship_advice_item_id": 6001, "quantity": 2 } ], "external_id": "SHIP-10042-1", "id": 7001, "ship_advice_id": 5001, "status": "shipped", "type": "shipment" } } ``` ## Create fulfillment POST /ship_advice/{shipadvice}/fulfillment Permission: fulfillment:create Confirm shipment against an existing ship advice. Use ship_advice_item_id, not order_item_id or sku, in fulfillment_items. Each quantity must fit the remaining fulfillable amount. Confirmation updates fulfillment state; external_id does not guarantee idempotency. Request example: ```json { "carrier": "UPS", "date_shipped": "2026-10-02T14:30:00Z", "tracking_info": [ { "number": "EXAMPLE-TRACKING-001" } ], "fulfillment_items": [ { "ship_advice_item_id": 6001, "quantity": 2 } ], "external_id": "SHIP-10042-1" } ``` Response example: ```json { "status": 201, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "carrier": "UPS", "date_shipped": "2026-10-02T14:30:00Z", "tracking_info": [ { "number": "EXAMPLE-TRACKING-001" } ], "fulfillment_items": [ { "ship_advice_item_id": 6001, "quantity": 2 } ], "external_id": "SHIP-10042-1", "id": 7001, "ship_advice_id": 5001, "status": "shipped", "type": "shipment" } } ``` ## Find acknowledgments GET /acknowledge/{resource} Permission: order:read List acknowledgment records for the current API identity. Filter query by ref_id to check whether your order or ship advice has already been acknowledged. An empty result can be null. Use this lookup after an ambiguous POST result, before deciding whether to retry. Response example: ```json { "status": 200, "execution_time": 0.01, "num_results": 1, "errors": null, "result": [ { "id": 801, "resource": "ship_advice", "ref_id": 5001, "external_id": "WMS-5001", "created_at": "2026-10-02T14:30:00Z" } ] } ``` ## Acknowledge a record POST /acknowledge/{resource}/{ref_id} Permission: order:create Mark an order or ship advice as received by your API identity after you have durably processed it. This is a create operation, not an idempotent upsert: sending it twice can fail with a duplicate-record error. It does not mark an order shipped. The permission for either resource is order:create. Request example: ```json { "external_id": "WMS-5001" } ``` Response example: ```json { "status": 200, "execution_time": 0.01, "num_results": 0, "errors": null, "result": { "id": 801, "resource": "ship_advice", "ref_id": 5001, "external_id": "WMS-5001", "created_at": "2026-10-02T14:30:00Z" } } ``` ## Read inventory summaries GET /inventory Permission: inventory:read Read product summaries assembled from inventory-detail rows in active warehouses. Filtering, sorting, limit, and page apply to warehouse rows BEFORE grouping by product_id; returned product summaries are then ordered by product_id. A product spanning pages can have partial totals on each page. Use inventory_detail for a complete warehouse-level sync and aggregate only after reading every page. Do not use the number of product summaries as a page-size signal. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": [ { "product_id": 101, "sku": "TRAIL-MUG", "quantity": 50, "allocated": 2, "not_available": 0, "on_hand": 48, "inventory_detail": [ { "id": 301, "product_id": 101, "warehouse_id": 20, "sku": "TRAIL-MUG", "quantity": 50, "allocated": 2, "not_available": 0, "on_hand": 48, "tracking": "basic" } ] } ] } ``` ## List pricing items GET /pricing_item Permission: pricing:read Read the stored base price, MSRP, and minimum advertised price for a catalog record. These amounts are not a promise of the final order price after channel pricing or promotions. Filter list requests by product_id to find the pricing record for a catalog item. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": [ { "id": 401, "product_id": 101, "price": { "amount": "29.00", "currency": "USD" }, "msrp": { "amount": "35.00", "currency": "USD" } } ] } ``` ## Get a pricing item GET /pricing_item/{pricingitem} Permission: pricing:read Read the stored base price, MSRP, and minimum advertised price for a catalog record. These amounts are not a promise of the final order price after channel pricing or promotions. Filter list requests by product_id to find the pricing record for a catalog item. This single-record GET supports If-None-Match with a previously saved ETag. A 304 response has no body; reuse the cached result. A 200 response supplies the current result and ETag. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "id": 401, "product_id": 101, "price": { "amount": "29.00", "currency": "USD" }, "msrp": { "amount": "35.00", "currency": "USD" } } } ``` ## Bulk update inventory PUT /inventory_detail/bulk Permission: inventory:update Set absolute stock for existing products and warehouses; missing stock rows are created. Send a JSON array with one row per product and warehouse, identified by product_id or sku plus warehouse_id, not by inventory-record id. Each row must include a positive product_id or a non-empty sku. Either identifier is sufficient; if both are supplied, product_id takes precedence. Do not submit derived kit stock. This bulk write overwrites quantity, allocated, cost, date_received, and expiration_date: omitted allocated becomes zero, omitted cost becomes zero, and omitted dates are cleared. Include the intended current metadata and coordinate concurrent writers. HTTP 201 can contain partial results. Inspect errors and re-read affected stock; summary counters are not individual row receipts. Request example: ```json [ { "sku": "TRAIL-MUG", "warehouse_id": 20, "quantity": 50, "allocated": 2, "cost": "12.50", "date_received": "2026-11-01T00:00:00Z", "expiration_date": null } ] ``` Response example: ```json { "status": 201, "execution_time": 0.018, "num_results": 1, "errors": { "values_not_found": { "product_id": null, "warehouse_id": null, "sku_not_found": null, "upc": null } }, "result": { "submitted": 1, "success": 1, "errors": 0 } } ``` ## List fulfillment providers GET /fulfillment_provider Permission: fulfillment_provider:read Look up providers and their IDs. Match a provider ID to warehouse.fulfillment_provider_id when working with warehouse inventory or routing. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": [ { "id": 6, "name": "Warehouse partner", "service": "External WMS", "type": "3pl", "active": true, "integration_subscription_id": 12 } ] } ``` ## Get a fulfillment provider GET /fulfillment_provider/{fulfillmentprovider} Permission: fulfillment_provider:read Read one provider by its Acenda ID. Warehouse records are read separately from /warehouse. This single-record GET supports If-None-Match with a previously saved ETag. A 304 response has no body; reuse the cached result. A 200 response supplies the current result and ETag. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "id": 6, "name": "Warehouse partner", "service": "External WMS", "type": "3pl", "active": true, "integration_subscription_id": 12 } } ``` ## List returns GET /return Permission: return:read Read return records and return-shipment tracking. Filter by order_id or status. These reads report return state; they do not create a return or issue a refund. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": [ { "id": 801, "order_id": 10042, "order_item_id": 20081, "quantity": 1, "rma": "RMA-10042", "external_return_id": "RETURN-10042", "reason": "Customer return", "status": "returned", "external_status": "received", "restock_inventory": true, "return_required": true, "advance_refund": false, "method": "mail", "carrier": "USPS", "date_shipped": "2026-10-25T00:00:00Z", "return_tracking": [ { "number": "9400100000000000000000", "url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=9400100000000000000000" } ] } ] } ``` ## Get a return GET /return/{return} Permission: return:read Read one return record, including return-shipment tracking, by its Acenda return ID. This single-record GET supports If-None-Match with a previously saved ETag. A 304 response has no body; reuse the cached result. A 200 response supplies the current result and ETag. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "id": 801, "order_id": 10042, "order_item_id": 20081, "quantity": 1, "rma": "RMA-10042", "external_return_id": "RETURN-10042", "reason": "Customer return", "status": "returned", "external_status": "received", "restock_inventory": true, "return_required": true, "advance_refund": false, "method": "mail", "carrier": "USPS", "date_shipped": "2026-10-25T00:00:00Z", "return_tracking": [ { "number": "9400100000000000000000", "url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=9400100000000000000000" } ] } } ``` ## List extended returns GET /return_extended Permission: return:read Read returns with product, SKU, and original-order context. Each id is the same return-record ID used by /return. Filter by top-level fields such as sku, order_id, or status. num_results is the number of records on the current page, not a total count. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": [ { "id": 801, "order_id": 10042, "order_item_id": 20081, "quantity": 1, "rma": "RMA-10042", "external_return_id": "RETURN-10042", "reason": "Customer return", "status": "returned", "external_status": "received", "restock_inventory": true, "return_required": true, "advance_refund": false, "method": "mail", "carrier": "USPS", "date_shipped": "2026-10-25T00:00:00Z", "return_tracking": [ { "number": "9400100000000000000000", "url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=9400100000000000000000" } ], "product_id": 101, "sku": "TRAIL-MUG", "external_sku": "TRAIL-MUG", "order_sales_channel_id": 7, "order_sales_channel_name": "Online store", "order_sales_channel_type": "web", "order_sales_channel_subtype": null, "order_sales_channel_country": "US", "order_purchase_order": "PO-10042", "order_external_order_id": "WEB-10042" } ] } ``` ## Get an extended return GET /return_extended/{id} Permission: return:read Read one return with product and original-order context. The ID is the Acenda return-record ID, not a product, order, or order-item ID. If no record matches, this endpoint currently returns HTTP 200 with a default-valued record; verify that result.id matches the requested ID before using it. This single-record GET supports If-None-Match with a previously saved ETag. A 304 response has no body; reuse the cached result. A 200 response supplies the current result and ETag. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "id": 801, "order_id": 10042, "order_item_id": 20081, "quantity": 1, "rma": "RMA-10042", "external_return_id": "RETURN-10042", "reason": "Customer return", "status": "returned", "external_status": "received", "restock_inventory": true, "return_required": true, "advance_refund": false, "method": "mail", "carrier": "USPS", "date_shipped": "2026-10-25T00:00:00Z", "return_tracking": [ { "number": "9400100000000000000000", "url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=9400100000000000000000" } ], "product_id": 101, "sku": "TRAIL-MUG", "external_sku": "TRAIL-MUG", "order_sales_channel_id": 7, "order_sales_channel_name": "Online store", "order_sales_channel_type": "web", "order_sales_channel_subtype": null, "order_sales_channel_country": "US", "order_purchase_order": "PO-10042", "order_external_order_id": "WEB-10042" } } ``` ## Bulk update base prices PUT /pricing_item/bulk Permission: pricing:update Update stored base prices, MSRP, and minimum advertised prices for existing products; missing pricing rows are created. Send a JSON array with one row per product, identified by product_id or sku, not by pricing-record id. Each row must include a positive product_id or a non-empty sku. Either identifier is sufficient; if both are supplied, product_id takes precedence. Omitted or null price columns retain their values; this is not a way to clear them. Supply currencies explicitly for new amounts. This route does not update promotions or custom prices. HTTP 200 can contain partial results: inspect errors and re-read affected prices before retrying. Summary counters are not individual row receipts. Request example: ```json [ { "sku": "TRAIL-MUG", "price": { "amount": "29.00", "currency": "USD" }, "msrp": { "amount": "35.00", "currency": "USD" } } ] ``` Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": { "values_not_found": { "product_id": null, "sku_not_found": null } }, "result": { "submitted": 1, "success": 1, "errors": 0 } } ``` ## List ship advice cancellations GET /ship_advice/{shipadvice}/cancellation Permission: cancellation:read Read cancellation records for this ship advice. Use these records and current ship advice item quantities to reconcile an ambiguous cancellation response. Response example: ```json { "status": 200, "execution_time": 0.018, "num_results": 1, "errors": null, "result": [ { "id": 9001, "ship_advice_id": 5001, "reason": "Unable to fulfill", "cancellation_items": [ { "ship_advice_item_id": 6001, "quantity": 1 } ] } ] } ``` ## Cancel unfulfilled items POST /ship_advice/{shipadvice}/cancellation Permission: cancellation:create Cancel remaining units on an existing ship advice using ship_advice_item_id and quantity. Cancellation records are immutable. New cancellation records return 201. A canonical retry for an already fully canceled item returns 200 with a null result; this narrow behavior is not a general idempotency guarantee for partial cancellations. Re-read state after an ambiguous response. Cancellation retains the original order and its external_order_id. It does not make that ID reusable within the same sales_channel_id. It cannot cancel quantities already fulfilled. Request example: ```json { "reason": "Unable to fulfill", "cancellation_items": [ { "ship_advice_item_id": 6001, "quantity": 1 } ] } ``` Response example: ```json { "status": 201, "execution_time": 0.018, "num_results": 1, "errors": null, "result": { "id": 9001, "ship_advice_id": 5001, "reason": "Unable to fulfill", "cancellation_items": [ { "ship_advice_item_id": 6001, "quantity": 1 } ] } } ``` Response example: ```json { "status": 200, "execution_time": 0.01, "num_results": 0, "errors": null, "result": null } ```