generated: '2026-08-30' method: searched source: >- https://dev.acquia.com/source-cms/reference/content-api.md, https://dev.acquia.com/source-cms/reference/authentication.md, https://dev.acquia.com/source-cms/reference/webhooks.md, https://dev.acquia.com/source-cms/reference/mcp-server.md, https://dev.acquia.com/cloud-platform/reference/acli.md, and the info.description plus declared operations of the 20 Cloud Platform OpenAPI files in openapi/ provider: Acquia providerId: acquia description: >- Cross-cutting runtime semantics for Acquia's two API surfaces. They are genuinely different products with different conventions — the Cloud Platform API is a HAL+JSON control plane for hosting infrastructure, the Source CMS Content API is a JSON:API content plane — and an agent must not carry assumptions from one to the other. surfaces: - id: cloud-platform name: Cloud Platform API base_url: https://cloud.acquia.com/api media_type: application/hal+json docs: https://cloudapi-docs.acquia.com/ - id: content-api name: Acquia Content API (Source CMS) base_url: '{DRUPAL_SITE_URL}/api' media_type: application/vnd.api+json docs: https://dev.acquia.com/source-cms/reference/content-api.md authentication: style: OAuth 2.0 Bearer on both surfaces cloud_platform: flow: client_credentials token_url: https://accounts.acquia.com/api/token credential_origin: >- API key + secret generated by the user at cloud.acquia.com > Account Settings > API Tokens. The secret is shown once. content_api: flows: [client_credentials, authorization_code, refresh_token] token_url: '{DRUPAL_SITE_URL}/oauth/token' header: 'Authorization: Bearer (exactly one space after Bearer)' token_lifetimes: access_token_seconds: 300 authorization_code_seconds: 300 refresh_token_seconds: 1209600 refresh_token_availability: >- Returned only by authorization_code and refresh_token grants. client_credentials NEVER returns one; server-to-server clients re-request tokens with their credentials. scope_default: >- A token request with no scope parameter carries EVERY scope selected on the API client. A scope parameter may only narrow to a subset; naming an unselected scope returns invalid_scope. detail: authentication/acquia-authentication.yml, scopes/acquia-scopes.yml idempotency: inbound_support: false inbound_detail: >- No Idempotency-Key request header is documented or declared on any write operation of either surface. An agent retrying a failed POST to the Content API or the Cloud Platform API cannot protect itself from a duplicate — this is the single largest agent-safety gap in Acquia's surface. outbound_support: true outbound_detail: >- Source CMS webhook DELIVERIES carry an Idempotency-Key header whose value is the SHA-1 of the payload; retries of the same delivery repeat the key, so a receiver can deduplicate. Acquia notes it is a hash, not a keyed signature, so it does not authenticate the sender. header: Idempotency-Key direction: outbound only (provider -> your webhook endpoint) tool_level: >- Two MCP tools are idempotent by design — create_vocabulary returns the existing vocabulary rather than duplicating, and get_or_create_term matches an existing term by case-insensitive exact name. That is tool behavior, not a protocol guarantee. status: na-inbound pagination: content_api: style: 'JSON:API page[] parameters' params: ['page[offset]', 'page[limit]'] response_fields: [links.self, links.next, links.last, meta.count] sharp_edge: >- Pagination links are computed BEFORE access filtering. A page can be "data": [] and still carry links.next / links.last — an empty page is NOT the end of a collection. Follow links.next, or check meta.omitted. Likewise meta.count is the total INCLUDING entries the caller cannot see, so data can be shorter than meta.count. cloud_platform: style: query parameters params: [limit, offset, sort, filter] field_selection: content_api: sparse_fieldsets: 'fields[]=' inclusion: 'include= (compound documents)' filtering: 'filter[...] with JSON:API filter syntax; malformed paths return 400' sorting: sort docs: https://dev.acquia.com/source-cms/reference/query-parameters/ request_id_tracing: content_api: none documented mcp: >- Tool-level error objects carry a requestId member ({code, message, retryable, details, requestId}). cloud_platform: none documented versioning: cloud_platform: 'Accept: application/hal+json, version=2 — header-negotiated per resource' content_api: unversioned path; JSON:API 1.1 reported in jsonapi.version detail: lifecycle/acquia-lifecycle.yml error_envelope: count: 4 summary: >- Four distinct error shapes across the estate: HAL+JSON {error, message} on Cloud Platform, JSON:API errors documents on the Content API, RFC 6749 {error, error_description} on the OAuth endpoints, and JSON-RPC 2.0 on MCP. None is RFC 9457. detail: errors/acquia-problem-types.yml rate_limit_signaling: documented_headers: none exhaustion_status: 429 retry_after: >- Acquia's wording is "honor a Retry-After header when present" — the header is not guaranteed and no X-RateLimit-*/RateLimit-* family is documented on either surface. detail: rate-limits/acquia-rate-limits.yml cors: content_api: >- Open by default. Allowed origins are restricted per site at API > CORS configuration; a blocked request surfaces only as a browser console CORS error, with no server-side error code. public_access: content_api: >- A site's Public access setting (API > JSON:API) can allow unauthenticated GETs of PUBLISHED content. Reading UNPUBLISHED content always requires the content:administer scope. write_default: >- Allowed operations at API > JSON:API defaults to Read-only. A fresh site returns 405 on every write until an administrator changes it — an agent's first write will fail on a default site. dry_run_mode: supported: false status: none detail: >- Neither surface documents a dry-run, preview or validate-only mode for writes. The closest published analogue is the Canvas draft channel: MCP write tools for Canvas pages, menu items and site settings write an AUTO-SAVE DRAFT rather than the canonical entity, and nothing is live until publish_auto_saves is called. That is a staging model, not a dry run — the draft is real persisted state. reversibility: status: documented grade_basis: >- Reversal paths exist and are named with real operationIds, but Acquia publishes NO window for any of them. The backup-retention period is not stated on any page reached in this pass (docs.acquia.com/acquia-cloud-platform/manage/backups returned 404), so no window is asserted here. Per the pipeline contract that is `documented` (0.4), not `verified`. write_surfaces: - surface: Cloud Platform API — environment database forward: postEnvironmentsDatabaseBackups (POST /environments/{environmentId}/databases/{databaseName}/backups) reversal: postEnvironmentsDatabaseRestoreBackup reversal_path: >- POST /environments/{environmentId}/databases/{databaseName}/backups/{backupId}/actions/restore reversal_cli: 'acli api:environments:database-backup-restore' window: null window_note: >- No retention period published. The restore itself is gated by state, not time: Acquia's spec declares the failure "The database backup has not completed yet, and cannot be restored at this time", plus refusals on Node.js and non-hosted applications. docs: https://dev.acquia.com/cloud-platform/code-workflow/guide/ - surface: Cloud Platform API — deployed code forward: postEnvironmentsSwitchCode (POST /environments/{environmentId}/code/actions/switch) reversal: postEnvironmentsSwitchCode reversal_path: the same operation, pointed back at the previous branch or tag reversal_cli: 'acli api:environments:code-switch' window: null window_note: >- Rollback is the same forward operation aimed at the prior ref — Acquia documents it under "roll back a release". No window applies; it is bounded only by what refs still exist. docs: https://dev.acquia.com/cloud-platform/code-workflow/guide/#roll-back-a-release - surface: Cloud Platform API — Continuous Delivery Environments forward: 'acli env:create' reversal: deleteEnvironment (DELETE /environments/{environmentId}) / acli env:delete window: null window_note: >- Deletion is the reversal of creation, and it is asynchronous — acli returns "is being deleted", not "deleted". There is no undelete. irreversible: true irreversible_note: >- env:mirror is explicitly destructive on the destination and has NO reversal. Acquia's own reference tells the operator to run a pre-flight check first. - surface: Source CMS MCP — Canvas pages, menu items, site settings forward: create_canvas_page, update_canvas_page, create_menu_item, update_menu_item, update_site_settings, set_homepage, set_page_layout, add_component_to_page, move_component, remove_component, update_component_props reversal: discard_auto_saves window: null window_note: >- Genuinely reversible BEFORE publication: every one of these writes to an auto-save draft, and discard_auto_saves throws the draft away without publishing. Once publish_auto_saves or publish_canvas_page commits it, there is no documented undo. Acquia states no expiry on an auto-save draft. strength: strong-before-publish - surface: Source CMS MCP — content entities forward: create_node, update_node, batch_create_nodes, create_media, create_remote_video, create_dam_media, create_vocabulary, get_or_create_term reversal: none window: null window_note: >- NO MCP delete tool exists for nodes, media or taxonomy terms — Acquia says so explicitly: the catalog's only two delete tools are delete_menu_item and delete_canvas_page. An agent can create content over MCP that it cannot remove over MCP; removal requires the JSON:API deleteEntry operation or the admin UI. irreversible_over_mcp: true - surface: Source CMS Content API forward: createEntry (POST), updateEntry (PATCH) reversal: deleteEntry (DELETE /api/{entityType}/{bundle}/{uuid}) window: null window_note: >- A hard delete with no documented soft-delete, trash or restore window. updateEntry has no revert operation in the published contract; Drupal revisions exist in the product but are not exposed as a published Content API operation. read_only: false na: false cross_links: errors: errors/acquia-problem-types.yml lifecycle: lifecycle/acquia-lifecycle.yml authentication: authentication/acquia-authentication.yml scopes: scopes/acquia-scopes.yml rate_limits: rate-limits/acquia-rate-limits.yml webhooks: asyncapi/acquia-source-cms-webhooks.yml mcp: mcp/acquia-mcp.yml