> ## Documentation Index
> Fetch the complete documentation index at: https://docs.goosybear.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Managed capabilities for Pages

> Discover, configure, and call the AI and retrieval operations attached to a hosted Pages site.

Managed capabilities let a hosted Pages site use a small set of AI, retrieval,
and image operations without receiving a provider credential. You define an
account policy, optionally narrow it for a project, and grant an exact set of
operations to one site. Goosy stores the site's runtime credential and never
returns its secret in an API response.

<Note>
  This page documents the request contracts. Deployment and account exposure
  are separate, so an environment may refuse these tools while its rollout,
  migration, or exposure switch remains closed. Use the catalogue response as
  the source of truth for the operations available to your account.
</Note>

## Before you configure a site

Use an ordinary workspace-scoped API key whose holder currently has **Use the
API and MCP** and **Manage Pages**. Managed policies and grants can only narrow
that access. They cannot add a permission the key holder does not have.

```bash theme={null}
export GOOSY_ADMIN_API_KEY="your-admin-api-key"
export GOOSY_SITE_ID="your-site-id"
export GOOSY_SITE_BOARD_ID="the-site-board-id"
```

Every tool on this page uses the same endpoint shape:

```text theme={null}
POST https://app.goosybear.ai/api/v1/tools/{tool}
Authorization: Bearer <api-key>
Content-Type: application/json
```

## Discover the available operations

Call `pages.capabilities.catalogue` before creating or changing a policy. With
an empty body, it returns the managed operations and writing tiers installed in
the current environment.

```bash theme={null}
curl --request POST \
  --url "https://app.goosybear.ai/api/v1/tools/pages.capabilities.catalogue" \
  --header "Authorization: Bearer $GOOSY_ADMIN_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{}'
```

Each operation reports its `leverage`, whether it requires confirmation,
whether it supports a standing approval, the writing tiers it accepts, and its
exact `managed_key_input_schema`. That schema is generated from the same
validator used by the managed dispatcher. The catalogue exposes only
operations with an installed managed dispatcher; do not build an allowlist
from names that are absent from this response.

To inspect the effective policy for one site, include its target:

```json theme={null}
{
  "target": { "kind": "site", "site_id": "<site-id>" }
}
```

The response's `effective` object contains the active grant and the intersection
of its account policy, optional project policy, and site grant. This abridged
response omits the operation's machine-readable input schema:

```json theme={null}
{
  "ok": true,
  "data": {
    "writing_tiers": ["standard", "plus"],
    "operations": [
      {
        "key": "pages.ai.generate_text",
        "leverage": "high-leverage",
        "requires_confirmation": true,
        "supports_standing_approval": true,
        "writing_tiers": ["standard", "plus"]
      }
    ],
    "effective": {
      "grant_id": "<grant-id>",
      "account_policy_id": "<policy-id>",
      "project_policy_id": null,
      "operation_keys": ["pages.ai.generate_text"],
      "allowed_tiers": ["standard"],
      "default_tier": "standard",
      "limits": {
        "max_input_bytes": 100000,
        "max_output_tokens": 2000,
        "max_search_queries": 1,
        "max_search_results": 5,
        "max_tool_calls": 1,
        "max_concurrent_requests": 1
      },
      "standing_approval": null
    }
  }
}
```

The tier names in this example are illustrative catalogue values. Read
`writing_tiers` and each operation's `writing_tiers` rather than hard-coding a
tier list.

## Configure the policy and site grant

`pages.capabilities.configure` always uses an exact two-call confirmation. The
first call proposes one configuration and makes no change. The second call
must repeat that same `configuration` and add the returned `confirmation_id`.
Post each JSON example below with the ordinary admin key:

```bash theme={null}
curl --request POST \
  --url "https://app.goosybear.ai/api/v1/tools/pages.capabilities.configure" \
  --header "Authorization: Bearer $GOOSY_ADMIN_API_KEY" \
  --header "Content-Type: application/json" \
  --data @configuration.json
```

### 1. Create the account policy

Send a `policy` configuration. An account policy uses `null` for both
`workspace_id` and `project_board_id`.

```json theme={null}
{
  "configuration": {
    "kind": "policy",
    "policy": {
      "policy_id": null,
      "scope_kind": "account",
      "workspace_id": null,
      "project_board_id": null,
      "operation_keys": [
        "pages.ai.generate_text",
        "web.search",
        "web.read",
        "web.image_search",
        "images.acquire_remote",
        "images.generate",
        "images.iterate",
        "workflow.invoke",
        "workflow.status"
      ],
      "allowed_tiers": ["standard"],
      "default_tier": "standard",
      "limits": {
        "max_input_bytes": 100000,
        "max_output_tokens": 2000,
        "max_search_queries": 1,
        "max_search_results": 5,
        "max_tool_calls": 1,
        "max_concurrent_requests": 1
      },
      "enabled": true,
      "activate": true
    }
  }
}
```

The proposal returns `state: "confirmation_required"`, a `confirmation_id`,
and `expires_in_seconds`. Repeat the complete object and add that id at the top
level. A successful confirmation returns `state: "configured"` with
`policy_id`, `version_id`, `version`, `activated`, and `request_replayed`.

### 2. Optionally add a project policy

A project policy uses `scope_kind: "project"` and supplies its exact
`workspace_id` and `project_board_id`. It can narrow the account policy's
operations, tiers, or limits. Save its returned `policy_id` for the grant.

### 3. Grant the site a narrower capability set

Reference the active account policy and, when used, the active project policy.
`target_board_id` is the board on which the site's managed image operations may
act.

```json theme={null}
{
  "configuration": {
    "kind": "grant",
    "grant": {
      "account_policy_id": "<account-policy-id>",
      "project_policy_id": null,
      "previous_grant_id": null,
      "target": {
        "kind": "site",
        "site_id": "<site-id>",
        "target_board_id": "<site-board-id>"
      },
      "operation_keys": [
        "pages.ai.generate_text",
        "web.search",
        "web.read",
        "web.image_search",
        "images.acquire_remote",
        "images.generate",
        "images.iterate",
        "workflow.invoke",
        "workflow.status"
      ],
      "allowed_tiers": ["standard"],
      "default_tier": "standard",
      "limits": {
        "max_input_bytes": 100000,
        "max_output_tokens": 2000,
        "max_search_queries": 1,
        "max_search_results": 5,
        "max_tool_calls": 1,
        "max_concurrent_requests": 1
      }
    }
  }
}
```

Confirm the exact grant in a second call. A ready site enrollment returns
`grant_saved: true` and `runtime_credential: "ready"`; it does not return a key
or secret. `state: "enrollment_pending"` means the grant was saved but
credential enrollment needs a retry with the same confirmed request.

Limits are intersected at request time, and the narrowest current value wins.
Lowering or disabling a policy therefore stops later work even when a site has
an older grant.

### Standing approvals

A standing approval lets an unattended integration run an operation whose
registered declaration normally requires confirmation. It remains bound to one
grant and does not bypass the caller's live permissions, the active policies,
credits, meters, or request limits.

Create one through the same two-call configure flow:

```json theme={null}
{
  "configuration": {
    "kind": "standing_approval",
    "standing_approval": {
      "grant_id": "<grant-id>",
      "disclosure_summary": "Generate approved campaign illustrations for this site.",
      "expires_at": "2026-12-31T23:59:59Z",
      "standing_automation_id": null
    }
  }
}
```

### Enroll an exact workflow version

A managed workflow uses two grants. The site grant permits `workflow.invoke`
and `workflow.status`. A separate workflow grant permits only the managed
operations that its steps call. Create the workflow grant through the same
two-call flow, targeting one active, published definition version:

```json theme={null}
{
  "configuration": {
    "kind": "grant",
    "grant": {
      "account_policy_id": "<account-policy-id>",
      "project_policy_id": null,
      "previous_grant_id": null,
      "target": {
        "kind": "workflow",
        "workflow_definition_id": "<workflow-definition-id>",
        "workflow_definition_version_id": "<workflow-definition-version-id>",
        "target_board_id": "<site-board-id>"
      },
      "operation_keys": ["web.search", "web.read"],
      "allowed_tiers": [],
      "default_tier": null,
      "limits": {
        "max_input_bytes": 100000,
        "max_output_tokens": 2000,
        "max_search_queries": 1,
        "max_search_results": 5,
        "max_tool_calls": 2,
        "max_concurrent_requests": 1
      }
    }
  }
}
```

The workflow version must contain only bounded `managed.native_tool` steps.
Each step declares a supported operation and its own ceilings. Managed
workflows do not accept delay, conversation, code, or other side-effect nodes,
and a managed step cannot call `workflow.invoke` or `workflow.status`.

Create two standing approvals for the same native standing automation: one on
the site grant and one on the workflow grant. Set `standing_automation_id` on
both approvals. Then enroll the exact pair through the same two-call configure
flow:

```json theme={null}
{
  "configuration": {
    "kind": "workflow_enrollment",
    "site_grant_id": "<site-grant-id>",
    "workflow_grant_id": "<workflow-grant-id>",
    "standing_automation_id": "<standing-automation-id>",
    "site_standing_approval_id": "<site-standing-approval-id>",
    "workflow_standing_approval_id": "<workflow-standing-approval-id>",
    "previous_enrollment_id": null
  }
}
```

The configured response includes `enrollment_id`,
`workflow_definition_id`, `workflow_definition_version_id`,
`service_actor_user_id`, `service_actor_role`, and `request_replayed`. Goosy
derives those workflow and actor bindings from the native enrollment. The
site's managed key is never passed to the workflow runtime.

## Call a managed operation

Calls originate from the site's server runtime using its reserved
`GOOSY_API_KEY` binding. This is distinct from the ordinary admin key used to
configure the site. Keep the managed binding out of browser code, source
archives, logs, and public previews. It selects the tenant, workspace, site,
board, and grant; operation bodies cannot override them. Goosy manages it and
does not return it through the configuration API.

Read-classified operations require an `idempotency_key`. An operation that
requires confirmation follows one of two paths:

* Without a standing approval, send the operation body without an
  `idempotency_key`. The response contains `state: "confirmation_required"`.
  Confirm it by calling the same operation with only `confirmation_id`; the
  stored request is used.
* With a verified standing approval, send the body with an `idempotency_key`.
  The request runs without an interactive confirmation.

An idempotency key identifies one exact request. Reusing it with different
input is refused. Reusing it with identical input returns the durable request
state and does not submit the provider work again. Keys are trimmed, nonempty
strings of at most 256 characters.

### Exact operation bodies

Send each body to
`POST https://app.goosybear.ai/api/v1/tools/{operation}`. The operation's
`managed_key_input_schema` in `pages.capabilities.catalogue` is authoritative
for runtime enum values, bounds, defaults, and request controls.

Only `pages.ai.generate_text` accepts `tier` and `requestedOutputTokens`. The
other eight operations reject both fields.

<Warning>
  `images.generate` and `images.iterate` also have ordinary API-key contracts.
  A site-managed credential is intercepted before those ordinary tool bodies
  and accepts only the managed schemas below. Do not send an ordinary
  `workspace`, `board`, `brief`, or provider field from managed site code.
</Warning>

#### `pages.ai.generate_text`

Choose `tier` from the operation's current catalogue entry, or omit it to use
the grant's current default. `requestedOutputTokens` is required on the initial
call and must be a positive integer within the effective limit.

```json theme={null}
{
  "instruction": "Turn this approved outline into a concise introduction.",
  "input": {
    "text": "The Pages service imports, previews, and publishes one exact revision.",
    "citations": [
      {
        "title": "Pages API",
        "url": "https://docs.example.com/pages",
        "excerpt": "Publish one exact revision."
      }
    ]
  },
  "requestedOutputTokens": 500,
  "tier": "standard"
}
```

The operation response is `{ "text": "..." }` inside the managed execution
envelope.

#### `web.search`

```json theme={null}
{
  "query": "durable request idempotency patterns",
  "mode": "answer",
  "maxResults": 5,
  "freshnessDays": 30,
  "includeDomains": ["example.com"],
  "idempotency_key": "search-release-notes-2026-09-10"
}
```

`maxResults` defaults to 5 and is bounded from 1 through 10. The result
contains an answer when available, normalized result records, a truncation
flag, and filters the provider could not apply.

#### `web.read`

```json theme={null}
{
  "url": "https://example.com/reference",
  "idempotency_key": "read-reference-v1"
}
```

Only HTTP and HTTPS URLs are accepted. The result contains the URL, optional
title, Markdown content, and a truncation flag.

#### `web.image_search`

```json theme={null}
{
  "query": "modern timber library exterior",
  "maxResults": 5,
  "imageType": "photo",
  "size": "large",
  "aspect": "wide",
  "usageRights": "commercial_or_other_licenses"
}
```

`maxResults` defaults to 5 and is bounded from 1 through 10. Keep the managed
response's `request_id`; acquisition refers back to this succeeded search
rather than accepting a public image URL.

#### `images.acquire_remote`

```json theme={null}
{
  "searchRequestId": "<managed-image-search-request-id>",
  "position": 1
}
```

The search request must belong to the same tenant, workspace, and grant. The
result contains `storageObjectId` and `boardItemId` for the copy attached to the
site's granted board.

#### `images.generate`

```json theme={null}
{
  "prompt": "Editorial illustration of a neighborhood design workshop",
  "count": 1,
  "aspectRatio": "16:9"
}
```

`count` defaults to 1. Its upper bound and accepted aspect ratios are published
by the generated request schema. The result contains `runId` and
`deliveredIds`.

#### `images.iterate`

```json theme={null}
{
  "parentGenerationId": "<generation-id>",
  "instruction": "Use a warmer palette and preserve the composition.",
  "count": 1,
  "aspectRatio": "16:9"
}
```

The parent generation must already belong to the exact board selected by the
site grant. The result contains `runId` and `deliveredIds`.

#### `workflow.invoke`

`workflow.invoke` requires an active workflow enrollment and both of its
standing approvals. It runs the enrolled, pinned workflow version synchronously
to a terminal result; an enqueue or accepted state is not reported as success.

```json theme={null}
{
  "enrollmentId": "<workflow-enrollment-id>",
  "input": {
    "topic": "Durable API request handling"
  },
  "idempotency_key": "invoke-editorial-research-v1"
}
```

The managed response contains `workflowRunId` and `status: "completed"`. A
failed child step fails the invocation. An uncertain paid child or terminal
write returns an uncertain managed request for reconciliation and is not
automatically replayed.

#### `workflow.status`

Use the managed request id returned by `workflow.invoke` to read its durable
native run state:

```json theme={null}
{
  "invocationRequestId": "<workflow-invoke-request-id>",
  "idempotency_key": "workflow-status-check-v1"
}
```

The response contains `workflowRunId`, `status` (`active`, `completed`, or
`abandoned`), and `completedAt` when the run has ended. The lookup remains
bound to the same tenant, workspace, and site grant.

## Handle execution states

Managed execution returns one of four states inside `data`:

| `state`                   | Meaning                                                    | Required action                                                                                        |
| ------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `confirmation_required`   | The operation needs a person's approval.                   | Store `confirmation_id` and confirm the exact request before `expires_in_seconds` elapses.             |
| `completed`               | The request reached `succeeded`, `failed`, or `uncertain`. | Read `request_id`, `status`, `response`, and `error_code`. Do not resubmit an uncertain provider call. |
| `reconciliation_required` | An identical request is already `running`.                 | Retain `request_id` and reconcile it; do not issue a different idempotency key.                        |
| `terminal_replay`         | This idempotency key already reached a terminal state.     | Reuse the stored `response` or `error_code`; no provider call was repeated.                            |

Example confirmation response:

```json theme={null}
{
  "ok": true,
  "data": {
    "state": "confirmation_required",
    "confirmation_id": "<confirmation-id>",
    "expires_in_seconds": 1800
  }
}
```

Confirm an execution operation with only the id:

```json theme={null}
{ "confirmation_id": "<confirmation-id>" }
```

The stored payload, output reservation, and selected writing tier are used;
fields sent beside `confirmation_id` do not replace them. Configuration
confirmation is different: `pages.capabilities.configure` requires the exact
`configuration` again.

## Credits and request limits

When a managed call is billable, it uses the account's existing credit balance
and the operation's native meter. Managed access does not create a separate
wallet. Admission checks the current balance and the effective input, output,
search, tool-call, and concurrency limits before dispatch. The execution
service records actual usage through the operation's normal settlement path;
the policy values are limits, not prices.

See [Actions that spend credits](/api/spending-actions) for the general
confirmation model and `credits.balance` precheck.

## Revoke access

Revoke the managed grant through `pages.capabilities.configure`. This is also
an exact two-call confirmation:

```json theme={null}
{
  "configuration": {
    "kind": "grant_revoke",
    "grant_id": "<grant-id>"
  }
}
```

Repeat the same `configuration` with `confirmation_id`. The configured result
reports `changed`, `standing_approvals_revoked`, `managed_keys_revoked`, and
`request_replayed`. To revoke only one standing approval, use:

```json theme={null}
{
  "configuration": {
    "kind": "standing_approval_revoke",
    "standing_approval_id": "<standing-approval-id>"
  }
}
```
