SAP B1 DI API error codes: what -5002, -1029, -2035 and -5006 mean, and how to fix them

By Zain Abbas, Kodenova · Updated

Short answerA DI API call returns 0 on success and a negative code on failure. Read the code and text with Company.GetLastError straight after the failed call. The codes seen most in real projects are -5002 (a validation wrapper; the text after it names the real field), -1029 (field cannot be updated), -2035 (record already exists), -2028 (no matching records), -5006 (action not supported, for example Cancel on an A/R invoice) and -5001 (internal error, often a null in a Service Layer payload).

A DI API call returns 0 on success and a negative number on failure. That number tells you the category; the text from Company.GetLastError tells you the cause. In the SAP Business One integrations I build (web portals, add-ons, e-invoicing bridges), seven codes cover almost every failure: -5002, -1029, -2035, -2028, -5006, -5001 and -4006. Below is what each one means, the exact message SAP returned in our tests, and the fix.

How do you read a DI API error correctly?

Every Add(), Update(), Cancel() and Remove() returns an int. If it is not 0, call GetLastError immediately. The error belongs to the Company object, and the next DI call overwrites it, so a log line written after a second call often shows the wrong error or none.

Two traps from real projects:

  1. Late-bound .NET code. If you use dynamic for the Company object, c.GetLastError(out int code, out string msg) throws Could not convert argument 0 for call to GetLastError. The .NET dynamic binder cannot pass COM out arguments. Call it through reflection instead.
  2. Logging only the number. -5002 alone is useless. The text carries an internal message number and often the exact field, like [INC1.UomCode] or [DocumentLines.UnitPrice][line: 1].
// Read the DI error without dynamic: works with late binding (no Interop DLL)
public static (int Code, string Message) ReadLastError(object company)
{
    var args = new object[] { 0, null };
    var byRef = new ParameterModifier(2);
    byRef[0] = true; byRef[1] = true;
    company.GetType().InvokeMember("GetLastError",
        BindingFlags.InvokeMethod, null, company, args,
        new[] { byRef }, CultureInfo.InvariantCulture, null);
    return (Convert.ToInt32(args[0]), Convert.ToString(args[1]) ?? "");
}

int rc = doc.Add();
if (rc != 0)
{
    var (code, msg) = ReadLastError(company);   // read it NOW, before any other DI call
    log.Error("DI Add failed [{Code}] {Msg}", code, msg);
}

In the Service Layer the same codes come back as JSON, for example {"error":{"code":-4006,"message":{"lang":"en-us","value":"Update the exchange rate"}}}.

Which DI API error codes come up most often?

CodeSAP text (short)What it usually meansFirst thing to check
-5002Varies, e.g. “UoM code cannot be empty [INC1.UomCode]”A business validation failed; the text names the fieldRead the field in square brackets
-1029“Field cannot be updated”You changed a field SAP locks on that document or recordDocument status, posted lines, account postings
-2035“This entry already exists in the following tables”Duplicate key: UDF, UDT, BP code, item codeCheck existence before Add
-2028“No matching records found”The key, code or saved query does not exist in this companyCompany database and exact key
-5006“The requested action is not supported for this object”Wrong method for that object type, e.g. Cancel on an invoiceUse the method that object supports
-5001“Internal error”Malformed input, often a null value in a Service Layer bodyRemove nulls and blank strings
-4006“Update the exchange rate”No rate for the document currency on that dateORTT for the posting date

What does -5002 mean in SAP Business One?

-5002 is a wrapper. SAP returns it for most document and master data validations and puts the real reason in the text. These are messages we got on live test companies, with the fix for each:

  • [-5002] 1470000542 - UoM code cannot be empty [INC1.UomCode] on an inventory counting. The item had a UoM group with two units (MT/KG). Single-UoM items passed. Fix: send UoMCode (the item’s inventory UoM) with the counted quantity.
  • [-5002] 480000112 ... does not exist on a goods issue with serial numbers. We sent the internal serial number. Outgoing documents need the SystemSerialNumber (the numerator SAP assigns per item). Keep the internal and manufacturer serials for receipts only.
  • “G/L Increase account is missing” on an inventory revaluation. The company had no Increase/Decrease G/L accounts set for the period or any warehouse. This is a setup problem, not a code problem: set the accounts in G/L account determination.
  • Base document already closed. Copying a delivery from a sales order that is fully delivered or closed fails with -5002. Check the base line before you build the target document (query below).
  • A UDF of type Float with no SubType. Creating a numeric UDF with st_None or without EditSize returned -5002. Set SubType to Price, Quantity, Rate, Sum or Percentage, and set the size.
  • A transfer based on a transfer request. Some DI builds rejected an OWTR line linked to an OWTQ base (BaseType 1250000001) that the Service Layer accepted for the same data. We now retry once as a plain copy and tell the user to close the request by hand.
  • Attachments on a closed object. SAP accepted attachment links on draft blanket agreements and open service calls only. Approved agreements and closed calls answered -5002.

One more that wasted an afternoon: when we tried to create a UDF that already existed through the Service Layer, SAP did not return -2035. It returned [-5002] No records. So do not rely on -2035 alone to detect “already exists”; check CUFD first.

Why do I get -1029 “Field cannot be updated”?

-1029 means the field exists but SAP will not let you change it in the object’s current state. The field in square brackets tells you which one. Cases from our tests:

  • [-1029] [DocumentLines.UnitPrice][line: 1] 'Field cannot be updated' on a sales return and on an A/R credit memo. SAP locks price and discount on those lines once they are posted. Header remarks still update.
  • [ORDR.VatSum] when editing a closed sales document. Closed and cancelled documents accept comment edits, but any line, price or tax change gives -1029. Check DocStatus before you offer an edit button.
  • [OACT.Postable] when “deactivating” a G/L account that already has postings. The portal was patching the wrong property. To make an account inactive, set FrozenFor = Y (with FrozenFrom / FrozenTo if you need a date range) and leave Postable alone.
  • A copied purchase order line. Once a PO line has been partly received (open quantity lower than quantity), SAP locks its price, discount and quantity. Lock those inputs in your UI so the user never hits the error.

An old SAP Community thread shows the same code on ORDR.DocRate, so it is not new behaviour. Treat -1029 as “your UI let the user edit something SAP considers final”.

How do you fix -2035 “This entry already exists”?

-2035 is a duplicate key. It shows up most when an add-on or integration creates its UDTs and UDFs at startup, or when an import sends the same BP or item code twice.

Make schema creation idempotent: look the field up first, create it only if missing, and treat -2035 on the create as success. For business data, check by your external key before Add(). For A/R or A/P invoices from an import, that is CardCode plus NumAtCard.

-- SQL Server: does the UDF exist? (TableID without @ for system tables)
SELECT FieldID, AliasID, Descr
FROM   CUFD
WHERE  TableID = 'OITM' AND AliasID = 'HSCode';

-- SAP HANA
SELECT "FieldID", "AliasID", "Descr"
FROM   "CUFD"
WHERE  "TableID" = 'OITM' AND "AliasID" = 'HSCode';

-- Is the base sales order line still open before you copy it? (SQL Server)
SELECT T0.DocNum, T0.DocStatus, T1.LineNum, T1.LineStatus, T1.OpenQty
FROM   ORDR T0 JOIN RDR1 T1 ON T1.DocEntry = T0.DocEntry
WHERE  T0.DocEntry = 1234;

-- SAP HANA
SELECT T0."DocNum", T0."DocStatus", T1."LineNum", T1."LineStatus", T1."OpenQty"
FROM   "ORDR" T0 JOIN "RDR1" T1 ON T1."DocEntry" = T0."DocEntry"
WHERE  T0."DocEntry" = 1234;

Note that AliasID in CUFD is stored without the U_ prefix.

What does -2028 “No matching records found” mean?

The record you asked for is not in this company database. In the DI API, GetByKey returns false instead of raising -2028, so always test its result. In the Service Layer you get -2028 on a GET by key, and also on SQLQueries('code')/List when the saved query does not exist in that company. That case, and how to recreate the query automatically, is covered in the Service Layer SQLQueries errors guide.

Common causes: you are logged into a different company (test vs production), the code has a different case or trailing spaces, or the key is a DocNum where SAP expects a DocEntry.

Why does Cancel() return -5006 on an A/R invoice?

-5006 says the method is not supported for that object. The classic case: Documents.Cancel() on an A/R invoice or a delivery. SAP cancels posting documents by creating a reversing document. Load the invoice, call CreateCancellationDocument(), which returns a new in-memory document (not an int), then call Add() on it. The original is then marked cancelled.

Sales orders, purchase orders, purchase requests and quotations do support plain Cancel(). Inventory postings work the other way round: in the DI version we use, goods receipts (59), goods issues (60) and stock transfers (67) do not expose CreateCancellationDocument at all, and Cancel() works. Map the method per object type instead of using one cancel path for everything.

What causes -5001 “Internal error”?

A bare -5001 Internal error names nothing, which makes it the hardest one to trace. In our Service Layer work the cause was a JSON body with "Field": null. In .NET, JsonIgnoreCondition.WhenWritingNull skips null object properties but not null values in a dictionary, so a payload built from a Dictionary<string, object> still sends them. Fix: add fields through a helper that drops nulls and blank strings, and you will not see -5001 from that source again.

How do you fix -4006 “Update the exchange rate”?

The document is in a foreign currency and there is no rate in ORTT for that currency on the posting date. SAP will not guess a rate. Either load the daily rate first, or set DocRate on the document when your source system already has the agreed rate.

-- SQL Server: is there a rate for USD on the posting date?
SELECT RateDate, Currency, Rate FROM ORTT
WHERE  Currency = 'USD' AND RateDate = '2026-10-25';

-- SAP HANA
SELECT "RateDate", "Currency", "Rate" FROM "ORTT"
WHERE  "Currency" = 'USD' AND "RateDate" = '2026-10-25';

How should an integration handle these errors?

What works for us in production:

  1. Log code, full text, object type and your own reference (for example the portal order number) on every failure.
  2. Split errors into retry and stop. A timeout or a lost session can be retried. -5002, -1029, -2035 and -5006 are data or design problems; retrying them only creates noise. For a missing saved query (-2028 on SQLQueries), recreating it and retrying once is safe.
  3. Translate for the user, keep the raw text for support. “This line is already received, so its price is locked” helps a buyer more than [-1029] [DocumentLines.UnitPrice]. Append SAP’s text in a details section.
  4. Prevent instead of catch. Most -1029 and -5002 cases above went away when the UI checked the document status, line status or item setup before sending.

If you are choosing between the two APIs for a new project, the add-on development guide and the Service Layer guide compare them. The table reference lists the tables used in the queries above, and the Service Layer request builder helps you test a payload before it reaches your code. If you need an integration or add-on built and maintained, see SAP Business One development.

Error messages quoted above come from Kodenova test runs on SAP Business One 10.0 test companies (DI API and Service Layer), October 2026.

Sources

Related services

FAQ

Frequently asked questions

Where is the full list of SAP Business One DI API error codes?

SAP does not publish one complete, current list. The SDK help explains GetLastError, and the message text that comes with the code is the useful part. A -5002 in particular covers hundreds of different validations, so log the full text, not only the number.

Do the Service Layer and the DI API return the same error codes?

Mostly yes. The Service Layer runs on the same business logic, so -5002, -1029, -2035 and -2028 mean the same thing in both. The Service Layer wraps them in JSON: error.code and error.message.value.

How do I cancel an A/R invoice through the DI API?

Not with Cancel(), which returns -5006. Call CreateCancellationDocument() on the loaded invoice, then Add() the document it returns. Sales orders, purchase orders and quotations do support Cancel().

Ready to get started?

Tell us what you need. You get a clear technical answer, a fixed scope and a price, usually within 24 hours.