← All posts
Tutorial

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 204 deletes.
  • 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:

ValueWhere it comes fromWhat to use it for
Your X-Request-IdRequest header supplied by your clientFind the HTTP attempt in your own logs
client_request_idJSON response echo of your supplied valueReconcile the response with that client attempt
request_idServer JSON responseGive support the server-side request reference
Response X-Request-IdServer response headerCapture tracing even without a response body
data.message_idA successful send response, when returnedIdentify 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 resultEvidence to retainNext decision
JSON success or errorStatus, server ID, client echo, error codes when presentInterpret the endpoint’s result
204 with no bodyStatus and response X-Request-IdDo not report JSON parsing as API failure
HTTP response with unreadable or unexpected bodyStatus, header ID if available, local attempt IDInvestigate the response boundary
No HTTP responseLocal attempt ID, start time, operationKeep 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:

UnifyPort API

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.