Error Handling & Retries

The Postnomic API uses standard HTTP conventions for error reporting. This guide covers the status codes you can expect, the two shapes an error body can take, and how to retry safely.

Overview

The Postnomic API uses standard HTTP conventions for error reporting. This guide covers the status codes you can expect, the two shapes an error body can take, and how to retry safely.

HTTP Status Codes

The API uses standard HTTP status codes to indicate the outcome of each request:

Success Codes

Code Meaning Usage
200 OK Request succeeded GET, PUT responses
201 Created Resource created POST responses for new entities
204 No Content Action completed DELETE responses

Client Error Codes

Code Meaning Common Cause
400 Bad Request Invalid request Missing required fields, malformed JSON, validation errors
401 Unauthorized Authentication failed Missing or invalid JWT/API key
403 Forbidden Permission denied Insufficient blog role, quota exceeded, feature not available on plan
404 Not Found Resource not found Invalid PublicId, slug, or blog reference
409 Conflict Conflict Duplicate slug, concurrent modification

Server Error Codes

Code Meaning Action
500 Internal Server Error Server failure Retry after a delay; report if persistent
503 Service Unavailable Service temporarily down Retry with exponential backoff

Error Body Shapes

Error responses come in two shapes, and it is worth handling both.

ProblemDetails (RFC 7807)

Framework-generated errors — model validation failures, unhandled exceptions, and any error raised through Problem(...) — use the ProblemDetails format. Postnomic adds requestId and traceId extensions to every one of them:

{
  "type": "https://tools.ietf.org/html/rfc7807",
  "title": "Forbidden",
  "status": 403,
  "detail": "Advanced analytics is not available on your current plan.",
  "instance": "GET /blogs/a1b2c3d4/analytics",
  "requestId": "0HN4ABCDEF123:00000001",
  "traceId": "00-abcdef1234567890abcdef1234567890-1234567890abcdef-01"
}

Key Fields

  • type — A URI reference identifying the error type
  • title — A short, human-readable summary of the problem
  • status — The HTTP status code
  • detail — A specific explanation of what went wrong and why
  • instance — The HTTP method and path of the failing request
  • requestId — A unique identifier for the request, useful for support inquiries
  • traceId — An OpenTelemetry trace ID for distributed tracing and debugging

Plain Text Messages

Many controller-level validation and quota checks return the message on its own, as a bare JSON string rather than a ProblemDetails object. For example, a comment rejected for a missing required field returns a 400 whose entire body is:

"Email is required."

Read the response body as a string when it does not parse as a ProblemDetails object.

When subscription quotas are exceeded, the API returns a 403 whose body is the reason string on its own — not a ProblemDetails object:

"Monthly post limit reached (5). Upgrade your plan to create more posts."

The exact strings are:

Quota Message
Blogs Blog limit reached ({n}). Upgrade your plan to create more blogs.
Posts per month Monthly post limit reached ({n}). Upgrade your plan to create more posts.
Users per blog User limit per blog reached ({n}). Upgrade your plan to add more users.
Storage Storage quota exceeded ({n} MB). Upgrade your plan for more storage.

These messages are always in English, regardless of the Accept-Language header.

Plan-gated features (advanced analytics, API keys, scheduling) are refused with a 403 as well, but those come back as ProblemDetails with the reason in detail.

To resolve quota errors, either delete existing resources or upgrade your subscription plan.

Rate Limiting

The API applies no general rate limit. There is no global limiter, no per-key or per-IP throttling on the content or public endpoints, and no X-RateLimit-* headers are emitted.

The only throttled endpoints are the two OAuth proxy routes used by the MCP connector's authorization flow, /oauth/register and /oauth/authorize. Each allows 10 requests per minute per IP and returns 429 Too Many Requests when that is exceeded. Ordinary API and SDK traffic never touches them.

Please still be considerate with request volume — use the Client SDK's built-in caching rather than polling.

Retry Strategies

Exponential Backoff

For transient errors (500, 503), implement exponential backoff:

int retryCount = 0;
int maxRetries = 3;

while (retryCount < maxRetries)
{
    var response = await httpClient.GetAsync(url);

    if (response.IsSuccessStatusCode)
        break;

    if (response.StatusCode >= HttpStatusCode.InternalServerError)
    {
        var delay = TimeSpan.FromSeconds(Math.Pow(2, retryCount));
        await Task.Delay(delay);
        retryCount++;
    }
    else
    {
        break; // Non-retryable error
    }
}

Do not retry 400, 401, 403, 404, or 409 — those indicate a problem with the request itself.

.NET Resilience

The Postnomic Client SDK uses .NET's built-in resilience features. If you are building a custom integration, consider using Microsoft.Extensions.Http.Resilience for automatic retry and circuit-breaker policies:

builder.Services.AddHttpClient("Postnomic")
    .AddStandardResilienceHandler();

Debugging Tips

  • Include the requestId and traceId from ProblemDetails responses when contacting support
  • Check the detail field — or, for the plain-string responses, the body itself — for specific guidance on resolving the error
  • Use the Scalar API docs (available at /scalar in development) to test requests interactively

Was this article helpful?

Thank you for your feedback!