---
title: "Error Codes"
description: "API error responses and how to handle them"
---

# Error Codes

When a request fails, the Octopost API returns a JSON error response with a consistent structure.

## Error Response Format

```json
{
  "error": "Human-readable error message",
  "code": "machine_readable_code",
  "details": {}
}
```

| Field | Type | Description |
|-------|------|-------------|
| `error` | string | A human-readable description of what went wrong. |
| `code` | string | A machine-readable error code for programmatic handling. |
| `details` | object \| undefined | Additional context about the error. Present on validation errors and some other error types. |

## Error Codes Reference

### 400 Bad Request

```json
{
  "error": "Request body must be valid JSON",
  "code": "bad_request"
}
```

The request was malformed. Common causes:
- Invalid JSON in the request body.
- Missing `Content-Type: application/json` header.
- Unsupported HTTP method for the endpoint.

---

### 401 Unauthorized

```json
{
  "error": "Invalid or expired API key",
  "code": "unauthorized"
}
```

Authentication failed. Common causes:
- Missing `Authorization` header.
- Malformed header (must be `Bearer <key>`).
- API key has been revoked.
- Using a test key against a live-only endpoint.

---

### 403 Forbidden

```json
{
  "error": "API key does not have permission for this action",
  "code": "forbidden"
}
```

The API key is valid but lacks the required permission. Common causes:
- Key is scoped to `posts:read` but the request is a write operation.
- Attempting to modify a system preset.
- Attempting to access another user's resource.

---

### 404 Not Found

```json
{
  "error": "Post not found",
  "code": "not_found"
}
```

The requested resource does not exist. Common causes:
- Invalid resource ID.
- Resource was deleted.
- Resource belongs to a different user.

---

### 409 Conflict

```json
{
  "error": "Post has already been published and cannot be modified",
  "code": "conflict"
}
```

The request conflicts with the current state of the resource. Common causes:
- Attempting to update a published post.
- Attempting to publish a post that is already publishing.
- Attempting to set a default preset when the operation would create an invalid state.

---

### 422 Validation Error

```json
{
  "error": "Validation failed",
  "code": "validation_error",
  "details": {
    "fields": {
      "content": "Content is required",
      "platforms": "At least one platform must be specified"
    }
  }
}
```

The request body is valid JSON but contains invalid data. The `details.fields` object maps field names to their specific validation errors.

Common causes:
- Missing required fields.
- Content exceeds a platform's character limit.
- Invalid platform identifier.
- `scheduled_for` is in the past.
- `primary_platform` is not included in `platforms` array.
- No connected account found for a specified platform.

---

### 429 Rate Limited

```json
{
  "error": "Rate limit exceeded. Try again in 45 seconds.",
  "code": "rate_limited",
  "details": {
    "limit": 300,
    "remaining": 0,
    "reset_at": "2026-04-03T12:01:00Z",
    "retry_after": 45
  }
}
```

Too many requests. See the [Rate Limits guide](/docs/api/rate-limits) for backoff strategies.

---

### 500 Server Error

```json
{
  "error": "An unexpected error occurred. Please try again later.",
  "code": "server_error"
}
```

Something went wrong on our end. These errors are automatically logged and investigated. If the problem persists, contact support.

---

## Retryable vs Non-Retryable Errors

When building resilient integrations, distinguish between errors that are safe to retry and those that require user intervention.

### Retryable

These errors are transient. Retry after a delay.

| Code | HTTP Status | Retry Strategy |
|------|-------------|----------------|
| `rate_limited` | 429 | Wait for `Retry-After` header or `details.retry_after` seconds. |
| `server_error` | 500 | Retry with exponential backoff (1s, 2s, 4s, 8s). Maximum 5 retries. |
| - | 502 | Retry with exponential backoff. |
| - | 503 | Retry with exponential backoff. |
| - | 504 | Retry with exponential backoff. |

### Non-Retryable

These errors require a fix before retrying.

| Code | HTTP Status | Action Required |
|------|-------------|-----------------|
| `bad_request` | 400 | Fix the request format or parameters. |
| `unauthorized` | 401 | Check or regenerate the API key. |
| `forbidden` | 403 | Use a key with the correct permissions. |
| `not_found` | 404 | Verify the resource ID exists. |
| `conflict` | 409 | Check the resource's current state before retrying the operation. |
| `validation_error` | 422 | Fix the request body based on `details.fields`. |

## Example: Error Handling in TypeScript

```typescript
interface OctopostError {
  error: string;
  code: string;
  details?: Record<string, unknown>;
}

async function createPost(content: string, platforms: string[]) {
  const response = await fetch("https://api.octopost.ink/v1/posts", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.OCTOPOST_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ content, platforms }),
  });

  if (!response.ok) {
    const error: OctopostError = await response.json();

    switch (error.code) {
      case "unauthorized":
        throw new Error("API key is invalid. Check your configuration.");
      case "validation_error":
        console.error("Validation errors:", error.details);
        throw new Error(`Validation failed: ${error.error}`);
      case "rate_limited":
        const retryAfter = (error.details as any)?.retry_after ?? 60;
        console.log(`Rate limited. Retrying in ${retryAfter}s...`);
        await new Promise((r) => setTimeout(r, retryAfter * 1000));
        return createPost(content, platforms); // retry
      case "server_error":
        throw new Error("Octopost server error. Try again later.");
      default:
        throw new Error(`API error: ${error.error}`);
    }
  }

  return response.json();
}
```
