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.
Quota-Related Errors (403)
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
/scalarin development) to test requests interactively