Module adcp.decisioning.errors

Typed exception subclasses for the AdCP error code vocabulary.

:class:AdcpError is the wire-shaped structured error adopters raise from inside Protocol method bodies — the framework catches at the dispatch seam and projects to the adcp_error envelope. The base class accepts any spec code as a string, but adopters who want catch-by-type ergonomics (except PermissionDeniedError:) or who want the correct recovery semantic auto-applied repeatedly re-derive the boilerplate. These subclasses bind each spec code to its canonical recovery classification (per schemas/cache/enums/error-code.json#enumMetadata) and offer a sensible default message adopters can override.

Recovery values are normative — they MUST match the enumMetadata block in the error-code schema. Adopters MUST NOT override recovery on these subclasses; if a different recovery is needed for a vendor variant, raise the base :class:AdcpError directly.

Classes

class AccountNotFoundError (*, message: str | None = None, field: str | None = None, **details: Any)
Expand source code
class AccountNotFoundError(AdcpError):
    """Spec ``ACCOUNT_NOT_FOUND`` (``recovery='terminal'``).

    Raised when the account reference cannot be resolved. The buyer
    verifies the account via ``list_accounts`` or contacts the seller.
    """

    def __init__(
        self,
        *,
        message: str | None = None,
        field: str | None = None,
        **details: Any,
    ) -> None:
        super().__init__(
            "ACCOUNT_NOT_FOUND",
            message=message or "Account not found.",
            recovery="terminal",
            field=field,
            details=dict(details) or None,
        )

Spec ACCOUNT_NOT_FOUND (recovery='terminal').

Raised when the account reference cannot be resolved. The buyer verifies the account via list_accounts or contacts the seller.

Ancestors

  • AdcpError
  • builtins.Exception
  • builtins.BaseException

Inherited members

class AuthRequiredError (*,
message: str | None = None,
field: str | None = None,
suggestion: str | None = None,
**details: Any)
Expand source code
class AuthRequiredError(AdcpError):
    """Spec ``AUTH_REQUIRED`` (``recovery='correctable'``).

    Raised when no credentials were presented. (The schema classifies
    this as ``correctable`` — the buyer fixes by attaching credentials
    and retrying.)
    """

    def __init__(
        self,
        *,
        message: str | None = None,
        field: str | None = None,
        suggestion: str | None = None,
        **details: Any,
    ) -> None:
        super().__init__(
            "AUTH_REQUIRED",
            message=message or "Authentication is required to access this resource.",
            recovery="correctable",
            field=field,
            suggestion=suggestion,
            details=dict(details) or None,
        )

Spec AUTH_REQUIRED (recovery='correctable').

Raised when no credentials were presented. (The schema classifies this as correctable — the buyer fixes by attaching credentials and retrying.)

Ancestors

  • AdcpError
  • builtins.Exception
  • builtins.BaseException

Inherited members

class BillingNotPermittedForAgentError (*,
rejected_billing: list[str],
suggested_billing: list[str] | None = None,
message: str | None = None)
Expand source code
class BillingNotPermittedForAgentError(AdcpError):
    """Spec ``BILLING_NOT_PERMITTED_FOR_AGENT`` (``recovery='correctable'``).

    Raised when the seller's ``supported_billing`` capability accepts
    the requested billing model, but the calling buyer agent's
    commercial relationship with the seller does not. The recovery
    shape is deliberately minimal — ``error.details`` MUST conform to
    ``error-details/billing-not-permitted-for-agent.json``
    (``rejected_billing`` plus an optional single ``suggested_billing``
    retry value, typically ``'operator'``).

    :param rejected_billing: The billing values the agent attempted that
        are not permitted (echoed in ``error.details.rejected_billing``).
    :param suggested_billing: Optional single retry value the seller
        recommends (echoed in ``error.details.suggested_billing``).
    """

    def __init__(
        self,
        *,
        rejected_billing: list[str],
        suggested_billing: list[str] | None = None,
        message: str | None = None,
    ) -> None:
        merged: dict[str, Any] = {"rejected_billing": list(rejected_billing)}
        if suggested_billing is not None:
            merged["suggested_billing"] = list(suggested_billing)
        super().__init__(
            "BILLING_NOT_PERMITTED_FOR_AGENT",
            message=(
                message or "Calling agent is not permitted to use the requested billing value."
            ),
            recovery="correctable",
            details=merged,
        )

Spec BILLING_NOT_PERMITTED_FOR_AGENT (recovery='correctable').

Raised when the seller's supported_billing capability accepts the requested billing model, but the calling buyer agent's commercial relationship with the seller does not. The recovery shape is deliberately minimal — error.details MUST conform to error-details/billing-not-permitted-for-agent.json (rejected_billing plus an optional single suggested_billing retry value, typically 'operator').

:param rejected_billing: The billing values the agent attempted that are not permitted (echoed in error.details.rejected_billing). :param suggested_billing: Optional single retry value the seller recommends (echoed in error.details.suggested_billing).

Ancestors

  • AdcpError
  • builtins.Exception
  • builtins.BaseException

Inherited members

class MediaBuyNotFoundError (*,
media_buy_id: str | None = None,
message: str | None = None,
field: str | None = None,
**details: Any)
Expand source code
class MediaBuyNotFoundError(AdcpError):
    """Spec ``MEDIA_BUY_NOT_FOUND`` (``recovery='correctable'``).

    Raised when the referenced media buy does not exist or is not
    accessible to the requesting agent. Sellers MUST return this code
    uniformly for any ``media_buy_id`` not owned by the calling
    account — never distinguish "exists in another tenant" from
    "does not exist".
    """

    def __init__(
        self,
        *,
        media_buy_id: str | None = None,
        message: str | None = None,
        field: str | None = None,
        **details: Any,
    ) -> None:
        merged: dict[str, Any] = dict(details)
        if media_buy_id is not None:
            merged["media_buy_id"] = media_buy_id
        super().__init__(
            "MEDIA_BUY_NOT_FOUND",
            message=message or "Media buy not found.",
            recovery="correctable",
            field=field,
            details=merged or None,
        )

Spec MEDIA_BUY_NOT_FOUND (recovery='correctable').

Raised when the referenced media buy does not exist or is not accessible to the requesting agent. Sellers MUST return this code uniformly for any media_buy_id not owned by the calling account — never distinguish "exists in another tenant" from "does not exist".

Ancestors

  • AdcpError
  • builtins.Exception
  • builtins.BaseException

Inherited members

class PermissionDeniedError (*,
scope: "Literal['agent', 'billing'] | None" = None,
reason: str | None = None,
message: str | None = None,
field: str | None = None,
suggestion: str | None = None,
**details: Any)
Expand source code
class PermissionDeniedError(AdcpError):
    """Spec ``PERMISSION_DENIED`` (``recovery='correctable'``).

    Raised when the authenticated caller is not authorized for the
    requested action under the seller's policies, or a required signed
    credential is missing/invalid.

    For per-agent commercial-status rejections (suspended / blocked),
    raise :class:`AdcpError` with code ``"AGENT_SUSPENDED"`` /
    ``"AGENT_BLOCKED"`` directly — those are dedicated AdCP 3.1 codes
    with their own ``recovery="terminal"`` semantics, not flavors of
    ``PERMISSION_DENIED``.

    :param scope: When the gate is a per-agent non-status provisioning
        constraint, set to ``'agent'`` (with ``reason``); when
        billing-relationship, set to ``'billing'``. Sellers MUST emit
        ``scope='agent'`` only when buyer-agent identity has been
        established (signed-request derivation or credential-to-agent
        mapping); otherwise omit.
    :param reason: When ``scope='agent'``, the registered provisioning
        gate that fired (``'sandbox_only'``, etc.) — see
        ``error-details/agent-permission-denied.json``.
    :param message: Optional human-readable override of the default.
    :param details: Additional fields merged into ``error.details``.
    """

    def __init__(
        self,
        *,
        scope: Literal["agent", "billing"] | None = None,
        reason: str | None = None,
        message: str | None = None,
        field: str | None = None,
        suggestion: str | None = None,
        **details: Any,
    ) -> None:
        # Hard-reject the AdCP 3.0.5 placeholder ``status=`` kwarg.
        # Without this, the kwarg would land in ``**details`` and emit
        # ``error.details.status`` — a field 3.1 removed from
        # ``agent-permission-denied.json``, which seller-side
        # schema-validating receivers would reject. For per-agent
        # commercial status, raise ``AdcpError`` with code
        # ``"AGENT_SUSPENDED"`` / ``"AGENT_BLOCKED"`` directly.
        if "status" in details:
            raise TypeError(
                "PermissionDeniedError.status= was removed in AdCP 3.1 "
                "(spec PR adcontextprotocol/adcp#3906). The "
                "placeholder details.status field is gone from "
                "error-details/agent-permission-denied.json. Migrate to: "
                "raise AdcpError('AGENT_SUSPENDED', recovery='terminal') "
                "or AdcpError('AGENT_BLOCKED', recovery='terminal') for "
                "per-agent commercial-status rejections, or pass "
                "reason='sandbox_only' for the remaining provisioning "
                "gate."
            )

        merged_details: dict[str, Any] = dict(details)
        if scope is not None:
            merged_details["scope"] = scope
        if reason is not None:
            merged_details["reason"] = reason

        super().__init__(
            "PERMISSION_DENIED",
            message=message or "Caller is not authorized for the requested action.",
            recovery="correctable",
            field=field,
            suggestion=suggestion,
            details=merged_details or None,
        )

Spec PERMISSION_DENIED (recovery='correctable').

Raised when the authenticated caller is not authorized for the requested action under the seller's policies, or a required signed credential is missing/invalid.

For per-agent commercial-status rejections (suspended / blocked), raise :class:AdcpError with code "AGENT_SUSPENDED" / "AGENT_BLOCKED" directly — those are dedicated AdCP 3.1 codes with their own recovery="terminal" semantics, not flavors of PERMISSION_DENIED.

:param scope: When the gate is a per-agent non-status provisioning constraint, set to 'agent' (with reason); when billing-relationship, set to 'billing'. Sellers MUST emit scope='agent' only when buyer-agent identity has been established (signed-request derivation or credential-to-agent mapping); otherwise omit. :param reason: When scope='agent', the registered provisioning gate that fired ('sandbox_only', etc.) — see error-details/agent-permission-denied.json. :param message: Optional human-readable override of the default. :param details: Additional fields merged into error.details.

Ancestors

  • AdcpError
  • builtins.Exception
  • builtins.BaseException

Inherited members

class RateLimitedError (*, message: str | None = None, retry_after: int | None = None, **details: Any)
Expand source code
class RateLimitedError(AdcpError):
    """Spec ``RATE_LIMITED`` (``recovery='transient'``).

    Raised when the request rate exceeds the seller's threshold. The
    buyer retries after the ``retry_after`` interval.
    """

    def __init__(
        self,
        *,
        message: str | None = None,
        retry_after: int | None = None,
        **details: Any,
    ) -> None:
        super().__init__(
            "RATE_LIMITED",
            message=message or "Request rate exceeded.",
            recovery="transient",
            retry_after=retry_after,
            details=dict(details) or None,
        )

Spec RATE_LIMITED (recovery='transient').

Raised when the request rate exceeds the seller's threshold. The buyer retries after the retry_after interval.

Ancestors

  • AdcpError
  • builtins.Exception
  • builtins.BaseException

Inherited members

class ServiceUnavailableError (*, message: str | None = None, retry_after: int | None = None, **details: Any)
Expand source code
class ServiceUnavailableError(AdcpError):
    """Spec ``SERVICE_UNAVAILABLE`` (``recovery='transient'``).

    Raised when the seller service is temporarily unavailable. The
    buyer retries with exponential backoff; ``retry_after`` MAY be set
    to hint a minimum delay.
    """

    def __init__(
        self,
        *,
        message: str | None = None,
        retry_after: int | None = None,
        **details: Any,
    ) -> None:
        super().__init__(
            "SERVICE_UNAVAILABLE",
            message=message or "Service is temporarily unavailable.",
            recovery="transient",
            retry_after=retry_after,
            details=dict(details) or None,
        )

Spec SERVICE_UNAVAILABLE (recovery='transient').

Raised when the seller service is temporarily unavailable. The buyer retries with exponential backoff; retry_after MAY be set to hint a minimum delay.

Ancestors

  • AdcpError
  • builtins.Exception
  • builtins.BaseException

Inherited members

class UnsupportedFeatureError (*, message: str | None = None, field: str | None = None, **details: Any)
Expand source code
class UnsupportedFeatureError(AdcpError):
    """Spec ``UNSUPPORTED_FEATURE`` (``recovery='correctable'``).

    Raised when a requested feature or field is not supported by this
    seller. The buyer checks ``get_adcp_capabilities`` and removes the
    unsupported field.
    """

    def __init__(
        self,
        *,
        message: str | None = None,
        field: str | None = None,
        **details: Any,
    ) -> None:
        super().__init__(
            "UNSUPPORTED_FEATURE",
            message=message or "Requested feature is not supported by this seller.",
            recovery="correctable",
            field=field,
            details=dict(details) or None,
        )

Spec UNSUPPORTED_FEATURE (recovery='correctable').

Raised when a requested feature or field is not supported by this seller. The buyer checks get_adcp_capabilities and removes the unsupported field.

Ancestors

  • AdcpError
  • builtins.Exception
  • builtins.BaseException

Inherited members

class ValidationError (*,
message: str | None = None,
field: str | None = None,
suggestion: str | None = None,
**details: Any)
Expand source code
class ValidationError(AdcpError):
    """Spec ``VALIDATION_ERROR`` (``recovery='correctable'``).

    Raised when a request contains invalid field values or violates
    business rules beyond schema validation. ``field`` SHOULD identify
    the offending path so buyers can highlight the input.
    """

    def __init__(
        self,
        *,
        message: str | None = None,
        field: str | None = None,
        suggestion: str | None = None,
        **details: Any,
    ) -> None:
        super().__init__(
            "VALIDATION_ERROR",
            message=message or "Request failed validation.",
            recovery="correctable",
            field=field,
            suggestion=suggestion,
            details=dict(details) or None,
        )

Spec VALIDATION_ERROR (recovery='correctable').

Raised when a request contains invalid field values or violates business rules beyond schema validation. field SHOULD identify the offending path so buyers can highlight the input.

Ancestors

  • AdcpError
  • builtins.Exception
  • builtins.BaseException

Inherited members