- Two tokens, not one. The access token is a one-hour Bearer. The refresh token (the UI calls it an API token) lives about 90 days. You exchange the refresh token for an access token; the refresh token never goes in an Authorization header.
- The endpoint depends on the plane and the org type:
/oauth/provider/tokenfor the provider,/oauth/tenant/<org>/tokenfor All Apps and freshly installed VM Apps orgs. - A VM Apps org upgraded from Aria Automation 8.x keeps the old vRA flow:
/csp/gateway/am/api/login.jsonthen/iaas/api/login. Same product, two auth paths. - Username and password works too, against
/cloudapi/1.0.0/sessions/provider. The Bearer comes back in thex-vmware-vcloud-access-tokenresponse header, not the body. - Send the API version in the Accept header (
application/json;version=9.0.0). Leave it off and you get a vague failure that looks like an RBAC problem but is not.
Who this is for: platform and cloud admins, automation engineers and architects who script against VCF Automation 9, build pipelines, or wire up Terraform and ITSM against the API.
Prerequisites: a VCF Automation 9.0 / 9.1 instance, an account with provider or org admin rights, and basic comfort with curl and OAuth refresh grants. You should know which org type you are hitting (see Part 3).
Here is the first call most people try, and the first one that fails:
curl -k 'https://flt-auto01.rainpole.io/cloudapi/1.0.0/orgs'
-H 'Authorization: Bearer <the refresh token I copied from the UI>'
HTTP/1.1 401 Unauthorized
The refresh token you copied out of the UI is not a Bearer token. It is a long-lived credential whose only job is to mint short-lived access tokens. Drop it straight into an Authorization header and the platform rejects it. This is the single most common VCF Automation API mistake I see on customer calls, and it has nothing to do with permissions. You authenticated with the wrong artifact.
The two tokens, and why the wrong one ends up in the header
VCF Automation (the product formerly known as VMware Aria Automation, and before that vRealize Automation) splits authentication into two artifacts on purpose. The refresh token, which the UI labels an API token, is long lived: about 90 days. The access token is a signed JWT that expires after one hour. Every real API call carries the access token as a Bearer. The refresh token only ever talks to the token endpoint.
The split exists so a leaked credential has a blast radius measured in minutes, not months, while your automation still holds something durable. Your pipeline keeps the 90-day refresh token in a secret store. At the top of each run it trades that token for a fresh one-hour access token. If the access token leaks into a log, it is dead within the hour. If the refresh token leaks, you revoke one credential and rotate. That asymmetry is the whole design, and it is why putting the refresh token in an Authorization header is not just wrong, it defeats the point.
Three API planes: provider, organization, resource
Before you authenticate you have to know which plane you are talking to, because the token you mint is scoped to it. A provider Bearer will not list a tenant project, and an org Bearer will not create a new tenant. Pick the plane first, then mint the matching token.
The provider plane is where you create and manage tenant organizations, provider gateways, regions and IP spaces. The organization (tenant) plane is where projects, policies, catalog items and deployments live. The resource plane is the IaaS and deployment surface inside an org, where machines, networks and day-2 actions sit. The first two have their own token endpoints; the resource APIs ride on the org Bearer.
Step 1: get a refresh token from the UI
You generate the refresh token once, by hand, with an account that holds the right role. As provider admin you open the API token area from the user menu and generate a token; as an org user you do the same inside the org. Copy it the moment it appears. The UI shows the full value once and then stores only a reference. Lose it and you generate a new one; you cannot retrieve the old.
For interactive work in the in-product Swagger explorer you do not need to do any of this. In VCF Automation 9 the embedded API documentation handles the token for you in the background, which is a real change from earlier releases where you pasted a Bearer into every try-it call. The manual exchange below is for scripts and pipelines running outside the UI.
Step 2: the provider token exchange
With a provider refresh token in hand, the exchange is one POST to the provider token endpoint:
curl -k --location 'https://flt-auto01.rainpole.io/oauth/provider/token'
--header 'Accept: application/*'
--header 'Content-Type: application/x-www-form-urlencoded'
--data-urlencode 'grant_type=refresh_token'
--data-urlencode 'refresh_token=<refresh token>'
The response carries the access token (and, if rotation is enabled, a new refresh token). Pull access_token out:
{
"access_token": "eyJraWQiOiJ1cm46dmNsb3VkOi4uLg",
"token_type": "bearer",
"expires_in": 3600
}
Now use that Bearer against the provider API. Listing every org on the platform is the cleanest smoke test:
curl -k 'https://flt-auto01.rainpole.io/cloudapi/1.0.0/orgs?page=1&pageSize=25'
-H 'accept: application/json;version=9.0.0'
-H 'Authorization: Bearer <access_token>'
A 200 with a list of orgs means the whole chain works: the refresh token was valid, the grant succeeded, and the Bearer is scoped to the provider. If you get a 401 here but the token exchange returned an access token, the problem is almost always the version header, not the token.
application/json;version=9.0.0 is the current value; the older 40.0 form still resolves. Omit the version entirely and the platform answers with a 406 or a generic 400, which sends people hunting for an RBAC fault that does not exist. Set the version on every call and this class of bug disappears.The tenant plane: All Apps and new VM Apps orgs
For a tenant org the URL changes from /provider to /tenant/<org name>. Everything else about the grant is identical:
curl -k --location 'https://flt-auto01.rainpole.io/oauth/tenant/allapps_org01/token'
--header 'Accept: application/*'
--header 'Content-Type: application/x-www-form-urlencoded'
--data-urlencode 'grant_type=refresh_token'
--data-urlencode 'refresh_token=<refresh token>'
This is where org type starts to matter, and it is the part the docs gloss over. An All Apps organization, the Kubernetes-API-based model, always uses this tenant flow. A VM Apps organization that was freshly installed on VCF 9 also uses it. The org name in the URL must be the real org name, not its display name, and it is case sensitive. Get it wrong and you get a 404 on the token endpoint, which reads like the platform is down when it is just a typo.
Endpoint and lifetime reference
| Plane / org type | Token endpoint | Access token life |
|---|---|---|
| Provider | /oauth/provider/token | ~1 hour |
| All Apps org | /oauth/tenant/<org>/token | ~1 hour |
| VM Apps org (new) | /oauth/tenant/<org>/token | ~1 hour |
| VM Apps org (upgraded 8.x) | /csp/gateway/am/api/login.json then /iaas/api/login | ~1 hour |
| Provider (basic auth) | /cloudapi/1.0.0/sessions/provider | ~1 hour |
Username and password, when you cannot pre-stage a refresh token
Sometimes you cannot generate a refresh token ahead of time. A bootstrap script running on a brand new instance, or a break-glass path that must not depend on a stored secret, needs to authenticate from credentials alone. The provider session endpoint takes HTTP basic auth and hands back a Bearer.
Build the credential string as username@system:password, base64-encode it, and POST:
export BASIC_AUTH=$(echo -n 'admin@system:YOUR_PASSWORD' | base64)
curl -k -i --location 'https://flt-auto01.rainpole.io/cloudapi/1.0.0/sessions/provider'
--header 'Accept: application/json;version=9.0.0'
--header 'Content-Type: application/json;version=9.0.0'
--header "Authorization: Basic ${BASIC_AUTH}"
-X POST
The catch that wastes an afternoon: the Bearer is not in the response body. It comes back in the x-vmware-vcloud-access-token response header, which is exactly why the command uses curl’s -i flag. Parse the header, not the JSON:
BEARER=$(curl -s -i -k 'https://flt-auto01.rainpole.io/cloudapi/1.0.0/sessions/provider'
--header 'Accept: application/json;version=9.0.0'
--header 'Content-Type: application/json;version=9.0.0'
--header "Authorization: Basic ${BASIC_AUTH}" -X POST
| awk -v 'IGNORECASE=1' '/x-vmware-vcloud-access-token:/ {print $2}' | tr -d 'r')
The legacy path: a VM Apps org upgraded from 8.x
If your VM Apps org was carried forward from an Aria Automation 8.x or vRA deployment, the authentication you already automated still works, and it is a different flow. It is the classic two-step vRA pattern: exchange credentials for a refresh token at the Cloud Services gateway, then exchange that refresh token for a Bearer at the IaaS login endpoint.
# 1. credentials -> refresh token
curl -k 'https://vra.corp.local/csp/gateway/am/api/login'
-H 'Content-Type: application/json'
-d '{"username":"svc-automation","password":"********"}'
# 2. refresh token -> access (Bearer) token
curl -k 'https://vra.corp.local/iaas/api/login'
-H 'Content-Type: application/json'
-d '{"refreshToken":"<refresh token>"}'
The second call returns a token field which is your Bearer. Note the casing difference from the new flow: refreshToken in a JSON body here versus refresh_token as a form field in the OAuth grant. Small thing, real bug, and a fast way to waste twenty minutes if you copy from the wrong runbook.
Lifetimes, rotation and what breaks in production
Once the token chain works in a terminal, the failures move to the schedule. Almost every production auth incident I have seen on this platform traces back to a token lifetime or a rotation setting, not to code.
401 in the middle of a long job
Symptom: the first calls succeed, later calls return 401. Cause: the access token crossed its one-hour expiry. Fix: catch the 401, re-run the refresh grant, retry once. Treat token refresh as a normal step in the loop, not a one-time setup.
406 or a vague 400 on every call
Symptom: the token exchange works but every API call is rejected. Cause: missing or wrong version in the Accept header. Fix: add application/json;version=9.0.0. This is the failure that masquerades as a permissions problem.
Rotation invalidates your stored token
If the account was created with rotation required, every refresh grant returns a new refresh token and invalidates the old one. Symptom: the job works once, then fails the next night with an invalid refresh token. Fix: persist the new refresh token from each response back into your secret store, or create the service account without forced rotation if your secret store cannot do a safe write-back. Decide this deliberately; do not discover it in production.
My Take
If you build one auth helper for VCF Automation, build it around the OAuth refresh grant: store the 90-day refresh token, mint a one-hour access token at the start of every phase, always send the version header, and branch on org type so an upgraded VM Apps org gets the legacy /csp plus /iaas/api/login path instead. Reach for the basic-auth session endpoint only for bootstrap, where you genuinely cannot pre-stage a refresh token, because passing credentials on every run is a worse secret to manage. The trap that catches everyone is assuming the product authenticates one way; in VCF 9 it authenticates four ways depending on plane and org history, and your code has to know which one it is talking to. Get this layer right once and the rest of the API, which we will use heavily from Part 21 onward with the vmware/vcfa Terraform provider, becomes ordinary REST. Pick the one flow your environment actually needs, script the re-auth, and stop hand-pasting Bearers.
References
Broadcom TechDocs · Get Your Access Token for the VCF Automation VM Apps API
vrealize.it · VCF Automation 9 API Access (Christian Ferber)



