Calling the Pulumi Cloud REST API from the CLI
The pulumi cloud api command lets you call any Pulumi Cloud REST API endpoint directly from the CLI. It runs non-interactively, is safe to script, and reuses the credentials you already use with pulumi login, so you don’t need to manage a separate token to call the API.
The command is modeled after gh api: you pass a path, an operation ID, or a METHOD path row and the CLI handles authentication, headers, path-template substitution, and content negotiation for you.
Subcommands
pulumi cloud api ships in three forms:
pulumi cloud api <path-or-operation-id>issues a single HTTP request against the named endpoint.pulumi cloud api list(alias:ls) lists every endpoint exposed by the Pulumi Cloud OpenAPI spec. Output is a TTY-friendly table by default; pass--format=jsonto get a stable, scriptable envelope.pulumi cloud api describe <path-or-operation-id>prints the parameters, request body, and response schemas for a single operation. Default output is a human-readable text render; pass--format=markdownfor a piping-friendly markdown document or--format=jsonfor the stable JSON envelope.
Authentication
pulumi cloud api uses the same access token as the rest of the Pulumi CLI. Anything that authorizes pulumi login — a personal, organization, or team access token, PULUMI_ACCESS_TOKEN, or an OIDC-issued token — also authorizes API calls.
If a request requires authentication and the CLI is not authenticated, pulumi cloud api exits with code 3 and an error envelope identifying the auth failure. A 401 or 403 response from the API maps to the same exit code.
Path-template substitution
Most Pulumi Cloud endpoints take an organization, project, or stack as part of the URL — for example, /api/orgs/{orgName}/members. pulumi cloud api resolves each template variable in the following order:
- A literal value typed directly into the URL (no template variable to fill).
- A matching
-For-ffield (consumed for the path and not forwarded as a query or body parameter). - The current Pulumi project context: the selected stack’s organization, the project name from
Pulumi.yaml, and the selected stack name.
When a stack is selected, you can usually call:
pulumi cloud api ListOrganizationMembers
and the CLI fills {orgName} from the selected stack automatically. When no project context is available, supply the values with -F:
pulumi cloud api GetStack -F orgName=acme -F projectName=web -F stackName=prod
If a path template and a request body field share a parameter name, the first matching -F is consumed for the path. Any subsequent -F with the same key is sent in the body, so you can pass -F twice to fill both. To keep the body fully separate, use --body or --input instead.
Request flags
pulumi cloud api accepts a gh api-style flag set for shaping the request:
| Flag | Purpose |
|---|---|
-X / --method | HTTP method. Defaults to GET, or POST when body fields, --body, or --input are present. |
-F / --field | Typed key=value. Numbers, booleans, and null are auto-detected; JSON object/array literals are parsed; @file reads from a file and @- reads from stdin. Routed as query parameters on GET/HEAD and as JSON body fields otherwise. |
-f / --raw-field | String key=value with no type coercion. Sent verbatim. |
-H / --header | Custom HTTP header Key: Value (repeatable). User headers win over the encoder’s defaults (Accept, Content-Type); a user-supplied Authorization is dropped to keep the resolved CLI token in place. |
--body | Inline request body sent verbatim. Mutually exclusive with --input. |
--input | Read the request body from a file; - reads from stdin. |
--paginate | Follow continuation cursors and emit a single combined JSON envelope. Capped at 1000 pages; on truncation, network failure, or cancellation, the partial result is flushed so callers always have something to act on. |
-i / --include | Include the HTTP status line and response headers in the output. |
--silent | Suppress the response body on success. Errors are still printed. |
--verbose | Dump the resolved request and the full response to stderr. |
--dry-run | Print the resolved request without sending it. |
--format | Drive content negotiation. Default uses the operation’s primary response content type (usually JSON). json or markdown request that format via the Accept header — rejected if the operation’s spec doesn’t declare it. raw keeps the operation’s default Accept and passes the response body through unchanged. |
Both requests and responses use gzip compression in transit. The CLI routes responses by content type: it pretty-prints JSON, passes text and markdown through as-is, and streams binary responses unchanged.
OpenAPI spec caching
The CLI fetches the OpenAPI spec once from /api/openapi/pulumi-spec.json and caches it under $PULUMI_HOME/cloud-api-cache/<host>/spec.json for 24 hours. When the cached copy is older than the TTL and a refresh fails (network error, 5xx response), the CLI returns the stale cache along with a stderr warning so transient outages don’t break list and describe.
Pass --refresh-spec to any subcommand to force a re-fetch.
Exit codes and error envelope
pulumi cloud api uses the standard Pulumi CLI exit-code mapping. The values it can emit are:
| Exit code | Meaning |
|---|---|
0 | Success. |
1 | Caller error: bad arguments, no matching operation, missing template variable, or partial pagination. |
2 | Invalid flag combination (for example, --body and --input together). |
3 | Authentication or authorization failure (missing credentials, 401, or 403). |
8 | Operation canceled (SIGINT or SIGTERM). |
255 | Internal CLI error. |
On failure, the CLI writes errors to stderr as a single-line JSON envelope with a stable code field. Agents and scripts can parse the envelope to react to specific failure modes; tests pin the schema so it does not drift between releases.
Examples
Basic requests
Inspect the currently authenticated user:
pulumi cloud api /api/user
Call by URL path with template variables filled from -F:
pulumi cloud api /api/orgs/{orgName}/members -F orgName=acme
Call by operation ID — orgName is taken from the current Pulumi project:
pulumi cloud api ListOrganizationMembers
Pass path variables explicitly when no project context is available:
pulumi cloud api GetStack -F orgName=acme -F projectName=web -F stackName=prod
Bodies and fields
Create a resource via POST; body fields are auto-detected from the operation:
pulumi cloud api CreateOrgToken -F orgName=acme \
-F name=ci-bot -F description="CI deploy token" \
-F admin=false -F expires=0
Send a nested JSON body by mixing scalar fields with an inline JSON literal:
pulumi cloud api CreateStack -F orgName=acme -F projectName=web \
-F stackName=prod -F 'tags={"env":"prod","team":"platform"}'
Pass an entire request body inline, or read it from a file or stdin:
pulumi cloud api UpdateStackTags -F orgName=acme -F projectName=web -F stackName=prod \
--body '{"env":"prod","team":"platform"}'
pulumi cloud api UpdateStackTags --input ./tags.json
cat tags.json | pulumi cloud api UpdateStackTags --input -
Filtering and pagination
Filter the JSON response with jq:
pulumi cloud api /api/user --format=json | jq '.githubLogin'
Walk every page of a paginated endpoint:
pulumi cloud api ListUserStacks --paginate | jq -r '.stacks[].stackName'
Discovery and dry-run
Browse the API surface and inspect a specific operation:
pulumi cloud api list --format=json | jq '.operations[] | select(.tag == "Stacks")'
pulumi cloud api describe CreateOrgToken --format=markdown
Preview the resolved request without sending it:
pulumi cloud api CreateOrgToken -F orgName=acme \
-F name=ci-bot -F description="CI" -F admin=false -F expires=0 --dry-run
See also
Thank you for your feedback!
If you have a question about how to use Pulumi, reach out in Community Slack.
Open an issue on GitHub to report a problem or suggest an improvement.