Errors & blockers

Typed error codes for a social media API

Why a structured, typed error envelope beats parsing free-text error messages, and what PostGate's error contract actually looks like.

Quick answer

Never branch on a provider's error message text — wording gets reworded, localized, or replaced without notice. Branch on a stable, machine-readable code instead. Every PostGate error returns one envelope with code, message, platform, retryable, details, and request_id, so error handling becomes an exhaustive map from code to handler rather than a chain of substring checks.

"Just check if the error message contains 'token expired'" is the kind of code that works in a demo and quietly rots in production. Provider error strings are not a contract — they get reworded, localized, or replaced with a new subcode, and every one of those changes silently breaks a substring match without ever throwing an exception you would notice. A typed, structured error code is a contract; free text never was one.

PostGate handles OAuth, publishing and delivery evidence for every network through one API. Start free or read the docs.

01

Why is matching on error message text a trap?

Matching on message text couples your error handling to wording nobody promised to keep stable. A provider can change "The access token could not be decrypted" to "Invalid OAuth access token - Cannot parse access token," ship the same underlying failure with a different subcode, or return the message in the caller's locale — and each of those is a legitimate change from the provider's point of view. From yours, it is a silent regression: the branch that used to catch this case stops firing, the error falls through to a generic handler, and nobody finds out until a customer notices their posts have stopped going out.

Worse, substring matching can also match too eagerly — a rate-limit message and a genuine revocation message can both happen to contain the word "access," and a broad match written to catch one can accidentally catch the other, routing a transient throttle into a permanent-failure code path.

02

What does a typed code actually buy you?

A stable, enumerable identifier is something you can switch on exhaustively, unit-test against literal values, and log as structured data instead of a free-text blob you have to grep. It separates the machine-actionable signal (the code) from the human-readable explanation (the message), so you can localize or improve the message for your own users without touching a single line of error-handling logic — the code does not change even if the wording around it does.

03

PostGate's error contract, field by field

Every PostGate API error returns one error object with the same five fields, regardless of which platform or endpoint produced it:

  • code — a stable, machine-readable identifier such as media_invalid or profile_disconnected. This is the value your code should branch on.
  • message — a human-readable explanation, safe to show in logs or, with your own wording layered on top, to a user. Never branch on this field.
  • platform — which provider produced the failure, when the error is platform-specific.
  • retryable — a boolean your retry logic can trust without inspecting anything else.
  • details — optional structured context specific to the error.
  • request_id — the value to hand to support, also sent as the X-Request-Id response header.
Example errorPostGate API
{
  "error": {
    "code": "platform_transient",
    "message": "Instagram is temporarily unavailable.",
    "platform": "instagram",
    "retryable": true,
    "details": {},
    "request_id": "req_01JXYZ"
  }
}
04

Build one map from code to handler, not a chain of string checks

With a stable code, error handling becomes a lookup instead of a cascade of conditionals. A map from code to handling behavior is exhaustive, reviewable in one place, and safe to extend — adding a new platform never requires touching the handlers for existing codes, because the codes it returns (media_invalid, profile_disconnected, platform_not_configured, platform_content_rejected, rate_limited, platform_transient) are the same finite set regardless of provider.

This also makes the unknown case explicit: a code you have not mapped yet should fall into a clearly labeled default branch, not silently match whatever if happened to be checked last.

05

A second worked example: the idempotency conflict

Reusing an Idempotency-Key with different content — a different body, profiles, or media than the original request — returns the same envelope shape with a conflict code and retryable: false, rather than silently applying the new content over what a customer already saw published:

Key reused with different contentPostGate API
{
  "error": {
    "code": "conflict",
    "message": "This idempotency key was already used with a different request.",
    "retryable": false,
    "request_id": "req_01JXYZ"
  }
}
06

Why keep request_id when the code is enough to act on?

A typed code tells your code what to do; request_id (and the matching X-Request-Id header) is what lets a human trace that specific failure later. Store it with every failed call so a support conversation can correlate one API request with the delivery evidence behind it, instead of re-describing the symptom from memory.

Frequently asked questions

Why shouldn't I match on an API error message string?

Because error messages are not a contract. Providers reword them, localize them into the caller’s locale, or replace them with new subcodes, and each change silently breaks a substring match without throwing anything you would notice. Substring matches can also match too eagerly — a rate-limit message and a revocation message can both contain the word "access," routing a transient throttle into a permanent-failure code path.

What fields does a PostGate API error return?

Every PostGate API error returns one error object with the same fields regardless of platform or endpoint: code (a stable machine-readable identifier to branch on), message (human-readable, never branch on it), platform (which provider produced the failure, when platform-specific), retryable (a boolean your retry logic can trust), details (optional structured context), and request_id (also sent as the X-Request-Id header).

How do I know if a social media API error is safe to retry?

Check the retryable boolean on the error object — it is designed to be trusted without inspecting anything else. For example, platform_transient comes back with retryable: true, while a conflict from reusing an Idempotency-Key with different content comes back with retryable: false, because retrying the same mismatched request can never succeed.

What error codes does PostGate return across platforms?

The same finite set regardless of provider: media_invalid, profile_disconnected, platform_not_configured, platform_content_rejected, rate_limited, and platform_transient, plus codes like conflict for idempotency violations. Because the set does not grow per platform, you can build one exhaustive map from code to handler, with unmapped codes falling into a clearly labeled default branch.

What happens if I reuse an Idempotency-Key with different content?

PostGate returns the same error envelope shape with code conflict and retryable: false, rather than silently applying the new content over what a customer already saw published. The message states the idempotency key was already used with a different request, and the request_id lets you trace that specific failure with support.

Build it with PostGate

One API for every social platform your customers care about — sandboxed, documented, and ready today.

Start building Read the docs