Skip to content

Call the Cloud Platform API

Goal: make one authenticated call to the Cloud Platform API: the REST API behind everything acli and the Cloud Platform user interface can do.

Reach for it when a task has to run without a person at the terminal (a nightly database refresh, environment creation in a pipeline) or when no acli command covers what you need. For everything else, acli wraps this API and handles the token exchange for you: start at the CLI quickstart.

  • An API token (API Key + API Secret) for your Acquia account.
  • A working OAuth 2.0 access token and one successful curl call listing your applications.
  • The two failure modes you’ll actually hit (expiry and rate limiting) and their fixes.
  • An Acquia account that can sign in to cloud.acquia.com
  • curl (preinstalled on macOS and Linux)
  • Optional: jq, the standard JSON filter. Step 4 uses it to pull the token out of the exchange response, and states how to manage without it.
  1. Open cloud.acquia.com/a/profile/tokens, give the token a human-readable label (for example local-automation), and click Create Token.

    Record the API Key and API Secret now: you can’t retrieve them after closing the browser tab.

  2. .env
    ACQUIA_CLOUD_API_KEY=d2b18a6c-0000-0000-0000-000000000000
    ACQUIA_CLOUD_API_SECRET=your-api-secret

    These are deliberately not named DRUPAL_CLIENT_ID / DRUPAL_CLIENT_SECRET. That pair names a per-site API client for the Content API; this key/secret pair is an account-level Cloud Platform credential that inherits your user’s permissions across the whole subscription. Distinct names keep the two from ever being swapped in a script.

  3. Exchange the credentials for an access token

    Section titled “Exchange the credentials for an access token”

    The Cloud Platform API uses the OAuth 2.0 client_credentials grant against Acquia’s central accounts service. Use --data-urlencode (not --data) so non-alphanumeric characters in the secret survive encoding:

    Terminal window
    set -a; source .env; set +a
    curl -s https://accounts.acquia.com/api/auth/oauth/token \
    --data-urlencode "client_id=$ACQUIA_CLOUD_API_KEY" \
    --data-urlencode "client_secret=$ACQUIA_CLOUD_API_SECRET" \
    --data-urlencode "grant_type=client_credentials"

    A successful response looks like this:

    {
    "token_type": "Bearer",
    "expires_in": 300,
    "access_token":
    "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9…(redacted)",
    "scope": "role:authenticated-user"
    }

    Note expires_in: 300: the token lives 300 seconds (5 minutes). Scripts regenerate it rather than store it.

  4. Capture the token and list the applications your account can see:

    Terminal window
    TOKEN=$(curl -s \
    https://accounts.acquia.com/api/auth/oauth/token \
    --data-urlencode "client_id=$ACQUIA_CLOUD_API_KEY" \
    --data-urlencode "client_secret=$ACQUIA_CLOUD_API_SECRET" \
    --data-urlencode "grant_type=client_credentials" \
    | jq -r .access_token)
    curl -s https://cloud.acquia.com/api/applications \
    -H "Authorization: Bearer $TOKEN"

    Without jq, run the token request from step 3 on its own and paste the access_token value into TOKEN= yourself.

    A 200 response returns JSON listing your applications (trimmed):

    {
    "total": 1,
    "_embedded": {
    "items": [
    {
    "uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "name": "myproject",
    "subscription": {
    "uuid": "…(redacted)",
    "name": "MyProject"
    }
    }
    ]
    },
    "_links": {
    "self": {
    "href": "https://cloud.acquia.com/api/applications"
    }
    }
    }

    That application uuid is the handle every other Cloud Platform API endpoint takes: environments, deployments, backups, logs, cron, and more.

Acquia’s accounts service exchanged your account-level API Key and Secret for an access token via the OAuth 2.0 client_credentials grant, and the Cloud Platform API at cloud.acquia.com/api accepted that token in an Authorization: Bearer header. The token expires after 300 seconds, so real automation wraps the exchange in a helper and re-runs it per batch of calls. The API is rate limited to 100 calls per minute per user, enough for CI tasks, but worth knowing before you write a polling loop.

{ "error": "invalid_client", "error_description": "The client secret supplied for a confidential client is invalid." }

either the key/secret is wrong, or the token request wasn’t form-encoded. The token endpoint only accepts Content-Type: application/x-www-form-urlencoded (which --data-urlencode sets); re-check the credentials from cloud.acquia.com/a/profile/tokens and keep the request shape from step 3. If the secret is lost, create a new token; secrets are shown once.

{ "errorCode": "E0000021", "errorSummary": "Bad request. Accept and/or Content-Type headers likely do not match supported values." }

you sent the token request as JSON. The OAuth 2.0 framework doesn’t support application/json here; send the fields form-encoded as in step 3.

403 Forbidden with { "error": "unauthorized", "message": "The access token has expired." } on the API call itself

the access token expired; it only lives 300 seconds. Re-run the token exchange and retry. (A malformed or revoked token returns the same 403 with "The access token provided is invalid." instead.)

HTTP/1.1 429 Too Many Requests

you exceeded the 100-calls-per-minute rate limit. The response carries a Retry-After header with the number of seconds until calls are unblocked:

HTTP/1.1 429 Too Many Requests
Retry-After: 57

The fix: back off, wait the Retry-After seconds, then retry, and add that backoff to any loop that made this happen.

Was this page helpful?