{
    "description": "Agent-oriented error reference for the Pharlo REST API (/api/*) and MCP server (/_mcp). Each entry tells an AI agent what the error means and what to do next. Codes are snake_case. See https://pharlo.io/docs/guides/error-handling.",
    "documentation": "https://pharlo.io/docs/guides/error-handling",
    "body_shapes": {
        "domain": {
            "applies_to": ["/api/*", "/_mcp"],
            "shape": "{ \"error\": \"<snake_case_code_or_human_message>\", \"message\": \"...\", ...extra }",
            "note": "Most domain errors put a human-readable message in `error` and carry no stable machine code. Only the codes listed under `errors` below are stable, machine-readable identifiers. Inspect the HTTP status (see `http_statuses`) when no code is present."
        },
        "http_validation": {
            "applies_to": ["/api/*"],
            "shape": "{ \"error\": { \"message\": \"Validation failed\", \"details\": { \"<field>\": [\"<message>\"] } } }",
            "note": "Request validation and other HTTP-layer errors nest the message (and, for validation, per-field `details`) under an `error` object. Unhandled server faults return `{ \"error\": { \"message\": \"Internal server error\" } }` with status 500."
        }
    },
    "errors": {
        "insufficient_credits": {
            "meaning": "The workspace does not have enough credits to perform the requested operation.",
            "http_status": 402,
            "extra_fields": ["credits_required", "credits_balance"],
            "response_headers": ["X-Credits-Required", "X-Credits-Balance"],
            "agent_action": "Do not retry. Tell the user they need to top up credits; report credits_required vs credits_balance. Retry only after the balance has increased.",
            "user_message": "You don't have enough credits for this action. Please top up your balance and try again.",
            "related_docs": ["https://pharlo.io/docs/guides/billing"],
            "related_tools": ["create_assignment", "retry_assignment", "get_channel_stats", "get_stats"]
        },
        "user_spending_limit_exceeded": {
            "meaning": "The operation would exceed the per-user spending limit configured for this member, even though the workspace may still have credits.",
            "http_status": 402,
            "extra_fields": ["credits_required", "credits_user_limit_remaining"],
            "response_headers": ["X-Credits-Required", "X-Credits-User-Limit-Remaining"],
            "agent_action": "Do not retry. Explain that the per-user spending limit was reached; an organization admin must raise the limit or another member must perform the action.",
            "user_message": "This action exceeds your spending limit. Ask an organization admin to raise it.",
            "related_docs": ["https://pharlo.io/docs/guides/billing"],
            "related_tools": ["create_assignment", "retry_assignment"]
        }
    },
    "http_statuses": {
        "400": {
            "meaning": "Bad request — domain validation failed (e.g. invalid email, weak password, invalid role, missing required field).",
            "agent_action": "Read `error` (or `error.details` for field-level validation). Fix the offending input and resubmit. Do not retry unchanged.",
            "related_docs": ["https://pharlo.io/docs/guides/error-handling"]
        },
        "401": {
            "meaning": "Unauthorized — missing, invalid, or expired credentials. On /api/* this is an API key (`ds_live_...`) problem; on /_mcp the OAuth 2.0 Bearer token is missing or invalid (a WWW-Authenticate header points at the OAuth metadata).",
            "agent_action": "Re-authenticate. For MCP, follow the WWW-Authenticate challenge to refresh the OAuth token; for REST, check the API key. Do not retry with the same credentials.",
            "related_docs": ["https://pharlo.io/docs/guides/authentication", "https://pharlo.io/docs/mcp/setup"]
        },
        "402": {
            "meaning": "Payment required — a billing limit was hit. See the `insufficient_credits` and `user_spending_limit_exceeded` codes for the specific cause.",
            "agent_action": "Inspect the `error` code and extra fields. Do not retry until credits or the spending limit allow it.",
            "related_docs": ["https://pharlo.io/docs/guides/billing"]
        },
        "403": {
            "meaning": "Forbidden — authenticated but not allowed (e.g. account blocked or deleted, invitation email mismatch, cannot remove the owner, invalid API key, incorrect current password context).",
            "agent_action": "Do not retry. Surface the `error` message to the user; the action is not permitted for this account/role.",
            "related_docs": ["https://pharlo.io/docs/guides/authentication"]
        },
        "404": {
            "meaning": "Not found — the referenced resource (assignment, connection, member, etc.) does not exist or is not visible to this workspace.",
            "agent_action": "Verify the ID. List the parent collection (e.g. list_assignments, list_connections) to find the correct ID before retrying.",
            "related_docs": ["https://pharlo.io/docs/guides/core-concepts"]
        },
        "409": {
            "meaning": "Conflict — the resource is in a state incompatible with the request (e.g. already a member, invitation already accepted, already in an organization, assignment already processing/published).",
            "agent_action": "Re-read the current state (e.g. get_assignment) and adjust. Do not blindly retry — the conflict will persist.",
            "related_docs": ["https://pharlo.io/docs/guides/core-concepts"]
        },
        "410": {
            "meaning": "Gone — the resource has expired and is no longer actionable (e.g. an expired organization invitation).",
            "agent_action": "Do not retry. Request a fresh resource (e.g. a new invitation or OAuth ticket).",
            "related_docs": ["https://pharlo.io/docs/guides/core-concepts"]
        },
        "422": {
            "meaning": "Unprocessable entity — the request is well-formed but semantically rejected (e.g. cannot modify the owner role, incorrect current password).",
            "agent_action": "Read the message, correct the semantic problem, and resubmit. Field-level details may appear under `error.details`.",
            "related_docs": ["https://pharlo.io/docs/guides/error-handling"]
        },
        "500": {
            "meaning": "Internal server error — an unexpected fault. Body is `{ \"error\": { \"message\": \"Internal server error\" } }`.",
            "agent_action": "Retry once with backoff. If it persists, stop and tell the user; do not loop.",
            "related_docs": ["https://pharlo.io/docs/guides/error-handling"]
        }
    },
    "planned": {
        "note": "These codes are NOT yet emitted by the API. They are reserved for capabilities the agent ecosystem commonly needs. Do not branch on them as if the API returns them today; check the live behavior (HTTP status + `error`) instead. They are tracked here so tooling can prepare for them.",
        "codes": {
            "connection_token_expired": "A connection's platform OAuth token has expired (today surfaced via get_connection `tokenStatus: expired`, not a distinct error code). Recover with get_oauth_connect_url.",
            "invalid_platform_payload": "The `payload` does not match the platform's capabilities (today surfaced as a generic validation message). Recover by calling get_platform_capabilities and rebuilding the payload.",
            "unsupported_platform": "The requested platform is not publishable (live platforms today: youtube, facebook).",
            "unsupported_content_type": "The content `type` is not supported for the target platform.",
            "invalid_media_url": "The supplied mediaUrl is not reachable or not a direct media file.",
            "upload_session_expired": "A browser upload session expired before completion (today surfaced via get_assignment_upload_session `status`).",
            "upload_failed": "A large-file upload failed.",
            "rate_limited": "The caller exceeded a request rate limit (no rate-limit error is emitted today).",
            "platform_quota_exceeded": "The platform's own API quota was exhausted.",
            "webhook_signature_invalid": "A webhook signature did not validate (signing exists, but no client-facing error code is defined)."
        }
    }
}
