2. API conventions
Portal endpoints use the envelopes below. Timestamps are ISO 8601 with explicit offsets; money is a decimal string. Authentication and public reset endpoints have their own documented response shapes.
2.1 Addresses and headers
Use a deployment-provided DLT_ORIGIN, for example https://dlt.example.com. The example hostname is not a deployed service.
| Purpose | Base path |
|---|---|
| Portal login, password recovery and shared currency lookup | {DLT_ORIGIN}/api/v1 |
| Implemented Portal APIs | {DLT_ORIGIN}/api/v1/portal |
Paths beginning /auth, /portal or /json in the detailed tables are relative to /api/v1. The canonical index uses full /api/v1/... paths. The signed email-verification web path is relative to the origin. Do not prepend /api/v1 twice.
Send Accept: application/json. Send Content-Type: application/json for JSON bodies and multipart form data for logo uploads. Let the HTTP client generate multipart boundaries.
Protected calls use:
Authorization: Bearer <access_token>
Accept: application/json
2.2 Merchant identity and environment
Implemented Portal APIs derive the merchant from the user token and accept no merchant selector. A user belongs to one merchant, matching the current User.merchant_id model. Multi-merchant switching is outside this version.
Portal collection list/detail/history/summary/chart endpoints require environment=test or environment=live. The same user PAT can read both explicitly selected modes. Do not infer environment from the browser hostname, token type, or merchant's current integration setting.
2.3 Portal response contract
The canonical /portal/* endpoints use this success envelope unless their table specifies 204 No Content. Login, logout and public password-reset responses have the endpoint-specific shapes documented in section 3:
{
"success": true,
"message": "Success",
"data": {}
}
Paginated Portal collections use data as an array and include meta:
{
"success": true,
"message": "Success",
"data": [],
"meta": {
"current_page": 1,
"per_page": 25,
"total": 0,
"last_page": 1
}
}
Unless overridden below, page defaults to 1 and per_page to 25; both are positive integers, with per_page capped at 100. Empty collections return 200 with an empty array. A missing individual resource returns 404.
Portal credential consumption returns JSON under data as documented in section 6.2. Never call response.json() on a 204 response.
Portal timestamps use ISO 8601 with an explicit offset; null timestamps are meaningful. Money is a decimal string paired with a currency; do not perform financial arithmetic using JavaScript floating-point numbers. Counts are integers; percentage values are decimal strings in percentage points from 0 to 100, or null for empty populations. Different timestamp serializers may emit Z, +00:00, other offsets or fractional seconds; parse as instants rather than assuming one fixed string format.
2.4 Request and client parsing rules
Send only documented inputs. Most Portal mutation and collection/history/reporting Form Requests reject unexpected fields; endpoint validation tables specify exceptions such as password change and metadata lists. Send only documented inputs even when extras are ignored. GET filters belong in the query string, not a JSON body. JSON PATCH submits only changed allowed fields; omission preserves stored values, while null is accepted only where specifically documented. Profile/merchant empty PATCH bodies are invalid. Integration-key DELETE requires its documented JSON body; logo/member/invitation DELETE requests do not.
Build queries with a URL encoder such as URLSearchParams. A literal + in an offset or dial code must not become a space. Route identifiers are opaque resource references: use returned values and URL-encode dynamic path segments; never derive another resource's ID from a displayed reference. Numeric IDs are currently serialized by Laravel as numbers; do not perform arithmetic on them. If deployed IDs can exceed JavaScript's safe integer range, agree an exact-integer parser or string transport with the DLT team rather than silently rounding them.
Nullable values are not errors: absent logo, pending email/expiry, legacy transaction amount/currency/brand/method/timestamps, optional delivery metadata and omitted historical payloads all have documented null behavior. Treat an empty array, missing individual resource and unavailable service distinctly. Transaction status is already normalized; do not recompute it from legacy flags or history HTTP status. Profile/member roles use lowercase strings; login's existing user projection uses numeric roles (see section 4).
The DLT backend API does not implement the Merchant Portal browser's cookie session, CSRF token endpoint, routing, UI or logout cookie cleanup. These are responsibilities of DLT Merchant Portal within the same DLT ecosystem. Bearer-token DLT calls and cookie-authenticated browser-to-Portal calls are separate transports.
2.5 Request validation conventions
Validation tables list every documented input’s location, required/optional state, type, size, permitted values and cross-field rules. Optional means omit the field; it does not automatically permit null or an empty value. Required fields and fields marked “required if supplied” reject null/empty values. Only explicitly nullable fields accept null. Send strings as JSON strings and upload images as multipart files. Query pagination/interval values are validated as integers where stated; URL values arrive as text. String limits are character counts; upload limits are KB.
Laravel trims most input strings and converts empty strings to null before validation. Password fields are not trimmed. The integration-key value preserves surrounding spaces, but a whitespace-only value still fails the required rule. Avoid sending placeholder angle-bracket values literally: replace them with real credentials, valid proofs or passwords satisfying the relevant policy.
For partial updates, supply at least one allowed field and omit unchanged fields. Fields such as role, ownership, activation, secrets and verification state cannot be injected into profile/merchant PATCH requests. Extra-field behavior is endpoint-specific: the tables explicitly distinguish validators that reject unlisted fields from older/list controllers that ignore them. Ignored fields grant no additional capability. GET examples use query parameters and no request body. Integration-key DELETE requires JSON; the other documented DELETE requests accept no body fields.
Route identifiers belong only in the path. Resource ownership, account state, password proof and permissions are checked in addition to format validation. These can produce 401/403/404/409/410/429/503 instead of a field-validation error; use each endpoint’s contract. A conflicting merchant selector may be rejected before request validation. Frontend checks help users complete forms; the server remains authoritative.
| Header | Requirement | Validation/behavior |
|---|---|---|
Accept |
Use application/json |
Requests documented here expect JSON API success/errors. |
Content-Type |
JSON mutations: application/json; logo: multipart with generated boundary |
OAuth machine grant uses application/x-www-form-urlencoded in the separate integrations guide. |
Authorization for login/acceptance |
HTTP Basic with the Portal application client ID and plaintext secret | Literal Basic prefix; strict Base64 of a nonempty client_id:client_secret pair; must match a usable registered confidential ecosystem web client. |
X-DLT-Application for login/acceptance |
merchant_portal |
Trimmed application code must match the Basic-authenticated registration; acceptance explicitly requires this application. |
Origin for login/acceptance |
Optional | If nonempty, must exactly match the registered origin allowlist after trailing-slash normalization. No wildcard matching. |
Authorization for protected APIs/logout |
Bearer <user_pat> |
Valid, unexpired, unrevoked PAT for an active eligible merchant user/application with required scope; permissions remain endpoint-specific. |
Portal body/query validation failures return 422 with error_code=VALIDATION_ERROR and an errors object keyed by field (arrays of messages). For example, an empty profile PATCH returns:
{
"success": false,
"error_code": "VALIDATION_ERROR",
"message": "Validation error",
"errors": { "profile": ["Supply at least one profile field."] }
}
Login and public password-reset validators retain their legacy 422 success/message/errors shape without error_code; reset-link uses message Validation error., while login/new-password use Validation error. OAuth grants return OAuth errors. Do not apply one error parser to all guides. Field-level messages are server feedback, not stable machine codes.
Every endpoint in the canonical index links to its validation table. The signed email link (5.3) and currency lookup (12.1) also document their input rules.