Trace UnifyPort API Errors With request_id and X-Request-Id
To trace a UnifyPort API failure, save the server’s request_id from the JSON response or its X-Request-Id response header. Send your own X-Request-Id request header to connect the response to your application; JSON responses echo that value as client_request_id. These identifiers help investigate a request. They are not message IDs, webhook event IDs, or a promise that repeating a request is safe.
Key takeaways
- Keep your application operation, each HTTP attempt, and the server request ID distinct.
- Read response headers even when there is no JSON body, including successful
204deletes. - A timeout can leave the outcome unknown and provide no server request ID.
- Record structured error codes, not credentials or entire message payloads.
Which request ID should you save?
The API introduction defines the tracing contract. The same header name has different roles depending on direction:
| Value | Where it comes from | What to use it for |
|---|---|---|
Your X-Request-Id | Request header supplied by your client | Find the HTTP attempt in your own logs |
client_request_id | JSON response echo of your supplied value | Reconcile the response with that client attempt |
request_id | Server JSON response | Give support the server-side request reference |
Response X-Request-Id | Server response header | Capture tracing even without a response body |
data.message_id | A successful send response, when returned | Identify the message, not the HTTP request |
The June API update introduced the tracing fields. Here the implementation concern is preserving them through every result path—not treating the feature announcement as a complete logging design.
Use an application-owned operation identifier to group work such as one approved reply. Give each HTTP attempt its own local identifier and store the relationship. Those are recommended local conventions, not extra UnifyPort request fields. Do not put customer names, phone numbers, message text, or credentials in identifiers.
Record one request without adding an automatic retry
Start with a read-only call to the current workspace endpoint. The following Node.js example uses built-in fetch and reads a backend-held UNIFYPORT_API_KEY. It logs an allowlisted diagnostic summary, not the request headers or response body.
The timeout is an illustrative client policy, not a service limit. The helper performs exactly one application-level attempt and returns the original Response for the caller to handle.
import { randomUUID } from 'node:crypto';
async function tracedWorkspaceRead(apiKey, operationId) {
const clientAttemptId = randomUUID();
const startedAt = new Date().toISOString();
const startedMs = Date.now();
let response;
try {
response = await fetch('https://api.unifyport.ai/v1/workspace', {
headers: {
'X-Api-Key': apiKey,
'X-Request-Id': clientAttemptId
},
signal: AbortSignal.timeout(10000)
});
} catch {
console.info({
operation_id: operationId,
client_attempt_id: clientAttemptId,
started_at: startedAt,
elapsed_ms: Date.now() - startedMs,
outcome: 'no_http_response'
});
throw new Error('No HTTP response; inspect the local attempt record');
}
const headerId = response.headers.get('X-Request-Id');
const body = response.status === 204
? null
: await response.clone().json().catch(() => null);
console.info({
operation_id: operationId,
client_attempt_id: clientAttemptId,
started_at: startedAt,
elapsed_ms: Date.now() - startedMs,
http_status: response.status,
server_request_id_header: headerId,
server_request_id_body: body?.request_id ?? null,
echoed_client_request_id: body?.client_request_id ?? null,
error_code: body?.error?.code ?? null,
numeric_code: body?.error?.numeric_code ?? null
});
return response;
}
const apiKey = process.env.UNIFYPORT_API_KEY;
if (!apiKey) throw new Error('Configure UNIFYPORT_API_KEY');
await tracedWorkspaceRead(apiKey, randomUUID());
The logged names such as operation_id and outcome are local application fields, not an API response schema. The generic response-handling branch includes 204 so the pattern can be reused for documented no-content operations; the workspace read itself returns 200 on success. Do not delete an account merely to test logging—use a local mock for the no-content case.
Preserving both header and body values makes missing or inconsistent responses visible. A response from an intermediary may not follow the API envelope. Keep that HTTP status and your local attempt record rather than inventing a server ID. In production, bound parsing and log sizes, restrict log access, and apply a retention policy.
Turn the record into a useful failure report
The error reference documents error.code, error.numeric_code, and error.message. Branch on code or numeric_code, not the human-readable message. A numeric code can refine an upstream cause without changing the legacy code or HTTP status.
| Observed result | Evidence to retain | Next decision |
|---|---|---|
| JSON success or error | Status, server ID, client echo, error codes when present | Interpret the endpoint’s result |
204 with no body | Status and response X-Request-Id | Do not report JSON parsing as API failure |
| HTTP response with unreadable or unexpected body | Status, header ID if available, local attempt ID | Investigate the response boundary |
| No HTTP response | Local attempt ID, start time, operation | Keep the result unknown until reconciled |
A minimal support report should include the environment, UTC attempt time, HTTP method and route template, server request ID if received, local attempt ID, status and error codes, and a short description of expected versus observed behavior. Share account or conversation identifiers only through an appropriate restricted channel. Exclude API keys, session material, signing secrets, reply tokens, and media URLs containing access signatures.
The X Chat troubleshooting guide shows how these records help separate an account problem from a conversation-specific failure. A request ID locates evidence; it does not itself diagnose the cause.
Do not turn tracing into retry permission
Sending the same client request ID again is not a documented idempotency mechanism for POST /v1/messages. If a send times out, your client may lack a response even though the operation took effect. Record uncertainty before deciding whether another send is justified.
This differs from LINE’s explicit retry-key contract. LINE documents x-line-accepted-request-id in a duplicate-acceptance response. That contract must not be inferred from UnifyPort’s similarly named tracing header. The LINE retry-key guide covers that separate workflow.
Webhook correlation is separate too. The delivery reference defines X-Device-Event-Id and X-Device-Delivery-Id; the latter can fall back to the event ID and is not guaranteed unique per HTTP attempt. Do not join a REST request and an incoming event merely because both have an ID. If you need a unique receiver-attempt record, generate one locally. Follow event-specific deduplication rules, including the HistorySync exception, independently of logging.
FAQ
Why is request_id missing after a successful delete?
A successful 204 has no JSON body. Read the response X-Request-Id header instead.
Can I look up send status by request_id?
The tracing contract does not document a request-status lookup endpoint. Save the actual endpoint result and reconcile uncertain operations through supported evidence; do not construct a new URL from the ID.
Is client_request_id an idempotency key?
No documented guarantee says so. It echoes your request header for correlation, not duplicate-send prevention.
Next step and sources
Add the diagnostic summary to one existing API client and test JSON errors, no-content responses, malformed bodies, and network failures with a local mock. Use the error reference as the contract for your error branches.
Checked on 2026-10-01:
Turn messaging integration into a stable product pipeline.
Start by sending through one API, then bring every inbound message back into your business system with standard events.