Skip to content

Authentication and capabilities

Capabilities are local, typed preflight checks. They improve error messages and prevent deterministic invalid requests; they do not replace Google's authorization decision.

Three capability sets

  • ResponseCapabilities: what this transport and concrete event can return.
  • OperationRegistry: which outbound operations this credential identity and known scopes may attempt through Bot.
  • PreviewCapabilities: which Developer Preview routes the application has explicitly enabled.

Scope-aware outbound preflight

For message creation, app auth accepts chat.bot; user auth accepts any of chat.messages.create, chat.messages, or chat.import. For updates, user auth accepts either chat.messages or chat.import.

The three media operations follow the same tri-state model:

Operation App auth User auth Admissible scopes
MEDIA_UPLOAD (media.upload) no yes user: chat.messages.create / chat.messages / chat.import
MEDIA_DOWNLOAD (media.download) yes yes app: chat.bot; user: chat.messages.readonly / chat.messages
ATTACHMENT_METADATA_GET (spaces.messages.attachments.get) yes no app: chat.bot

Memberships and Space listing are deliberately APP-only:

Operation App auth User auth Admissible scopes
MEMBERSHIPS_CREATE yes no app: chat.app.memberships + Workspace administrator approval
MEMBERSHIPS_GET yes no app: chat.bot / chat.app.memberships
MEMBERSHIPS_LIST yes no app: chat.bot / chat.app.memberships
MEMBERSHIPS_DELETE yes no app: chat.app.memberships + Workspace administrator approval
SPACES_LIST yes no app: chat.bot

The corresponding registry auth paths never include USER credentials. See Memberships and Spaces.

A dual-identity Bot resolves these against the identity the operation needs: bot.user.attachments.upload runs on the USER identity, and attachments=[InputFile(...)] sends use the USER identity for the whole operationmedia.upload AND the final messages.create run on the USER client (an APP-authenticated create cannot consume a USER-uploaded attachment; the cross-identity handoff is rejected by Google). bot.app.attachments.get_metadata uses the APP identity, attachments.download accepts either. See Files, Images & Media.

scopes=None means unknown, not empty. Chattice preserves the identity baseline and lets the server decide; it performs no hidden discovery or token introspection call. An explicit empty iterable means reliably known absence and fails locally.

from chattice.auth import AuthMode
from chattice.capabilities import REGISTRY, CapabilityNotSupported, Operation

try:
    REGISTRY.require(
        Operation.MESSAGES_CREATE,
        identity=AuthMode.USER,
        scopes={"https://www.googleapis.com/auth/chat.messages"},
    )
except CapabilityNotSupported:
    ...  # configuration cannot even attempt the call

Passing preflight is not an authorization guarantee. Google can still return 403 because of missing consent/admin approval, Space membership, resource role, policy, or resource state. Chattice surfaces this as ChatPermissionDeniedError.

401 vs 403 vs capability errors

Error Meaning First check
Incoming HTTP 401 request verification failed exact audience, bearer token, Google identity, clock/network
ChatUnauthenticatedError outbound credential was rejected key/token validity and refresh
CapabilityNotSupported configuration is deterministically unsupported auth mode, known scopes, transport/event, Preview opt-in
ChatPermissionDeniedError Google authenticated the call but denied it scope grant, app membership, role, admin approval, feature enrollment

User auth: REQUEST_CONFIG response

When a user-auth application receives an interaction without a stored OAuth token (or with an expired grant it cannot refresh), the documented Google answer is a synchronous REQUEST_CONFIG response carrying an authorization URL. The OAuth authorization flow itself belongs to the application.

from chattice.transports.http import RequestConfigResponse


@router.message()
async def needs_auth(message: MessageEvent) -> RequestConfigResponse:
    return RequestConfigResponse(
        auth_url="https://accounts.google.com/o/oauth2/v2/auth?..."
    )

Google drives the user through authUrl; the application then completes the code exchange, stores the resulting credentials, and returns them through a UserCredentialsProvider (see Files, Images & Media).

Preview opt-in

from chattice import Dispatcher
from chattice.capabilities import PreviewFeature

dispatcher = Dispatcher(
    preview_features={
        PreviewFeature.REPLACE_CARDS,
        PreviewFeature.PINNED_MESSAGES,
    }
)

Enrollment in Chattice is only an explicit application decision. The Google account must separately be eligible/enrolled and have the required identity, scopes, and resource permissions.

Next: Recipes.

Preview gating

Operations marked preview=True in the operation registry require an explicit per-Bot opt-in:

bot = Bot(..., enable_preview=True)

Without it the executor rejects the call with CapabilityNotSupported naming the operation. Stable operations are always available.