The Acumatica contract-based REST API is the cleanest way to integrate external systems with Acumatica ERP. Whether you're pushing invoices from an e-commerce store, pulling customer data into a CRM, or syncing inventory with a warehouse, the contract-based API is the right tool.
This guide walks through what I've learned shipping integrations against this API for production tenants — from the authentication choices that actually matter, to the patterns that prevent you waking up at 3 AM to retry batches.
Developers who already know REST and JSON, but are new to Acumatica's specific dialect. Examples are language-agnostic but I'll use C# and PHP where it helps.
What is ZIMRA FDMS?
The Fiscalisation Data Management System (FDMS) is the Zimbabwe Revenue Authority's centralised platform for receiving, validating and storing every fiscal receipt issued by a registered taxpayer. Unlike older hardware fiscal-device regimes, FDMS is software-first: your ERP signs each receipt with an FDMS-issued device certificate and submits it over HTTPS in near real time.
For an Acumatica deployment in Zimbabwe, that means three jobs the ERP itself was never built to do: hold a per-device X.509 certificate, sign each invoice/credit note payload, and survive offline windows without losing receipts.
1. Device registration
Every fiscal device in FDMS has an ID, a model, a serial and an X.509 certificate issued by ZIMRA. Registration runs once per device — typically during installation:
// 1. Generate CSR (RSA 2048)
var rsa = RSA.Create(2048);
var req = new CertificateRequest(
$"CN={deviceSerial}, O={taxpayerName}, C=ZW",
rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
var csrPem = PemEncoding.Write("CERTIFICATE REQUEST", req.CreateSigningRequest());
// 2. POST to ZIMRA — receives the signed device certificate
var body = new {
certificateRequest = new string(csrPem),
activationKey = ZimraSettings.ActivationKey
};
var resp = await http.PostAsJsonAsync(
$"https://fdmsapi.zimra.co.zw/Device/v1/{deviceId}/RegisterDevice", body);
var pem = (await resp.Content.ReadFromJsonAsync<RegisterDeviceResponse>()).Certificate;
SaveDeviceCertificate(deviceId, rsa, pem); // store private key + cert
Persist the private key in a protected store — Acumatica's UploadFile with role-restricted access works, or an Azure Key Vault reference for cloud-hosted tenants. Never commit it to a customisation package.
2. The fiscal day lifecycle
ZIMRA enforces a strict daily envelope. You must explicitly open a fiscal day before submitting receipts, and explicitly close it (with a Z-report) before opening the next:
- OpenDay → returns a
fiscalDayNoyou stamp on every receipt. - SubmitReceipt (repeat N times) → each call returns a
receiptServerSignature. - CloseDay → submit aggregated totals; ZIMRA validates against the receipts it received.
If aggregates don't match, the day stays open and you must reconcile. Build this lifecycle into a state machine on a custom DAC — never trust an in-memory variable on a multi-user ERP.
3. Signing and submitting a receipt
Every receipt payload is canonicalised, SHA-256 hashed and RSA-signed with the device certificate. The signature is what ZIMRA validates server-side:
public async Task<FdmsResult> SubmitInvoiceAsync(ARInvoice inv)
{
var payload = new ZimraReceiptDto {
DeviceID = _settings.DeviceId,
ReceiptType = MapType(inv.DocType), // FiscalInvoice / CreditNote / DebitNote
ReceiptCurrency = inv.CuryID,
ReceiptCounter = _counter.Next(),
ReceiptGlobalNo = _globalCounter.Next(),
InvoiceNo = inv.RefNbr,
ReceiptDate = inv.DocDate.Value,
BuyerData = BuildBuyer(inv),
ReceiptLines = BuildLines(inv),
ReceiptTaxes = BuildTaxes(inv),
ReceiptPayments = BuildPayments(inv),
ReceiptTotal = inv.CuryDocBal,
PreviousReceiptHash = _lastHash, // hash chain
};
var canonical = JsonCanonicalizer.Serialize(payload);
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(canonical));
payload.ReceiptDeviceSignature = new Signature {
Hash = Convert.ToBase64String(hash),
Signature = Convert.ToBase64String(_rsa.SignHash(hash,
HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1))
};
var resp = await _http.PostAsJsonAsync(
$"/Device/v1/{_settings.DeviceId}/SubmitReceipt",
new { Receipt = payload });
return await HandleResponse(resp, inv);
}
The PreviousReceiptHash field forms a tamper-evident chain — losing it forces a fresh fiscal day. Store it durably on the device DAC after every successful submission.
4. Linking credit and debit notes
FDMS requires every credit/debit note to reference the original fiscal invoice's global receipt number, not the Acumatica RefNbr. Add a custom field on ARRegister to capture the original receipt at posting time, and validate it in the graph before submission:
public sealed class ARRegisterExt : PXCacheExtension<ARRegister>
{
public static bool IsActive() => true;
[PXDBString(40, IsUnicode = true)]
[PXUIField(DisplayName = "Original FDMS Receipt #")]
public string UsrZimraOriginalReceipt { get; set; }
public abstract class usrZimraOriginalReceipt : BqlString.Field<usrZimraOriginalReceipt> { }
}
5. Offline buffering and retries
Power and connectivity in production sites are unreliable. The FDMS contract allows queued submission as long as you replay receipts in order and close each day within 48 hours of its first receipt. The pattern that has worked for me:
- Persist every signed payload to a
ZimraOutboxDAC before calling the API. - Use an Acumatica
PXLongOperationon a 60-second schedule to drain the outbox in FIFO order. - On a 4xx response, mark the row
Failedand surface it on a Reconciliation screen — never silently retry a hard rejection. - On 5xx / network errors, leave the row
Pending; the next tick retries.
6. Common errors
The three you will see most in production:
INVALID_SIGNATURE— almost always a canonicalisation difference (key order, whitespace, BOM). Use a deterministic JSON canonicalizer; do not let Newtonsoft reorder.RECEIPT_COUNTER_OUT_OF_ORDER— your outbox replayed out of order. Re-enable strict FIFO and never parallelise the drain worker.FISCAL_DAY_NOT_OPEN— you missed the OpenDay call after an Acumatica service restart. Add a graph-init check.
Production checklist
- Device certificate stored encrypted, key access audited.
- Receipt counter + global counter + previous-hash persisted atomically with the API call.
- Outbox + reconciliation screen visible to finance, not just IT.
- Automatic CloseDay scheduler at end-of-business with manual override.
- Sandbox tested against ZIMRA's full receipt-type matrix — including cancellations and discounted lines.
- NTP-synced host with drift alerting.
If you're rolling out Acumatica into Zimbabwe and want this integration delivered to spec, get in touch — happy to share the production package.