Saudi commerce · Salla · Inventory architecture

Salla order stock relocation: move branches without overselling

A production guide to treating Salla stock relocation as an auditable command, recovering unknown outcomes and reconciling every downstream inventory projection.

TOPIC HUBE-commerce Engineering
A warehouse worker uses a barcode scanner to take inventory from labeled shelves at a logistics center.
L

Moving an order's allocated stock from one Salla branch to another is a business state transition, not a cosmetic branch edit. The write changes where inventory is associated with an order; your ERP, OMS, picking queue and branch-level availability can all drift if the integration treats a successful HTTP response as the end of the workflow.

This is a practical, documentation-based engineering explainer—not news or a Salla product announcement. The endpoint, scopes, request fields and documented response codes below come from Salla's official Merchant API documentation, verified on October 31, 2026. The locking, idempotency, read-back, ledger and reconciliation patterns are engineering recommendations for production integrations; they are not claims about undocumented Salla behavior.

The answer first

Use POST /admin/v2/orders/{order_id}/inventory/relocate only after resolving active stock branches and validating the exact order item IDs and requested quantities. Serialize changes with a per-order lock, record an internal operation key, call the endpoint once, and then read branch quantities again. If the write times out, treat the outcome as unknown: inspect authoritative state before deciding whether another write is safe.

Keep Salla as the authority for the Salla-side allocation, while your ERP or OMS maintains a separate integration ledger. Reconciliation should compare evidence from both systems and raise an exception when the destination does not reflect the expected movement. A 200 response is evidence that Salla accepted the relocation; it is not evidence that every downstream system completed its own update.

ConcernSafe authorityEvidence to retain
Order-side relocationSalla relocation endpointOrder ID, source, destination, item IDs, quantities, time and response
Branch eligibilitySalla branches read endpoint plus merchant policyBranch ID, active status, stock capability and policy version
Current Salla quantitySalla product quantities read endpointBranch filter, item/SKU identity, quantity and observation time
ERP/OMS projectionThe downstream system's own ledgerExternal movement ID, status and applied digest
Recovery decisionReconciliation workerBefore/after snapshots, ambiguity reason and operator decision

Start with the documented contract

Salla documents the relocation operation as:

POST https://api.salla.dev/admin/v2/orders/{order_id}/inventory/relocate
Authorization: Bearer <token>
Content-Type: application/json

The documented scope is orders.read_write. The request example contains a source branch ID, a destination branch ID, and an array of items containing an item id and quantity:

{
  "source": 1923546554,
  "destination": 495787195,
  "items": [
    { "id": 123413414, "quantity": 2 }
  ]
}

Salla's page shows a successful 200 response and also documents 403 Forbidden and 422 Parameter Error outcomes. It does not document an idempotency-key request header, transactional coupling to an external ERP, or the exact recovery procedure after a client timeout. Therefore, do not invent those guarantees. Build the missing safety in your integration layer.

For verification, Salla documents GET /admin/v2/products/quantities with the products.read scope and a branch query parameter. The example response includes product/item identity, sku_id, sku, quantity, sold_quantity, unlimited_quantity and variant information. Salla also documents GET /admin/v2/branches with branches.read. Its response example includes branch identity, status, stock capability and fulfillment-related attributes.

These reads are the foundation of a conservative preflight and read-back process. They do not prove that the relocation endpoint implements a particular internal inventory ledger.

Model relocation as a command with evidence

Create an internal command record before making the write. The command should be immutable except for lifecycle fields:

{
  "operation_key": "merchant-42:order-3155923424:relocate:v3",
  "merchant_key": "merchant-42",
  "order_id": "3155923424",
  "source_branch_id": "1923546554",
  "destination_branch_id": "495787195",
  "items": [
    { "order_item_id": "123413414", "quantity": 2 }
  ],
  "requested_by": "allocation-rule-7",
  "requested_at": "2026-10-31T17:10:00Z",
  "state": "prepared",
  "source_snapshot_digest": "sha256:...",
  "destination_snapshot_digest": "sha256:..."
}

This is an application design, not a Salla payload. The operation key prevents your workers from creating two local commands for the same business intent. It does not make the remote endpoint idempotent. Store the exact request digest and never mutate an existing intent by reusing its key with different branches, items or quantities.

Use order item IDs in the command because the documented relocation body uses items[].id. Do not silently substitute SKU text. SKUs can be absent, duplicated across systems, reformatted or mapped to variants differently. Keep SKU as a reconciliation attribute, not as the remote command identity unless the endpoint explicitly documents it.

Original engineering diagram: a Salla relocation is prepared, serialized per order, written once, read back, reconciled and delivered through an outbox. Cover photo by Liz Roll/FEMA, public domain in the United States via Wikimedia Commons; resized or cropped for responsive presentation.
Original engineering diagram: a Salla relocation is prepared, serialized per order, written once, read back, reconciled and delivered through an outbox. Cover photo by Liz Roll/FEMA, public domain in the United States via Wikimedia Commons; resized or cropped for responsive presentation. Open for a larger view

Preflight before any write

The preflight should answer five questions:

  1. Is this the right order and current business intent? Reject a stale command if the allocation rule, order status or operator decision changed after the command was prepared.
  2. Are source and destination distinct, active stock locations? Read branches and apply merchant policy. A branch that exists is not automatically eligible for this order or shipping method.
  3. Do all item IDs belong to the order? Resolve them from an authoritative order read, not from a human-entered SKU.
  4. Are quantities positive and bounded by the intended movement? Reject zero, negative, duplicate and unexpectedly large lines before calling Salla.
  5. Can the integration explain the current branch state? Capture source and destination quantity snapshots with timestamps. If the baseline is already inconsistent, quarantine the move instead of making the discrepancy harder to diagnose.

The List Branches example exposes fields such as status, is_stock, pickable, shippable and safety_stock. Treat them as inputs to policy, not a universal formula. For example, a pickable branch might still be disallowed for a specific order because of carrier coverage, reservation state or internal operations policy.

Products with unlimited_quantity require an explicit branch policy. A numeric comparison designed for finite stock may not be meaningful for such an item. Mark the line as unlimited and avoid manufacturing a decrement/increment equation that the source does not promise.

Serialize per order, not globally

Two workers can relocate the same order at nearly the same time: an allocation rule reacts to stock, while an operator changes the fulfillment branch. A global lock protects correctness but destroys throughput. No lock allows conflicting commands.

Use a distributed lock or compare-and-set lease keyed by merchant and order ID:

lock key = merchant_key + ":order:" + order_id
acquire a short lease
reload command and order intent
run preflight
send at most one relocation request
persist response or ambiguity
enqueue read-back
release lease

The lease must have an owner token and safe renewal. A worker must not release another worker's lease after its own lease expires. Keep the remote call timeout shorter than the lease, and record the lease owner in diagnostics.

Do not use a single merchant-wide mutex. Relocations for different orders are independent and should proceed concurrently within API limits. Rate limiting and concurrency control are separate controls: a token bucket limits traffic, while the order lock prevents conflicting business transitions.

Treat timeouts as an unknown outcome

There are three broad result classes:

ResultWhat is knownNext action
200 successSalla returned a documented success responsePersist receipt, then read back and reconcile
403 or 422The request was rejectedDo not retry unchanged; inspect authorization, access and validation data
Timeout, connection reset or lost responseThe server may or may not have applied the moveMark unknown, read current state, then decide

The dangerous shortcut is an immediate blind retry after a timeout. If the first request committed but its response was lost, the retry may apply another change or produce a confusing validation error. Because the public page reviewed here does not document an idempotency key, the client cannot assume that repeating the same body is harmless.

Move the command to unknown and run a recovery read. Compare current source and destination evidence with both the preflight snapshot and the expected post-state. If the evidence clearly matches the expected movement and no competing change is visible, close the command as confirmed_after_read. If it clearly matches the baseline, a controlled retry may be allowed after revalidating the order. If the evidence is mixed or another actor changed inventory, require manual or rule-based reconciliation; do not guess.

Branch-level product quantities alone may be insufficient to attribute a delta to one order when other sales or adjustments occur concurrently. That is why the recovery record should include the order's current allocation, local command ledger, timestamps and any downstream movement IDs—not only two quantity numbers.

Read back and reconcile downstream systems

After a documented success, enqueue reconciliation instead of updating every external system inside the request handler. The worker should:

  1. read the relevant product quantities for source and destination branches;
  2. refresh the order or allocation view used by the integration;
  3. compare results with the prepared command and preflight snapshots;
  4. write an append-only integration ledger entry;
  5. publish an outbox event to ERP, OMS, WMS and picking consumers;
  6. verify each downstream projection independently;
  7. close the operation only when evidence meets the policy.

An outbox message might carry the operation key, order ID, branch IDs, item IDs, quantities and source receipt. Consumers should deduplicate by operation key plus destination system. A downstream timeout is another unknown outcome: query by the external reference before creating a second stock movement.

Keep accounting separate from observation. The Salla quantity read is current provider state. Your integration ledger is evidence of commands and decisions. The ERP stock ledger is the ERP's accounting model. Forcing one number to overwrite another without classifying the difference can hide shrinkage, late orders, manual adjustments or mapping bugs.

A practical reconciliation state machine

Use explicit states rather than booleans:

prepared
  -> preflight_failed
  -> sending
sending
  -> accepted
  -> rejected
  -> unknown
accepted
  -> reconciling
unknown
  -> reconciling
reconciling
  -> confirmed
  -> exception
exception
  -> reviewed
  -> compensated

Never move unknown directly to failed. Unknown means insufficient evidence, not failure. Similarly, do not mark accepted as fully complete until the systems that matter to the business have been reconciled.

Compensation is a new business command, not a database rollback. If an operator decides to move stock back, create a new reviewed operation with reversed branches and its own evidence. Preserve the original command and the reason for compensation.

Best practices

  • Request only the scopes needed by each component: the writer needs orders.read_write, while reconciliation reads can use products.read and branch discovery uses branches.read.
  • Store merchant identity in every lock, operation key, ledger row and metric.
  • Resolve branch and order item IDs from authoritative reads; never trust free-form client input.
  • Lock per order and revalidate intent after acquiring the lock.
  • Capture before snapshots, exact request digest, response and after snapshots.
  • Separate remote acceptance from end-to-end confirmation.
  • Route ambiguous outcomes to read-before-retry recovery.
  • Use a transactional outbox for ERP/OMS/WMS updates and stable external references.
  • Alert on operations stuck in unknown or reconciling.
  • Measure relocation age, unknown outcome rate, reconciliation lag, exception rate and duplicate-command suppression.
  • Redact bearer tokens and customer data from logs; retain only the minimum diagnostic evidence.

Anti-patterns and limits

  • Retrying a timed-out relocation immediately and assuming the first call failed.
  • Treating 200 as proof that ERP, OMS and warehouse queues are updated.
  • Sending SKU strings where the documented body expects item IDs.
  • Moving stock from and to the same branch or allowing duplicate item lines.
  • Applying a finite-stock equation to unlimited_quantity items without policy.
  • Running two allocation rules against the same order without serialization.
  • Overwriting ERP stock with a Salla quantity snapshot without explaining the delta.
  • Using one incomplete quantity read as proof that a relocation failed.
  • Deleting command evidence after compensation.

The public documentation reviewed here does not define an idempotency header, a change-event schema for this exact relocation, or a transaction spanning Salla and external systems. An integration can reduce risk and make outcomes auditable, but it cannot create cross-system atomicity that the APIs do not expose.

Implementation checklist

  1. Grant the writer orders.read_write and keep read scopes in separate credentials where practical.
  2. Resolve order item and branch IDs from current authoritative reads.
  3. Create an immutable command record with a unique operation key.
  4. Acquire a merchant-and-order lock and revalidate intent.
  5. Capture source and destination snapshots.
  6. Call the relocation endpoint once and persist the exact outcome.
  7. Send timeouts to an unknown state and read before retrying.
  8. Reconcile Salla state and append the integration ledger entry.
  9. Publish downstream changes through an idempotent outbox.
  10. Test concurrent commands, partial lines, 403, 422, timeouts, unlimited stock and compensating moves.

Related reading: Salla order reservations and calendar reconciliation, conditional webhooks and reconciliation guardrails, Salla order auto-assignment rules, and the E-commerce Engineering hub.

Primary references

Prepared by: Noor Yasser

Official references

These references document the tools discussed. Examples and design decisions are illustrative and should be adapted to the project and its versions.

Prepared by: Noor Yasser

FROM DECISION TO DELIVERY

Working through a similar engineering challenge?

I help teams turn architecture decisions into a clear scope and dependable, reviewable implementation.

Book a 30-minute callRelated serviceBackend engineering & API integrationsRelevant projectLogistics at scale