Skip to content
DLTdevelopers
Sign in
DLT Merchant Portal/Account management Public documentation

Merchant & credentials

For DLT Merchant Portal developers · DLT maintainers · Contract v1.7

Configured API origin: https://stage-checkout.dxp.dtic.com.ph

Build with the right credentials. API access requires the documented authentication and permissions. Examples use synthetic data and placeholder origins.

6. Merchant account management in the Portal

These endpoints power DLT Merchant Portal screens for the signed-in user's registered business. Every protected request uses the Portal user PAT. Integration credentials returned or edited here are assets managed for that business; the Portal does not use those assets to authenticate its own DLT API calls.

6.1 Merchant and logo endpoints

Method and path Role Request Result
GET /portal/merchant Both None 200, merchant profile
PATCH /portal/merchant Administrator Editable fields below 200, updated merchant profile
GET /portal/merchant/logo Both None 200, { "url": null } or logo URL
POST /portal/merchant/logo Administrator Multipart file 200, { "url": "https://..." }
DELETE /portal/merchant/logo Administrator None 204

Editable fields: name (1–150 characters), contact_person (1–100), email (valid email, maximum 255), dial_code (nonempty string, maximum 8), phone (nonempty string, maximum 30), address (nonempty string, maximum 500), and nullable website (HTTPS URL, maximum 2,048). PATCH requires at least one allowed field; omitted fields remain unchanged. Supplied fields other than website cannot be null/empty. Strings are trimmed by Laravel middleware. Merchant email is a business contact field; it does not change any user's login email or trigger the self-profile verification flow.

Read-only fields include id, reference_id, currency, is_active, is_production, services, and timestamps. PATCH rejects unexpected fields, including fees, settlement/provider settings, and credential data, with 422 VALIDATION_ERROR. Account activation, enabling live processing, settlement settings, and provider assignments remain DLT-controlled. Account creation/deletion is outside this portal version.

Merchant scope comes from the authenticated user's server-resolved context. Routes accept no merchant ID in the path or request. Mismatched compatibility selectors return 403 MERCHANT_SCOPE_MISMATCH; other unexpected mutation fields return 422. Developer mutation requests return 403 FORBIDDEN before file processing or field validation. Missing/revoked access returns 401; inactive user/merchant or ineligible application/scope returns the existing context 403 errors.

Profile and logo GET/POST/PATCH responses use the standard success/message/data envelope with Cache-Control: no-store. Logo DELETE returns 204 with an empty body and is idempotent when no logo exists. DELETE accepts no fields. Profile mutations return message Merchant profile updated.; logo GET/POST return Success. These endpoints also use the existing API-wide request limit.

Example PATCH body:

{
  "name": "Example Merchant",
  "contact_person": "Alex Reyes",
  "website": null
}

Logo POST uses multipart field file, validated as a real PNG, JPEG, or WebP image up to 2,048 KB. Other file types, invalid image contents, extra fields, and oversized uploads return 422 VALIDATION_ERROR. Server-generated filenames are stored through the configured default filesystem beneath public/uploads/img/merchants/{authenticated_merchant_id}/; client filenames and supplied paths cannot select the storage destination.

Use the returned logo URL; missing or unusable stored files return url:null. DLT must configure public file serving before integration.

merchant data object:

{
  "id": 7,
  "reference_id": "MER_EXAMPLE",
  "name": "Example Merchant",
  "contact_person": "Alex Reyes",
  "email": "operations@example.com",
  "dial_code": "+63",
  "phone": "9000000000",
  "address": "Example business address",
  "website": "https://merchant.example.com",
  "logo_url": null,
  "currency": "PHP",
  "is_active": true,
  "is_production": true,
  "services": [{ "id": 11, "code": "COLLECTION", "type": "collection", "is_active": true, "is_production": false }],
  "created_at": "2026-09-01T00:00:00Z",
  "updated_at": "2026-10-01T09:00:00Z"
}

services projects actual provider-service assignments using their stored id, code, and type. is_active requires both the service and assignment to be active; is_production is the assignment's stored environment flag. It is not a synthesized test/live readiness matrix. Empty assignments return []. OAuth secrets, provider credentials, integration keys, callback settings, settlement balances, and fee configuration are excluded.

6.2 Portal management of merchant integration credentials

Implement the Portal's integration credential screen with these APIs. The backend calls them using the signed-in user's PAT. An authorized Merchant Administrator can manage credentials for their business's own integration; Developer users can view metadata. The separate Merchant integrations guide explains how the business's integration uses issued credentials.

Method and path Role Body Result
GET /portal/merchant/machine-clients Both Pagination query 200, metadata list
POST /portal/merchant/machine-clients Administrator label, reason, current_password 201, client metadata and one-use delivery reference
POST /portal/merchant/machine-clients/{client_id}/rotate Administrator label, reason, current_password, overlap_minutes 201, replacement metadata and delivery reference
POST /portal/merchant/machine-clients/{client_id}/revoke Administrator reason, current_password 200, revoked metadata
POST /portal/merchant/credential-deliveries/{delivery_id}/consume Issuing Administrator current_password 200, one-use credential data

client_id is the merchant ownership record ID, distinct from oauth_client_id; the route accepts a positive decimal ID up to 20 digits. delivery_id is a UUID. All requests use a merchant user PAT and the server-resolved merchant; no merchant selector is accepted. Both roles may list metadata; only Administrators may perform changes. Consumption additionally requires the same user who issued the delivery. Framework/personal-access/ecosystem client mappings cannot be rotated or revoked through these APIs.

Implemented validation: unique merchant label, 3–100 characters, beginning with an alphanumeric character and allowing letters, numbers, spaces, periods, underscores, and hyphens; reason 10–500 characters; current password required as a string up to 128 characters. Rotation requires integer overlap_minutes from 5 through 10,080. Unexpected fields, including environment or supplied OAuth secrets, are rejected with 422 VALIDATION_ERROR. Labels remain reserved after revocation; use a new operational label when issuing a replacement. These credentials are merchant-bound; no per-credential test/live restriction is asserted.

Wrong current password returns 422 with a current_password field error and leaves credentials unchanged. Credential mutations and audit writes are atomic.

Mutation limits are 5/minute per actor, merchant, and operation; consumption is 10/minute per actor and delivery, in addition to the API-wide throttle. Issuance, rotation, and revocation have separate operation buckets. Throttling returns 429 RATE_LIMITED with Retry-After; mutation-limit failures are audited. Developer mutation denial is 403 FORBIDDEN; wrong merchant selectors use the existing context 403 codes. Cross-merchant/missing ownership IDs or non-machine mappings return 404 NOT_FOUND.

The list uses standard pagination (default 25, maximum 100), ordered by descending ownership ID. Empty lists return 200 with data=[] and pagination metadata. Items expose id, oauth_client_id, label, is_active, created_at, last_used_at, revoked_at, rotation_deadline_at, and replaces_merchant_oauth_client_id. is_active accounts for mapping state, Passport client revocation, machine-client eligibility, and rotation deadline. Timestamps can be null. A delivery reference (id, expires_at) is shown only to its issuing Administrator while the client and delivery remain usable; it is null for Developers, other actors, expired/consumed deliveries, or revoked clients. Reading the list performs system-audited expired-delivery cleanup for the merchant; it does not revoke the underlying client.

Creation/rotation returns 201, data.client metadata, and data.delivery containing only id and expires_at. No secret appears in issuance or metadata responses. Revocation returns 200 with the revoked metadata directly under data and message Machine client and its tokens were revoked. All credential responses use Cache-Control: no-store.

Rotation: the API creates a replacement integration client and sets the prior client's overlap deadline. Show rotation_deadline_at and the replacement relationship (replaces_merchant_oauth_client_id) so the Administrator can coordinate their business's integration update before cutoff. Explicit revocation can end the overlap early. Repeating rotation on a prior record already assigned a deadline, or rotating an unavailable client, returns 409 CONFLICT without another replacement. The integration-side token behavior is covered in the separate Merchant integrations guide.

Revocation: revoke the Passport client, its access/refresh/auth-code records, deactivate the ownership, clear its rotation deadline, and delete any pending secret delivery. Repeated revocation returns 409 CONFLICT. Other machine clients and Portal user tokens are unaffected.

Secret delivery: the encrypted secret is available for five minutes to the issuing Administrator only. Consumption returns exactly data.oauth_client_id, data.oauth_client_secret, data.grant_type, and data.token_url, then deletes the delivery in the audited transaction. Responses include Cache-Control: no-store, private, max-age=0, Pragma: no-cache, and X-Content-Type-Options: nosniff. An unavailable, consumed, expired, or client-invalidated delivery returns 410 GONE; an existing delivery belonging to another merchant/actor returns 404 NOT_FOUND. Invalid UUID route syntax returns 404. Expired delivery removal commits before the 410 response; failed consumption audits preserve the delivery for retry.

Provide the one-use credential handover only to the issuing Administrator, so they can configure their business's integration. Keep secret responses out of browser persistence, shared caches and telemetry; do not replace the Portal ecosystem credentials or user PAT with the returned values. If consumption succeeds but the response is lost, the secret cannot be retrieved again; issue/rotate new credentials as appropriate. Never automatically retry issuance/rotation or consumption after an ambiguous response. Read metadata first; issuer-only delivery references support recovery when the issuance response is lost.

issuance body:

{
  "label": "Merchant backend primary",
  "reason": "Initial merchant integration setup",
  "current_password": "<administrator_password>"
}

Example issuance response:

{
  "success": true,
  "message": "Machine client issued. Consume the secret within five minutes.",
  "data": {
    "client": {
      "id": 12,
      "oauth_client_id": 31,
      "label": "Merchant backend primary",
      "is_active": true,
      "created_at": "2026-10-01T09:00:00Z",
      "last_used_at": null,
      "revoked_at": null,
      "rotation_deadline_at": null,
      "replaces_merchant_oauth_client_id": null
    },
    "delivery": {
      "id": "00000000-0000-4000-8000-000000000001",
      "expires_at": "2026-10-01T09:05:00Z"
    }
  }
}

Consume through POST /api/v1/portal/merchant/credential-deliveries/{delivery_id}/consume with only current_password. Example response (placeholder secret):

{
  "success": true,
  "message": "Success",
  "data": {
    "oauth_client_id": 31,
    "oauth_client_secret": "<one-use-secret>",
    "grant_type": "client_credentials",
    "token_url": "https://checkout.example.com/oauth/token"
  }
}

6.3 Portal management of merchant integration keys

These APIs power the Portal's settings screen for a business's integration keys. The Portal calls them with the user PAT; the key values are managed account assets. GET returns presence metadata; saved values cannot be retrieved.

Method and path Role Contract
GET /portal/merchant/integration-keys Both 200, data with has_authentication_token, has_secret_key, updated_at
PUT /portal/merchant/integration-keys/{key_type} Administrator value, current_password, reason; 200, metadata only
DELETE /portal/merchant/integration-keys/{key_type} Administrator current_password, reason; 204

key_type must be exactly authentication_token or secret_key; another type returns 404 NOT_FOUND. value is a non-empty, non-whitespace-only string up to 4,096 characters. The Portal preserves its exact bytes, including surrounding spaces; confirm the value matches the merchant integration. reason is required, 10–500 characters; current_password is required, at most 128 characters. Unaccepted fields return 422 VALIDATION_ERROR. A conflicting merchant selector returns 403 MERCHANT_SCOPE_MISMATCH; there is no merchant-ID parameter on these routes.

Both roles may GET; only Administrator may PUT/DELETE. Wrong current password returns 422 on current_password without changing keys. Mutations are limited to 5/minute per actor, merchant, operation and key type, plus the API limit.

Example replacement body:

{
  "value": "<merchant_integration_key>",
  "current_password": "<administrator_password>",
  "reason": "Replace merchant integration signing key"
}

GET and successful PUT return presence metadata only:

{
  "success": true,
  "message": "Success",
  "data": {
    "has_authentication_token": true,
    "has_secret_key": false,
    "updated_at": "2026-10-02T10:00:00+08:00"
  }
}

PUT uses message Integration key updated.. GET with no integration returns both flags false and updated_at: null, without creating a record. DELETE uses the replacement body with value omitted, returns 204 with no body, and is idempotent even when the key/integration is absent. It preserves the other key, logo, webhook URLs, and x_token. Responses use Cache-Control: no-store, private. No endpoint reads back plaintext or ciphertext.

Impact shown to the Administrator: changing integration keys can affect the business's payment notifications. Explain that the integration owner must coordinate a signing-key change and that removing a key without a fallback can interrupt future notifications. Previously queued notifications may still use an earlier key. The Portal does not verify or replay those notifications; its responsibility is the authenticated settings operation and clear feedback. OAuth clients and Portal user PATs remain active after these key changes.

6.4 Request validation reference

These tables reflect the current implementation’s request rules. See input conventions for required/optional/null behavior and error formats.

Validation: GET /api/v1/portal/merchant

Field Location Requirement Validation rules
(none) Body/query No documented inputs No payload is required; see behavior below.

Send no request body. No body/query fields are validated or used to choose a merchant; scope comes from the user PAT.

Validation: PATCH /api/v1/portal/merchant

Field Location Requirement Validation rules
name JSON body Optional; required if supplied Nonempty string, maximum 150 characters.
contact_person JSON body Optional; required if supplied Nonempty string, maximum 100 characters.
email JSON body Optional; required if supplied RFC-valid email string, maximum 255 characters; business contact only, no user-email uniqueness/verification flow.
dial_code JSON body Optional; required if supplied Nonempty string, maximum 8 characters; no additional dial-code pattern is imposed.
phone JSON body Optional; required if supplied Nonempty string, maximum 30 characters; no digits-only pattern is imposed.
address JSON body Optional; required if supplied Nonempty string, maximum 500 characters.
website JSON body Optional; nullable Null or HTTPS URL string, maximum 2,048 characters. Null clears the website.

Supply at least one allowed field. Omission preserves existing values; null/empty is invalid except website (empty string becomes null). Strings are trimmed. Unlisted payload/query fields are rejected with 422 VALIDATION_ERROR. A conflicting merchant selector may instead be rejected earlier with 403 MERCHANT_SCOPE_MISMATCH. Administrator only; read-only account, service, environment and credential fields cannot be changed.

Field Location Requirement Validation rules
(none) Body/query No documented inputs No payload is required; see behavior below.

Send no request body. No payload/query fields are validated or used; returns the scoped merchant’s logo.

Field Location Requirement Validation rules
file Multipart form Required Uploaded file containing a real PNG, JPEG (jpg/jpeg) or WebP image; maximum 2,048 KB. A filename/URL string is not an uploaded image.

Unlisted payload/query fields are rejected with 422 VALIDATION_ERROR. A conflicting merchant selector may instead be rejected earlier with 403 MERCHANT_SCOPE_MISMATCH. Administrator only. Let the client generate the multipart boundary; client filename/path cannot choose storage destination.

Field Location Requirement Validation rules
(none) Body/query No documented inputs No payload is required; see behavior below.

Send no request body. Any supplied body/query fields are rejected with 422. Administrator only; success returns 204 with no response payload.

Validation: GET /api/v1/portal/merchant/machine-clients

Field Location Requirement Validation rules
page Query Optional; default 1 Integer, minimum 1. Null/empty values are invalid.
per_page Query Optional; default 25 Integer, 1–100 inclusive. Null/empty values are invalid.

Send no request body. Pagination is validated; additional fields are not rejected by the list validator and do not add filters. Merchant context still applies.

Validation: POST /api/v1/portal/merchant/machine-clients

Field Location Requirement Validation rules
label JSON body Required String, 3–100 characters; first character is ASCII alphanumeric, subsequent characters are ASCII letters/digits, spaces, ., _ or -. Unique within this merchant, including revoked labels.
reason JSON body Required Nonempty string, 10–500 characters inclusive.
current_password JSON body Required Nonempty string, maximum 128 characters; must match the authenticated Administrator’s current password. Incorrect password returns a field error without mutation.

Unlisted payload/query fields are rejected with 422 VALIDATION_ERROR. A conflicting merchant selector may instead be rejected earlier with 403 MERCHANT_SCOPE_MISMATCH. Administrator only. No environment, merchant selector or caller-provided OAuth secret is accepted.

Validation: POST /api/v1/portal/merchant/machine-clients/{client}/rotate

Field Location Requirement Validation rules
client Path Required Positive canonical decimal ID, 1–20 digits; pattern [1-9][0-9]{0,19}. No leading zero, sign or decimal point. Malformed, missing or inaccessible resources return 404 NOT_FOUND. Use the merchant ownership ID, not oauth_client_id.
label JSON body Required String, 3–100 characters; first character is ASCII alphanumeric, subsequent characters are ASCII letters/digits, spaces, ., _ or -. Unique within this merchant, including revoked labels.
reason JSON body Required Nonempty string, 10–500 characters inclusive.
current_password JSON body Required Nonempty string, maximum 128 characters; must match the authenticated Administrator’s current password. Incorrect password returns a field error without mutation.
overlap_minutes JSON body Required Integer, 5–10,080 inclusive; no independent environment field.

Unlisted payload/query fields are rejected with 422 VALIDATION_ERROR. A conflicting merchant selector may instead be rejected earlier with 403 MERCHANT_SCOPE_MISMATCH. Administrator only. Prior client must be usable and not already assigned a rotation deadline; otherwise 409. Use a fresh unique label.

Validation: POST /api/v1/portal/merchant/machine-clients/{client}/revoke

Field Location Requirement Validation rules
client Path Required Positive canonical decimal ID, 1–20 digits; pattern [1-9][0-9]{0,19}. No leading zero, sign or decimal point. Malformed, missing or inaccessible resources return 404 NOT_FOUND. Use the merchant ownership ID.
reason JSON body Required Nonempty string, 10–500 characters inclusive.
current_password JSON body Required Nonempty string, maximum 128 characters; must match the authenticated Administrator’s current password. Incorrect password returns a field error without mutation.

Unlisted payload/query fields are rejected with 422 VALIDATION_ERROR. A conflicting merchant selector may instead be rejected earlier with 403 MERCHANT_SCOPE_MISMATCH. Administrator only. Already-revoked client returns 409; this operation requires JSON despite being a revocation.

Validation: POST /api/v1/portal/merchant/credential-deliveries/{delivery}/consume

Field Location Requirement Validation rules
delivery Path Required UUID route identifier from the returned resource. Invalid syntax, missing or foreign resources return 404 NOT_FOUND.
current_password JSON body Required Nonempty string, maximum 128 characters; must match the authenticated Administrator’s current password. Incorrect password returns a field error without mutation.

Unlisted payload/query fields are rejected with 422 VALIDATION_ERROR. A conflicting merchant selector may instead be rejected earlier with 403 MERCHANT_SCOPE_MISMATCH. Issuing Administrator only; an existing delivery belonging to another actor/merchant returns 404. Expired (five minutes), consumed, missing or client-invalidated delivery returns 410 GONE.

Validation: GET /api/v1/portal/merchant/integration-keys

Field Location Requirement Validation rules
(none) Body/query No documented inputs No payload is required; see behavior below.

Send no request body. No payload/query fields are validated or used; only presence metadata is returned.

Validation: PUT /api/v1/portal/merchant/integration-keys/{keyType}

Field Location Requirement Validation rules
keyType Path Required Exactly authentication_token or secret_key, case-sensitive; unsupported values return 404.
value JSON body Required Nonempty, non-whitespace-only string, maximum 4,096 characters. Surrounding spaces are preserved; do not trim the integration key.
current_password JSON body Required Nonempty string, maximum 128 characters; must match the authenticated Administrator’s current password. Incorrect password returns a field error without mutation.
reason JSON body Required Nonempty string, 10–500 characters inclusive.

Unlisted payload/query fields are rejected with 422 VALIDATION_ERROR. A conflicting merchant selector may instead be rejected earlier with 403 MERCHANT_SCOPE_MISMATCH. Administrator only; key value cannot be read back.

Validation: DELETE /api/v1/portal/merchant/integration-keys/{keyType}

Field Location Requirement Validation rules
keyType Path Required Exactly authentication_token or secret_key, case-sensitive; unsupported values return 404.
current_password JSON body Required Nonempty string, maximum 128 characters; must match the authenticated Administrator’s current password. Incorrect password returns a field error without mutation.
reason JSON body Required Nonempty string, 10–500 characters inclusive.

A JSON request body is required for this DELETE; value is not accepted. Unlisted payload/query fields are rejected with 422 VALIDATION_ERROR. A conflicting merchant selector may instead be rejected earlier with 403 MERCHANT_SCOPE_MISMATCH. Administrator only; success returns 204 with no response payload.

6.5 Complete merchant request and response examples

The following are synthetic request/response pairs. Replace IDs and credential placeholders with values from your environment. GET requests have no body; filters belong in the query string. Examples show successful operations; validation and access rules above still apply.

GET /portal/merchant

Request (no body):

GET /api/v1/portal/merchant
Accept: application/json
Authorization: Bearer <user_pat>

Response: 200.

{
  "success": true,
  "message": "Success",
  "data": {
    "id": 7,
    "reference_id": "MER_EXAMPLE",
    "name": "Example Merchant",
    "contact_person": "Alex Reyes",
    "email": "operations@example.com",
    "dial_code": "+63",
    "phone": "9000000000",
    "address": "Example business address",
    "website": "https://merchant.example.com",
    "logo_url": null,
    "currency": "PHP",
    "is_active": true,
    "is_production": true,
    "services": [
      {
        "id": 11,
        "code": "COLLECTION",
        "type": "collection",
        "is_active": true,
        "is_production": false
      }
    ],
    "created_at": "2026-09-01T00:00:00Z",
    "updated_at": "2026-10-01T09:00:00Z"
  }
}

PATCH /portal/merchant

Request:

PATCH /api/v1/portal/merchant
Accept: application/json
Authorization: Bearer <user_pat>
Content-Type: application/json

{
  "name": "Example Merchant Updated",
  "contact_person": "Alex Reyes",
  "website": null
}

Response: 200.

{
  "success": true,
  "message": "Merchant profile updated.",
  "data": {
    "id": 7,
    "reference_id": "MER_EXAMPLE",
    "name": "Example Merchant Updated",
    "contact_person": "Alex Reyes",
    "email": "operations@example.com",
    "dial_code": "+63",
    "phone": "9000000000",
    "address": "Example business address",
    "website": null,
    "logo_url": null,
    "currency": "PHP",
    "is_active": true,
    "is_production": true,
    "services": [
      {
        "id": 11,
        "code": "COLLECTION",
        "type": "collection",
        "is_active": true,
        "is_production": false
      }
    ],
    "created_at": "2026-09-01T00:00:00Z",
    "updated_at": "2026-10-03T09:00:00+08:00"
  }
}

Request (no body):

GET /api/v1/portal/merchant/logo
Accept: application/json
Authorization: Bearer <user_pat>

Response: 200.

{
  "success": true,
  "message": "Success",
  "data": {
    "url": null
  }
}

Request (multipart file upload): Let curl generate the multipart boundary. Replace the local filename with a valid image.

curl --request POST "https://dlt.example.com/api/v1/portal/merchant/logo" \
  --header "Authorization: Bearer <user_pat>" \
  --header "Accept: application/json" \
  --form "file=@merchant-logo.png;type=image/png"

Response: 200. The URL is illustrative; use the returned URL.

{
  "success": true,
  "message": "Success",
  "data": {
    "url": "https://dlt.example.com/storage/uploads/img/merchants/7/example.png"
  }
}

Request (no body):

DELETE /api/v1/portal/merchant/logo
Accept: application/json
Authorization: Bearer <user_pat>

Response: 204 No Content — empty body; do not parse JSON.

HTTP/1.1 204 No Content

GET /portal/merchant/machine-clients

Request (no body):

GET /api/v1/portal/merchant/machine-clients?page=1&per_page=25
Accept: application/json
Authorization: Bearer <user_pat>

Response: 200.

{
  "success": true,
  "message": "Success",
  "data": [
    {
      "id": 12,
      "oauth_client_id": 31,
      "label": "Merchant backend primary",
      "is_active": true,
      "created_at": "2026-10-01T09:00:00Z",
      "last_used_at": null,
      "revoked_at": null,
      "rotation_deadline_at": null,
      "replaces_merchant_oauth_client_id": null,
      "delivery": null
    }
  ],
  "meta": {
    "current_page": 1,
    "per_page": 25,
    "total": 1,
    "last_page": 1
  }
}

POST /portal/merchant/machine-clients

Request:

POST /api/v1/portal/merchant/machine-clients
Accept: application/json
Authorization: Bearer <user_pat>
Content-Type: application/json

{
  "label": "Merchant backend primary",
  "reason": "Initial merchant integration setup",
  "current_password": "<administrator_password>"
}

Response: 201.

{
  "success": true,
  "message": "Machine client issued. Consume the secret within five minutes.",
  "data": {
    "client": {
      "id": 12,
      "oauth_client_id": 31,
      "label": "Merchant backend primary",
      "is_active": true,
      "created_at": "2026-10-01T09:00:00Z",
      "last_used_at": null,
      "revoked_at": null,
      "rotation_deadline_at": null,
      "replaces_merchant_oauth_client_id": null
    },
    "delivery": {
      "id": "00000000-0000-4000-8000-000000000001",
      "expires_at": "2026-10-01T09:05:00Z"
    }
  }
}

POST /portal/merchant/machine-clients/12/rotate

Request:

POST /api/v1/portal/merchant/machine-clients/12/rotate
Accept: application/json
Authorization: Bearer <user_pat>
Content-Type: application/json

{
  "label": "Merchant backend replacement",
  "reason": "Scheduled merchant credential rotation",
  "current_password": "<administrator_password>",
  "overlap_minutes": 60
}

Response: 201.

{
  "success": true,
  "message": "Replacement issued. Complete cutover before the rotation deadline.",
  "data": {
    "client": {
      "id": 13,
      "oauth_client_id": 32,
      "label": "Merchant backend replacement",
      "is_active": true,
      "created_at": "2026-10-03T09:00:00+08:00",
      "last_used_at": null,
      "revoked_at": null,
      "rotation_deadline_at": null,
      "replaces_merchant_oauth_client_id": 12
    },
    "delivery": {
      "id": "00000000-0000-4000-8000-000000000002",
      "expires_at": "2026-10-03T09:05:00+08:00"
    }
  }
}

The replacement points to ownership ID 12. Read the client list to see the old client's rotation_deadline_at; the replacement's deadline is null. The following revocation ends the old client's overlap early.

POST /portal/merchant/machine-clients/12/revoke

Request:

POST /api/v1/portal/merchant/machine-clients/12/revoke
Accept: application/json
Authorization: Bearer <user_pat>
Content-Type: application/json

{
  "reason": "Cutover to replacement is complete",
  "current_password": "<administrator_password>"
}

Response: 200.

{
  "success": true,
  "message": "Machine client and its tokens were revoked.",
  "data": {
    "id": 12,
    "oauth_client_id": 31,
    "label": "Merchant backend primary",
    "is_active": false,
    "created_at": "2026-10-01T09:00:00Z",
    "last_used_at": null,
    "revoked_at": "2026-10-03T09:03:00+08:00",
    "rotation_deadline_at": null,
    "replaces_merchant_oauth_client_id": null
  }
}

POST /portal/merchant/credential-deliveries/00000000-0000-4000-8000-000000000002/consume

Request:

POST /api/v1/portal/merchant/credential-deliveries/00000000-0000-4000-8000-000000000002/consume
Accept: application/json
Authorization: Bearer <user_pat>
Content-Type: application/json

{
  "current_password": "<administrator_password>"
}

Response: 200.

{
  "success": true,
  "message": "Success",
  "data": {
    "oauth_client_id": 32,
    "oauth_client_secret": "<one-use-secret>",
    "grant_type": "client_credentials",
    "token_url": "https://checkout.example.com/oauth/token"
  }
}

GET /portal/merchant/integration-keys

Request (no body):

GET /api/v1/portal/merchant/integration-keys
Accept: application/json
Authorization: Bearer <user_pat>

Response: 200.

{
  "success": true,
  "message": "Success",
  "data": {
    "has_authentication_token": false,
    "has_secret_key": false,
    "updated_at": null
  }
}

PUT /portal/merchant/integration-keys/authentication_token

Request:

PUT /api/v1/portal/merchant/integration-keys/authentication_token
Accept: application/json
Authorization: Bearer <user_pat>
Content-Type: application/json

{
  "value": "<merchant_integration_key>",
  "current_password": "<administrator_password>",
  "reason": "Replace merchant integration key"
}

Response: 200.

{
  "success": true,
  "message": "Integration key updated.",
  "data": {
    "has_authentication_token": true,
    "has_secret_key": false,
    "updated_at": "2026-10-03T09:00:00+08:00"
  }
}

DELETE /portal/merchant/integration-keys/authentication_token

Request:

DELETE /api/v1/portal/merchant/integration-keys/authentication_token
Accept: application/json
Authorization: Bearer <user_pat>
Content-Type: application/json

{
  "current_password": "<administrator_password>",
  "reason": "Remove unused merchant integration key"
}

Response: 204 No Content — empty body; do not parse JSON.

HTTP/1.1 204 No Content

PUT /portal/merchant/integration-keys/secret_key

Request:

PUT /api/v1/portal/merchant/integration-keys/secret_key
Accept: application/json
Authorization: Bearer <user_pat>
Content-Type: application/json

{
  "value": "<merchant_integration_key>",
  "current_password": "<administrator_password>",
  "reason": "Replace merchant integration key"
}

Response: 200.

{
  "success": true,
  "message": "Integration key updated.",
  "data": {
    "has_authentication_token": false,
    "has_secret_key": true,
    "updated_at": "2026-10-03T09:00:00+08:00"
  }
}

DELETE /portal/merchant/integration-keys/secret_key

Request:

DELETE /api/v1/portal/merchant/integration-keys/secret_key
Accept: application/json
Authorization: Bearer <user_pat>
Content-Type: application/json

{
  "current_password": "<administrator_password>",
  "reason": "Remove unused merchant integration key"
}

Response: 204 No Content — empty body; do not parse JSON.

HTTP/1.1 204 No Content

Each integration-key PUT example assumes the other key is absent. DELETE accepts a JSON body, unlike logo and team deletion.

DLT Developer Documentation · Updated October 2, 2026