---
title: "Agent Consent Flow | Gazebo Docs"
description: "Integrate the Agent Consent Flow hosted private beta with signed requests, S256 PKCE, scoped grants, token introspection, and immediate revocation."
url: "https://gazebohq.com/docs/consent"
---

Agent Consent Flow is a hosted private beta for signed, scoped authorization requests around independently conformable [AIP Consent](/spec/consent). It gives developers a real authorization flow without defining a new standard, changing AIP, or claiming OAuth compatibility.

The hosted service validates a developer's signed request, shows the requested scopes to an authenticated user, records the decision, and issues a short-lived opaque token through a server-side code exchange. The user's underlying service credentials are never returned by the Consent protocol.

## Availability

Agent Consent Flow is available to signed-in Gazebo users as a private beta in the **Agent Consent Flow** area of the app. The TypeScript SDK currently ships with the hosted beta source and is not yet published to a public package registry.

## Protocol flow

1. Generate an ES256 P-256 key pair. Keep the private JWK in your server-side secrets.
2. Register the public JWK, exact callback URI, and allowed scopes in Gazebo.
3. Generate a random callback state and an S256 PKCE verifier/challenge pair.
4. Sign a short-lived authorization request JWT with your private JWK.
5. Submit the JWT to `POST /api/consent/v1/authorization`.
6. Send the user to the returned transaction at `https://app.gazebohq.com/consent/authorize?transaction=...`.
7. Validate the state returned to your exact callback URI.
8. Exchange the one-time code and PKCE verifier at `POST /api/consent/v1/token`.
9. Introspect the opaque token before honoring its scopes.
10. Revoke the token or the complete user grant when access is no longer required.

## SDK example

```ts
import {
  createSignedAuthorizationRequest,
  exchangeAuthorizationCode,
  generateEs256KeyPair,
  generatePkce,
  generateState,
  validateCallbackState,
} from "@workspace/consent-sdk";

// Run once during setup. Register publicKeyJwk in Gazebo and keep
// privateKeyJwk only in your server-side secret store.
const { publicKeyJwk, privateKeyJwk } = generateEs256KeyPair();

// Run for each authorization attempt.
const pkce = generatePkce();
const state = generateState();
const request = createSignedAuthorizationRequest(
  {
    clientId: process.env.GAZEBO_CONSENT_CLIENT_ID!,
    audience: "gazebo-consent-v1",
    redirectUri: "https://agent.example.com/consent/callback",
    scopes: ["issues.read", "issues.create"],
    codeChallenge: pkce.challenge,
    state,
  },
  privateKeyJwk,
);

const authorization = await fetch(
  "https://gazebohq.com/api/consent/v1/authorization",
  {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ requestJwt: request.requestJwt }),
  },
).then((response) => response.json());

const consentUrl =
  `https://app.gazebohq.com/consent/authorize?transaction=${authorization.transactionId}`;

// At your registered callback:
if (!validateCallbackState(callbackState, state)) {
  throw new Error("Invalid callback state");
}

const token = await exchangeAuthorizationCode("https://gazebohq.com", {
  clientId: process.env.GAZEBO_CONSENT_CLIENT_ID!,
  code: callbackCode,
  redirectUri: "https://agent.example.com/consent/callback",
  codeVerifier: pkce.verifier,
});
```

Persist the state and PKCE verifier in a short-lived, server-side session tied to the browser that started the request. Never place the private JWK, verifier, authorization code, or access token in logs.

## Signed request requirements

Authorization request JWTs must:

- use `ES256` with a registered P-256 public JWK;
- use the registered client ID as `iss`;
- use `gazebo-consent-v1` as `aud`;
- expire within five minutes;
- contain a unique `jti`;
- use an exact registered HTTPS callback URI (`localhost` is allowed for local development);
- request only registered scopes;
- include an S256 PKCE challenge.

Wildcards, callback fragments, unsigned requests, alternate signing algorithms, plain PKCE, replayed request IDs, and scope expansion are rejected.

## Approval and denial

The user must have an authenticated Gazebo browser session to approve or deny a transaction. API keys, agent tokens, and Consent access tokens cannot make consent decisions.

Approval creates a scoped grant and a one-time authorization code in one database transaction. Denial creates no grant and returns `error=access_denied` with the original state to the registered callback.

## Token handling

- Authorization codes expire after one minute and can be consumed once.
- Access tokens are opaque and expire after one hour.
- Only SHA-256 hashes of codes and tokens are stored.
- Token introspection returns only active status, client ID, scopes, and expiry.
- Deactivating a client or revoking a grant immediately revokes its active access tokens.
- Expanding scope always requires a new signed request and user decision.

The developer's resource server remains responsible for mapping each scope to a real operation and denying any operation not covered by an active introspection result.

## Security boundary

Agent Consent Flow authorizes scopes; it does not send a user's underlying vault credentials to the client. A client cannot use a Consent token as a Gazebo account token, agent token, or MCP token, and Consent tokens are rejected outside the isolated Consent protocol/resource-server integration boundary.

The current private beta intentionally uses short-lived opaque tokens with database-backed revocation rather than self-contained bearer JWTs. This makes revocation immediate and keeps the authorization server authoritative.

## Relationship to AIP

[AIP Consent](/spec/consent) is normative and remains unchanged. Agent Consent Flow is one implementation around that independently conformable section. Other implementations can conform directly to AIP without using Agent Consent Flow.
