X-ERP API Developer Guide
1. Audience and Scope
This guide is for developers integrating external applications with X-ERP. It describes the existing API, not a new public contract layer. The companion endpoint reference covers the compiled server's discoverable operations, including ERP, portal, administrative and application-specific operations. An endpoint's inclusion in documentation does not grant access or mean it is suitable for unattended external integration.
Use the files together:
API-Developer-Guide.md: authentication, examples, conventions and integration precautions.Endpoint-Reference.md: generated methods, paths, inputs and declared response types, grouped by controller.openapi.json: machine-readable schemas and operations for OpenAPI-compatible tools.coverage.json: exact generation counts, actions outside OpenAPI and operations without declared success schemas.README.md: generation, validation and publication instructions.
The existing ../Partner-APIs/ package is a separate curated partner subset. This comprehensive package does not replace its allowlist.
Contract limitations
v1identifies the Swagger document. Existing routes are generally/api/<Controller>/..., not/api/v1/.... No long-term backward-compatibility guarantee is implied.- Some controllers are source-generated; others inherit many operations from a generic controller. Consult the exact operation rather than extrapolating routes from the page name.
- Some actions return
IActionResult, files, anonymous objects or dynamically shaped results. Missing response schemas are disclosed, not fabricated. - Actions without an explicit HTTP method, vendor protocols and browser form endpoints are discussed separately. They are not silently represented as invented REST operations.
- The reference describes compiled metadata. It does not prove database availability, business-rule behavior or successful live requests. Examples below were checked against source contracts, not executed against a company database.
2. Before Making Requests
Obtain from the deployment administrator:
- The HTTPS application base URL, including its virtual-directory path, if any.
- The company database name selected for the integration.
- An active integration account and its required Web API permissions.
- A test company with suitable sample records.
- Confirmation of which write, posting, sending and maintenance workflows the integration may perform.
Use placeholders only in examples. Never publish credentials, cookies, personal data, connection strings or production request/response captures. An account marked for Web API use can sign in through the API; this does not turn the API into a bearer-token service.
For example, if the application base URL is https://erp.example.com/xerp/, the login URL is https://erp.example.com/xerp/api/WebApiUser/WebApiLogin. Resolve relative api/... paths against that base; a leading slash would discard the virtual directory in many HTTP clients.
3. Authentication and Company Context
Login
POST api/WebApiUser/WebApiLogin
Content type: application/json.
The basic request is:
{
"email": "integration@example.com",
"password": "<retrieve from your secret store>",
"databaseName": "<company database name>",
"rememberMe": false,
"keepLoggedIn": false
}email, password and databaseName are required. On success the endpoint returns HTTP 200 with:
{
"message": "Login successful."
}This response is not an APIEntityResponse<T> and does not contain an access token. ASP.NET Core Identity issues the authentication cookie named X-ERPAppAuthCookie. Retain all Set-Cookie values using an HTTP cookie container; framework cookie chunking may result in more than one cookie. Do not concatenate or parse the protected cookie payload yourself.
The authenticated identity contains the company database selection. Use separate cookie containers for separate companies or accounts. Do not share one session across tenants, and do not assume a query parameter can switch the company of an existing session.
Lifetime and renewal
For API login, the normal session is initially given a 20-minute expiry with refresh allowed; the application cookie configuration enables sliding expiration. Preserve renewed cookies returned by subsequent requests.
If keepLoggedIn is true, keepLoggedInHours selects a persistent session duration. The request model accepts 1–720, but the current login implementation caps the duration at 168 hours. Its default is 8 hours. Do not treat the model's upper range as the effective lifetime.
rememberMe controls persistence for the normal API login path. Authentication can still fail or expire; clients must handle that rather than assume a permanently valid cookie.
Login failures and throttling
- The application converts outgoing HTTP 401 responses to 403. Do not wait only for 401 to detect an authentication failure.
- Login failure responses can be JSON strings rather than error envelopes.
- Login is limited to five attempts in a 30-second window per client IP; excess requests receive 429. Several integrations behind the same proxy may share a limit.
- Lockout and password-change requirements can prevent login. Do not repeatedly retry rejected credentials.
- If a forced password change is required,
newPasswordandconfirmNewPasswordare supported by the login input. Coordinate credential rotation with the administrator. - An account requiring two-factor authentication can receive
RequiresTwoFactor. This endpoint does not implement a complete programmatic second-factor exchange; arrange an appropriate supported authentication workflow rather than assuming the password request succeeds.
There is no OAuth client-credentials or JWT exchange documented by this guide. Partner portal credential/session endpoints are separate business workflows; their stored session identifiers must not be substituted for the application authentication cookie.
Browser clients and logout
The application cookie is HttpOnly, Secure and SameSite=Lax. Use HTTPS and normal browser cookie handling. A Swagger UI Authorize dialog cannot manually write a browser's Cookie header. A separately hosted documentation site also does not automatically share the application's authenticated browser session.
The /Account/Logout and related routes are Identity/browser endpoints, with form and redirect behavior. They are not JSON bearer-token revocation endpoints. For a server-to-server client, discard its cookie container when ending local use; that alone is not a claim of server-side session revocation. Coordinate any account/session revocation requirements with the deployment administrator.
4. First Requests
cURL
The following commands use curl.exe so PowerShell does not select an alias. Prepare a temporary login.json containing the login body, with access limited to the integration user. Do not commit that file or cookies.txt.
curl.exe --fail-with-body --silent --show-error --cookie-jar cookies.txt --header "Content-Type: application/json" --data-binary "@login.json" "https://erp.example.com/xerp/api/WebApiUser/WebApiLogin"
curl.exe --fail-with-body --silent --show-error --cookie cookies.txt --cookie-jar cookies.txt "https://erp.example.com/xerp/api/Article/Page?skip=0&take=20&requireTotalCount=true"The first command must succeed before the second is attempted. In an automated script, inspect the process exit code and response instead of running both unconditionally. Delete the temporary credential and cookie files when finished. Do not use --insecure to bypass TLS validation.
C# with a cookie container
This .NET example uses environment variables as placeholders for configuration/secret-store integration. It performs only login and a read operation.
using System.Net;
using System.Net.Http.Json;
static string RequiredSetting(string name) =>
Environment.GetEnvironmentVariable(name)
?? throw new InvalidOperationException($"Missing setting: {name}");
var cookies = new CookieContainer();
using var handler = new HttpClientHandler
{
CookieContainer = cookies,
UseCookies = true,
AllowAutoRedirect = false
};
using var client = new HttpClient(handler)
{
BaseAddress = new Uri(RequiredSetting("XERP_BASE_URL").TrimEnd('/') + "/"),
Timeout = TimeSpan.FromSeconds(60)
};
using var login = await client.PostAsJsonAsync("api/WebApiUser/WebApiLogin", new
{
email = RequiredSetting("XERP_EMAIL"),
password = RequiredSetting("XERP_PASSWORD"),
databaseName = RequiredSetting("XERP_DATABASE"),
rememberMe = false,
keepLoggedIn = false
});
if (login.StatusCode != HttpStatusCode.OK)
throw new InvalidOperationException($"X-ERP login failed: HTTP {(int)login.StatusCode}.");
using var page = await client.GetAsync("api/Article/Page?skip=0&take=20&requireTotalCount=true");
if (!page.IsSuccessStatusCode)
throw new InvalidOperationException($"X-ERP read failed: HTTP {(int)page.StatusCode}.");
var pageJson = await page.Content.ReadFromJsonAsync<System.Text.Json.JsonElement>();
// Process only the fields required by the integration; do not log company data.Use a securely managed, reusable client/session in a real integration. Do not create a fresh login for every request. Handle HTTP and business errors as described below; the example deliberately does not implement retries or credential rotation.
JavaScript in an already authenticated, same-origin browser
const applicationBaseUrl = "https://erp.example.com/xerp/";
const url = new URL("api/Article/Page", applicationBaseUrl);
url.search = new URLSearchParams({ skip: "0", take: "20", requireTotalCount: "true" });
const response = await fetch(url, { credentials: "same-origin" });
if (!response.ok) {
throw new Error(`X-ERP request failed: HTTP ${response.status}`);
}
const page = await response.json();This assumes the browser is already authenticated on that origin. It does not establish cross-origin access or bypass SameSite/CORS restrictions. Do not place integration-account passwords into a public browser application. Server-side JavaScript clients need an actual cookie-jar implementation; native fetch is not, by itself, a persistent login session.
5. Reading the Endpoint Reference
For every operation, follow all of these independently:
- HTTP method and exact path, including every required route segment.
- Path versus query parameters and their declared types.
- Request-body content type and schema.
- Response content type and declared schema, when available.
- Authentication, permission and operation-specific notes.
A POST may perform a read, and a PUT or POST may take both a body and query values. Do not infer binding solely from the verb. OpenAPI's required values describe binding/model metadata, not every database or business rule.
The OpenAPI security scheme describes cookie authentication as apiKey in cookie; this is the OpenAPI representation of an existing cookie session, not a separately issued API key.
Permission names commonly combine the controller with Read, Create, Update or Delete, such as Article-Read. Exact availability depends on account configuration and the action. The reference is documentation of requirements, not permission provisioning.
6. Response and Error Handling
Standard business envelope
Many operations return:
{
"data": null,
"success": false,
"errorMessages": ["<business validation message>"]
}Always check both HTTP status and success. Some existing endpoints return HTTP 200 with success: false; this remains a supported compatibility case. An empty or null data value is not necessarily a transport failure.
Other operations return grid results, primitive JSON values, JSON strings, files or operation-specific objects. Do not deserialize every response into this envelope.
Common status handling
| Status | Client behavior |
|---|---|
| 200/other declared success | Inspect the documented payload and success if an envelope is returned. |
| 400 | Inspect validation/business messages; fix input rather than blindly retry. Automatic model validation may use a ProblemDetails-shaped object. |
| 403 | Session may be absent/expired, credentials rejected, or access unavailable. The server converts 401 to 403; do not repeatedly reauthenticate every 403. |
| 404 | Route or record may be missing; some session/resource operations also use it. |
| 409 | Handle an integrity or concurrency conflict. Reload state before deciding whether to retry a write. |
| 422 | Some business workflows may report validation failure; follow the particular operation. |
| 429 | Back off; respect Retry-After if supplied, but do not assume it is always present. |
| 500 | Retain the error ID and contact support; do not expose raw server details to end users. |
Integrity and EF concurrency failures handled by the API middleware produce ProblemDetails-shaped JSON with status, title, detail, instance, code and traceId. Codes include DATA_INTEGRITY and CONCURRENCY_CONFLICT. Treat the body shape, not only a particular content-type header, as relevant when parsing existing responses.
Unexpected failures handled by that middleware return an error ID and a safe message. Development responses may additionally contain technical detail; never publish that detail or use it as a stable integration contract. Middleware responses need not be listed as explicit response attributes on every action.
Retry policy
- Retry only operations known to be safe to repeat, with a bounded attempt count and backoff.
- Do not automatically replay document posting, email sending, financial processing, stock movements, order placement, imports or maintenance operations after a timeout. The server may have completed them before the client lost the response.
- No global idempotency-key mechanism is promised by this API documentation.
- Keep error IDs and minimal operational metadata in logs; redact credentials, cookies and business payloads.
7. Paging, Sorting and Filtering
Operations taking DataSourceLoadOptions bind individual query parameters. Do not send one JSON loadOptions body.
| Parameter | Format |
|---|---|
skip | Integer offset. Negative values become zero. |
take | Requested page size. Use a positive value, typically 20–100. Values above 1000 are clamped. |
requireTotalCount | Boolean request for a total count where the action supports it. |
sort | URL-encoded JSON sorting array. |
filter | URL-encoded JSON filter expression. |
group, select, totalSummary, groupSummary | JSON descriptors; support depends on the action. |
requireGroupCount, isCountQuery | Boolean flags; support depends on the action. |
Always specify positive take. The current binder caps oversized values but does not turn omitted or zero take into a guaranteed bounded page. Custom SQL-backed lists and ordinary DevExtreme data-source lists can support different options.
For the inherited Article/Page operation:
curl.exe --fail-with-body --silent --show-error --cookie cookies.txt --cookie-jar cookies.txt --get "https://erp.example.com/xerp/api/Article/Page" --data-urlencode "skip=0" --data-urlencode "take=20" --data-urlencode "requireTotalCount=true" --data-urlencode 'sort=[{"selector":"Id","desc":false}]'A filter expression can be ["Id","=","DEMO-001"]. The value must match the selected entity property's type; property names such as Id refer to model fields, not necessarily their camel-cased JSON names. Use exact property names and URL encoding.
Generic Page defaults to descending primary-key order when no sort is supplied; PageAsc defaults to ascending order. Other specialized list actions may use different default ordering.
Grid payloads are not wrapped in APIEntityResponse<T>. They typically contain data and optionally counts/summaries. Grouped results are not flat entity arrays. Many actions explicitly use a serializer without a naming policy, so nested entity fields can retain PascalCase even when ordinary MVC responses use camelCase. Confirm the operation's actual shape before generating a strongly typed client.
8. Generic Entity Operations
Where exposed by the particular controller, the generic implementation provides:
| Operation | Semantics |
|---|---|
GET api/<Controller> | Read the entity list in a business envelope; not necessarily paged. Prefer a supported page operation for larger collections. |
POST .../GetAllByValue | Read matching entities using property filters combined with AND. |
POST .../GetAllGraphByValue | Read matching entities with validated navigation includes. |
POST .../GetByValue | Read the first matching entity; a missing entity produces 404 in the generic implementation. |
POST .../TryGetByValue | A missing match returns 200 with success: true and data: null. |
POST .../SearchByValue | Uses the first filter for a case-normalized contains search; not a general query language. |
POST api/<Controller> | Create an entity; required relationships and business constraints still apply. |
PUT api/<Controller> | Update an entity; not a generic JSON Patch operation. |
PUT .../UpsertGraph | Write an entity graph; review relationship and lifecycle behavior before use. |
PUT .../UpsertGraphWithExtraFields | Write a graph with supported XX/Toolbox field values. Use its exact schema. |
POST .../DeleteByFilter | Destructive filter-based deletion. Requires deliberate, narrowly scoped filters. |
Always verify that the operation appears for that controller in the reference. Overrides and dedicated business operations can differ from the inherited implementation.
A read request for an article looks like:
{
"filters": [
{ "name": "Id", "value": "DEMO-001" }
],
"includeEntities": null
}Send it to POST api/Article/GetByValue with application/json. PropertyFilter.value is a string even when targeting a numeric property. Use at least one valid filter for single-record searches and deletion. Avoid broad deletion and do not assume empty filters are safe.
For graph reads, includeEntities can contain comma-separated navigation paths, including dotted nested paths. Only actual navigation paths validated against entity metadata are accepted. Do not submit arbitrary SQL or assume every model property is a navigation.
Prefer the dedicated workflow endpoint for posting, approving, sending, warehousing or other business transitions. A generic entity update is not a substitute for the associated business process.
9. Data, Files and Specialized Operations
- Follow each schema's key type. Some entities have string keys and others integer keys; do not assume every
Idis numeric. - Preserve the latest row-version value when an entity uses optimistic concurrency. Byte arrays use the declared JSON representation, usually base64. Do not manufacture concurrency tokens.
- Use invariant numeric formatting and schema-defined ISO date/time formats. Preserve date-time offsets where required; a date-only field is not an instant in UTC.
- Do not assume enums are transmitted by name; consult the schema and serializer attributes.
- Null navigation values may reflect the selected graph/serialization behavior rather than a deleted relationship.
- For
multipart/form-data, use the exact file-field name and any required query/form values in the reference. Let the HTTP client generate the multipart boundary. - Chunk-upload endpoints have operation-specific state and metadata. Do not treat a method named
UploadChunkFileas a generic one-request file upload. - For downloads, inspect status and content type before writing bytes. An error response is not a valid PDF, spreadsheet or archive.
- Financial, stock, email, backup, plugin and other administrative operations may have irreversible side effects. Documentation visibility is not approval to call them against production.
10. Routes Outside Ordinary OpenAPI Coverage
Actions without HTTP-method metadata
The endpoint inventory lists any compiled MVC action that cannot be assigned an explicit verb. No method or replacement path is invented for it. Such entries require review before an integration depends on them. Their presence does not constitute a new supported public route.
Reporting and dashboard protocols
The report designer, document viewer and query builder controllers explicitly opt out of ApiExplorer. The dashboard uses vendor route registration under api/dashboard. These endpoints implement the installed DevExpress component protocols, not a stable CRUD interface inferred from their names.
The application uses reporting service paths such as /DXXRD, /DXXRDV and /DXXQB. Follow the supported DevExpress integration for the deployed component version and the application's configured base path. The generated inventory retains available MVC action/route metadata; it does not claim to enumerate every vendor-generated route or message schema.
Identity/browser endpoints
These routes are registered separately from MVC controllers and are not included in the controller-only export:
| Method and route | Browser workflow |
|---|---|
POST /Account/PerformExternalLogin | Form values provider and returnUrl; initiates an external authentication challenge. |
POST /Account/Logout | Form returnUrl; clears the session, signs out and redirects. |
POST /Account/Logout2 | Clears the session, signs out and redirects to the application's logout notification flow. |
POST /Account/Manage/LinkExternalLogin | Authenticated external-login linking with form provider. |
POST /Account/Manage/DownloadPersonalData | Authenticated personal-data download; contains sensitive account data. |
Browser form/antiforgery and redirect behavior must be respected. These are not JSON integration-login endpoints. Razor component pages, static assets and Blazor transport connections are not treated as REST operations by this reference. Deployment-specific plugins or future dynamically registered routes require their own coverage review.
11. Integration Acceptance Checklist
- [ ] Base URL, virtual directory and HTTPS are correct.
- [ ] Login selects the intended company; cookies are isolated per company/account.
- [ ] Required operations work with the intended account in a test company.
- [ ] 200-with-
success:false, validation, expiry, 403, 409, throttling and technical errors are handled. - [ ] Positive page sizes and operation-specific filter support are verified.
- [ ] Row versions, date/time values, decimals and identifier types round-trip correctly.
- [ ] Writes are tested with representative relationships and business states.
- [ ] Retry behavior cannot duplicate side effects.
- [ ] Secrets and business data are absent from public examples and diagnostic logs.
- [ ] The published reference was generated from the intended application revision; unresolved coverage exceptions are acknowledged.