Mosce ERP · Help Center
Integrations

Inventory movements from an external system

How an external system (your own point of sale, an online store, a warehouse system) reports sales, returns, and inventory adjustments that already happened to Mosce ERP, identifying the product by its internal code.

If your business also sells outside of Mosce ERP - your own point of sale, a third-party online store, a legacy warehouse system - you can connect that system so it automatically reports inventory movements that already happened: a sale already charged, a return already received, an adjustment already made. Mosce ERP updates your real stock with every movement, identifying the product by its internal code.

Reading time: ~8 min

When to use this

  • You sell through more than one channel (a physical store with its own point of sale, a marketplace, an online store) and want Mosce ERP to reflect your real inventory without capturing it by hand.
  • You have a warehouse system or an external ERP that already tracks physical stock in and out, and you want Mosce ERP to find out automatically.
  • You need your Mosce ERP stock to stay accurate every time a movement happens outside the application.

What this is NOT

  • It is not an inventory reservation. A movement reported here already happened: the deduction or the addition is real, not a hold for a pending order. For reservations created when an order is placed inside Mosce ERP, see Inventory and stock.
  • It does not issue tax documents. This is purely operational integration: it only touches your stock. It never creates, modifies, or voids any document filed with the DGII.

Before you start

  • Access to this integration is part of the Professional, Enterprise, and Corporate plans. It is not available on the Starter plan.
  • You need the tenant:integrations permission to turn the integration on and manage its credentials. See Users, roles, and permissions to assign it to a role.
  • Even if your plan includes the integration, it ships off by default. You must turn it on explicitly before any credential can be used - accidentally enabling a channel that writes to your inventory should never happen by mistake.
Module / FeatureFreeStarterProfessionalEnterpriseCorporate
e-CF invoicing (DGII)
Inventory
Batch inventory adjustment-
Point of sale (POS)
Tasks (Kanban)-
Sales commissions-
Double-entry accounting--
Advanced reports
Outgoing webhooks-
Data tools--
XLSX export--
Backups--
Inventory API access--

Step 1 - Turn the integration on

  1. Go to Settings → API Access.
  2. Turn the switch on. While it is off, any existing credential is rejected, even a valid one.

Step 2 - Generate a credential

  1. Click New credential and give it a name that identifies the system that will use it (for example "Point of sale - Naco branch", "Warehouse ERP").
  2. Mosce ERP generates the credential and shows it only once. Copy it right away and store it in your external system's secrets manager.

The credential cannot be viewed again after you close the dialog. If you lost it, your only option is to rotate it (see Rotating or revoking a credential) - there is no way to recover the original value.

Step 3 - Send movements

Your system makes POST requests to:

https://your-domain/api/v1/integrations/inventory/movements

with the credential in the Authorization header:

Authorization: Bearer hlx_live_...

Every movement identifies the product by its internal code - the same code you see in your Mosce ERP catalog - and does not require a branch or warehouse: it is applied wherever that product carries stock, without your system needing to know your account's branch and warehouse structure.

{
  "idempotencyKey": "pos-ticket-99841",
  "occurredAt": "2026-09-21T14:32:10Z",
  "movements": [
    { "code": "0142", "quantity": -3, "reason": "sale", "note": "Ticket 99841" },
    { "code": "0311", "quantity": 2, "reason": "return" }
  ]
}
  • code - the product's internal code in your catalog.
  • quantity - signed. Negative deducts stock, positive adds it. Zero is not accepted.
  • reason - the reason for the movement: sale, return, adjustment, transfer_out / transfer_in, damage, or count (physical count). The product's history in Mosce ERP is organized by this reason.
  • occurredAt - optional, informational: when the movement actually happened in your system.
  • idempotencyKey - required and unique per business. If your system retries the request (after a network timeout, for example), reusing the same key returns the same response without applying the movement again. Without this key, a retry could deduct the same sale twice. Reusing the same key with a different body is not treated as a retry: Mosce ERP rejects it with 422 instead of applying the new body - use a fresh key for a different operation.

Each request accepts up to 200 movements.

What Mosce ERP responds

Each line is resolved independently, and a line that cannot be applied does not block the rest from being applied:

{
  "accepted": 1,
  "discarded": 1,
  "results": [
    { "code": "0142", "status": "accepted", "movementId": "..." },
    { "code": "0311", "status": "discarded", "warning": "PRODUCT_HAS_NO_INVENTORY_RECORD" }
  ]
}

A movement is discarded - it never brings down the whole request - when:

ReasonWhat it means
PRODUCT_NOT_FOUNDNo product in your catalog has that code.
PRODUCT_HAS_NO_INVENTORY_RECORDThe product exists but has no stock record in the warehouse where the movement was resolved to apply - even if the product does carry stock in another warehouse on your account.
PRODUCT_DOES_NOT_TRACK_INVENTORYThe product is a service or is marked to not track stock.
LOCATION_AMBIGUOUSMosce ERP could not automatically determine where to apply the movement.
PRODUCT_INACTIVEThe product is deactivated in your catalog.

Every discarded movement also shows up on the integration's activity screen, with its reason, so you can fix it without having to dig through your own system's logs.

Reviewing what was sent

If your team tells you "we sent the sale and it's not showing up," you don't have to guess what happened: Mosce ERP stores every request that comes in with the exact body your system sent and the exact response it got back, both in JSON.

  1. Go to Settings → API Access and open the Received requests card - a separate list from the activity screen, dedicated only to the log of each call.
  2. Page through the list with the Previous / Next buttons - newest first - until you find the request you're looking for, and open it.
  3. The detail view shows both JSON payloads side by side, with a copy button on each one, so you can compare them against what your external system believes it sent.

If your system retried the request reusing the same idempotency key, that request shows up marked as a repeat. It's the most common explanation for "I resent it and nothing happened": Mosce ERP did not apply the movement again because it had already applied it the first time, and returned the same response without touching your stock a second time.

Sending limit

This integration protects the platform with two independent limits, neither of which depends on your plan - they apply the same way to every account with the integration turned on:

  • A burst limit: up to 60 requests every 10 seconds.
  • A sustained limit on data volume: up to 6,000 inventory-movement lines per minute, not requests. A single request with 200 movements consumes 200 units of this budget, not 1.

Every response includes headers that reflect the sustained limit, the movement-lines one:

HeaderWhat it means
X-RateLimit-LimitMovement lines allowed in the current window.
X-RateLimit-RemainingMovement lines you have left in the current window.
X-RateLimit-ResetWhen the window resets.

If you exceed either limit, Mosce ERP responds with 429 and a Retry-After header telling you how long to wait before retrying. Design your system to read these headers by counting movement lines, not requests - confusing the two units makes you overestimate your real budget by up to a factor of 200.

Rotating or revoking a credential

  • Rotate: generates a new credential while the previous one keeps working for 24 hours. Use this to update your external system without interrupting the flow of movements.
  • Rotate now: generates a new credential and invalidates the previous one on the spot, with no overlap window. Use this only when you suspect a credential leaked.
  • Revoke: permanently disables the credential. Credentials cannot be deleted - revoking is their final state, precisely so the movement history keeps explaining who did what even after you stop using that credential.

How long things are kept

Not everything this integration touches is kept for the same length of time. There are three separate rules:

  • The log of each request - the body that came in and the response that went out - is kept for 90 days, a fixed window that is the same on every plan.
  • The detail of a discarded line - why a movement could not be applied - follows your account's audit-log retention, which depends on your plan and can be shorter than 90 days. The integration's activity screen shows you the exact number of days that applies to your account, so you don't have to guess.
  • The effect of an accepted movement on your stock never expires. It's your inventory history, and it's kept permanently.

In other words: past that window, what happened to your stock from an already-applied request stays forever, but the exact detail of what was sent - and why a particular line was discarded - may stop being available. If you need to keep that detail past its window, store it in your own system too.

Common errors

SymptomLikely causeFix
Every request comes back unauthorizedThe integration is off, even though the credential is validTurn on the switch under Settings → API Access
A product is always discarded with PRODUCT_HAS_NO_INVENTORY_RECORDThe product has no stock record in the warehouse where the movement was resolved to apply - for example, the product lives in warehouse B but its default warehouse points to ACheck the product's default warehouse in Mosce ERP and fix it if it does not match the warehouse where the product actually carries stock
Resending the same ticket should not duplicate the sale, but you're worried it mightThe retry is not reusing the same idempotencyKeyAlways reuse the same idempotency key for retries of the same event from your system
The request responds 422 with IDEMPOTENCY_KEY_BODY_MISMATCHThe same idempotencyKey was reused with a body different from the original requestUse a fresh key for a different operation, or resend the exact same body if this really is a retry
The credential stopped working without warningIt was rotated or revoked from the admin screenCheck the credential list and generate a new one if needed

Frequently asked questions

Can I specify which branch or warehouse the movement happened at?

No. The product code is unique across your whole account, so the movement is applied wherever that product carries stock, without your system needing to know Mosce ERP's internal branch and warehouse structure.

What happens if the product has stock in more than one warehouse?

Mosce ERP automatically resolves where to apply the movement based on your catalog configuration. If it cannot determine this with certainty, the movement is discarded with LOCATION_AMBIGUOUS instead of being applied in the wrong place.

Does this integration create tax documents?

No. It is purely operational integration: it only updates your stock. It never creates, modifies, or voids any document filed with the DGII.

How many credentials can I have active at once?

The cap depends on your plan. The Settings → API Access screen shows the real number for your account and how many you already have in use. If you hit the cap and need a new one, revoke a credential you no longer use first, or move to a plan with more room.

For how long can I review the detail of a request I already sent?

The log of each request - the body that came in and the response that went out - is kept for 90 days, the same on every plan. The effect on your stock is never lost, even after the request log has expired. See How long things are kept for all three rules.


Last updated: 2026-09-21