Summary and recommendation
The Terraform Cloud REST API follows JSON:API spec throughout - all requests and responses use `Content-Type: application/vnd.api+json`, and every payload wraps fields inside `data.attributes` and `data.relationships`.
Authentication is via Bearer token in the `Authorization` header;
three token types exist (User, Organization, Team), each with different scope ceilings.
Organization tokens cannot call `/account/details`;
that endpoint requires a user token.
Pagination is offset-based using `page[number]` and `page[size]` parameters, with a default page size of 20 and a maximum of 100.
The API enforces a rate limit of 30 requests per second per IP across all plans, returning HTTP 429 on breach.
`X-RateLimit-Limit` and `X-RateLimit-Remaining` headers are present;
`Retry-After` behavior is not explicitly documented.
For identity graph construction, note that user identity is keyed on the username string (e.g., `jsmith`), not a UUID.
There is no lookup-by-email endpoint;
to resolve an email to a membership ID, you must call `GET /api/v2/organizations/{org}/organization-memberships` and filter client-side.
Membership IDs use the `ou-*` prefix and are required for deletion operations.
API quick reference
| Has user API | Yes |
| Auth method | Bearer token (API token in Authorization header) |
| Base URL | Official docs |
| SCIM available | No |
| SCIM plan required | Enterprise |
Authentication
Auth method: Bearer token (API token in Authorization header)
Setup steps
- Log in to Terraform Cloud.
- Navigate to User Settings > Tokens.
- Click 'Create an API token', provide a description, and copy the generated token.
- Include the token in all API requests as: Authorization: Bearer
- Alternatively, use an Organization token (Settings > API Tokens) or Team token for scoped access.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| User API Token | Full access to resources the user can access; acts as the authenticated user. | Account details, organization membership, team membership operations on behalf of the user. |
| Organization API Token | Scoped to a single organization; cannot perform user-specific account operations. | Managing organization memberships and invitations at the org level. |
| Team API Token | Scoped to a single team; limited to team-level operations. | Managing team membership for the specific team. |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | string | Unique identifier for the user (username). | auto-assigned | immutable | Used as the resource ID in JSON:API relationships. |
| username | string | The user's Terraform Cloud username. | required | not updatable via API | Unique across Terraform Cloud. |
| string | The user's email address. | required | updatable via account endpoint | Used for invitations; returned in account details. | |
| avatar-url | string (URL) | URL to the user's avatar image. | auto-assigned | not directly updatable via API | Gravatar-based. |
| is-service-account | boolean | Indicates if the user is a service account. | auto-assigned | immutable | |
| two-factor | object | Two-factor authentication status: {enabled, verified, delivery}. | auto-assigned | managed via UI/account settings | Read-only via API. |
| permissions | object | Computed permissions object for the authenticated user. | auto-assigned | derived from team/org membership | Read-only. |
| status | string | Membership status in organization context (e.g., 'invited', 'active'). | set to 'invited' on org invite | transitions to 'active' on acceptance | Appears on organization-membership objects, not the user object directly. |
Core endpoints
Get current user account details
- Method: GET
- URL:
https://app.terraform.io/api/v2/account/details - Watch out for: Returns details for the token owner only; cannot retrieve arbitrary users by ID via this endpoint.
Request example
GET /api/v2/account/details
Authorization: Bearer <token>
Content-Type: application/vnd.api+json
Response example
{
"data": {
"id": "user-abc123",
"type": "users",
"attributes": {
"username": "jsmith",
"email": "jsmith@example.com",
"avatar-url": "https://..."
}
}
}
List organization memberships
- Method: GET
- URL:
https://app.terraform.io/api/v2/organizations/{organization_name}/organization-memberships - Watch out for: Requires an Organization token or user token with owner permissions. Paginated with page[number]/page[size].
Request example
GET /api/v2/organizations/my-org/organization-memberships
Authorization: Bearer <token>
Response example
{
"data": [
{
"id": "ou-abc123",
"type": "organization-memberships",
"attributes": {
"status": "active",
"email": "jsmith@example.com"
}
}
]
}
Invite user to organization
- Method: POST
- URL:
https://app.terraform.io/api/v2/organizations/{organization_name}/organization-memberships - Watch out for: User receives an email invitation; membership status is 'invited' until accepted. Requires at least one team relationship in the payload.
Request example
POST /api/v2/organizations/my-org/organization-memberships
Content-Type: application/vnd.api+json
{"data":{"type":"organization-memberships","attributes":{"email":"newuser@example.com"},"relationships":{"teams":{"data":[{"type":"teams","id":"team-xyz"}]}}}}
Response example
{
"data": {
"id": "ou-newid",
"type": "organization-memberships",
"attributes": {
"status": "invited",
"email": "newuser@example.com"
}
}
}
Remove user from organization
- Method: DELETE
- URL:
https://app.terraform.io/api/v2/organization-memberships/{organization_membership_id} - Watch out for: Use the organization-membership ID (ou-*), not the username. This removes the user from the org and all its teams.
Request example
DELETE /api/v2/organization-memberships/ou-abc123
Authorization: Bearer <token>
Response example
HTTP 204 No Content
List team members
- Method: GET
- URL:
https://app.terraform.io/api/v2/teams/{team_id}/users - Watch out for: Returns user objects, not team-membership objects. Requires team token, org token, or owner user token.
Request example
GET /api/v2/teams/team-xyz/users
Authorization: Bearer <token>
Response example
{
"data": [
{
"id": "user-abc123",
"type": "users",
"attributes": {"username": "jsmith"}
}
]
}
Add users to team
- Method: POST
- URL:
https://app.terraform.io/api/v2/teams/{team_id}/relationships/users - Watch out for: The user must already be an organization member. The 'id' field is the username string, not a numeric ID.
Request example
POST /api/v2/teams/team-xyz/relationships/users
Content-Type: application/vnd.api+json
{"data":[{"type":"users","id":"jsmith"}]}
Response example
HTTP 204 No Content
Remove users from team
- Method: DELETE
- URL:
https://app.terraform.io/api/v2/teams/{team_id}/relationships/users - Watch out for: Does not remove the user from the organization, only from the specified team.
Request example
DELETE /api/v2/teams/team-xyz/relationships/users
Content-Type: application/vnd.api+json
{"data":[{"type":"users","id":"jsmith"}]}
Response example
HTTP 204 No Content
Show organization membership
- Method: GET
- URL:
https://app.terraform.io/api/v2/organization-memberships/{organization_membership_id} - Watch out for: Membership ID must be known in advance; there is no lookup-by-email endpoint. Retrieve it from the list endpoint first.
Request example
GET /api/v2/organization-memberships/ou-abc123
Authorization: Bearer <token>
Response example
{
"data": {
"id": "ou-abc123",
"type": "organization-memberships",
"attributes": {"status": "active", "email": "jsmith@example.com"}
}
}
Rate limits, pagination, and events
- Rate limits: Terraform Cloud enforces rate limiting on API requests. The documented limit is 30 requests per second per IP address for most endpoints. Responses include rate-limit headers.
- Rate-limit headers: Yes
- Retry-After header: No
- Rate-limit notes: When the rate limit is exceeded, the API returns HTTP 429. The X-RateLimit-Limit and X-RateLimit-Remaining headers are documented. Retry-After header behavior is not explicitly documented in official sources.
- Pagination method: offset
- Default page size: 20
- Max page size: 100
- Pagination pointer: page[number] and page[size]
| Plan | Limit | Concurrent |
|---|---|---|
| All plans | 30 requests/second per IP | 0 |
- Webhooks available: No
- Webhook notes: Terraform Cloud does not offer webhooks for user-management events (user invited, user removed, etc.). Notifications are available for run events only, not user lifecycle events.
- Alternative event strategy: Poll the organization-memberships list endpoint to detect changes, or use audit log streaming (available on Plus/Enterprise) to capture user-related audit events.
SCIM API status
- SCIM available: No
- SCIM version: Not documented
- Plan required: Enterprise
- Endpoint: Not documented
Limitations:
- SCIM provisioning is not natively available in Terraform Cloud as of the current documentation.
- User provisioning via SSO (SAML) is supported on Plus and Enterprise plans, but this is JIT (just-in-time) provisioning, not SCIM.
- The context reference indicates no native SCIM; Enterprise plan is required for SSO/SAML-based JIT provisioning.
- Automated deprovisioning requires manual API calls or IdP-side session termination; there is no SCIM DELETE support.
Common scenarios
Invite and assign: Retrieve the target team ID via GET /api/v2/organizations/{org}/teams, then POST /api/v2/organizations/{org}/organization-memberships with the user's email and team ID in the relationships payload.
The team relationship is mandatory - the API will reject an invite without it.
Poll GET /api/v2/organization-memberships/{id} to detect the invited → active status transition;
the user must accept the email invitation before the membership activates.
Remove from org: List memberships via GET /api/v2/organizations/{org}/organization-memberships, filter by email client-side to obtain the ou-* membership ID, then call DELETE /api/v2/organization-memberships/{id}.
This removes the user from all teams in the org simultaneously.
Verify by confirming the record no longer appears in the list response.
If the user holds an owner role, ensure at least one other owner exists before deletion or the call will be blocked.
Directory sync without SCIM: Retrieve current team members via GET /api/v2/teams/{team_id}/users, diff against your directory source, then POST additions and DELETE removals against /api/v2/teams/{team_id}/relationships/users.
Users being added must already be org members;
run the invite flow first if not.
Schedule this job on a polling interval - no webhooks exist for user-management events.
On Plus/Enterprise with SAML configured, be aware that team assignments set via API may be overridden by SAML attribute mappings on the user's next login.
Invite a new user to an organization and assign to a team
- Obtain the target team ID by calling GET /api/v2/organizations/{org}/teams and locating the team by name.
- POST to /api/v2/organizations/{org}/organization-memberships with the user's email and the team ID in the relationships payload.
- The user receives an invitation email; poll GET /api/v2/organization-memberships/{id} to check when status transitions from 'invited' to 'active'.
Watch out for: The team relationship is mandatory in the invite payload. If the user already has a Terraform Cloud account, they must accept the invitation before the membership becomes active.
Remove a user from the organization
- Call GET /api/v2/organizations/{org}/organization-memberships and filter by email to find the membership ID (ou-*).
- Call DELETE /api/v2/organization-memberships/{organization_membership_id}.
- Verify removal by confirming the membership no longer appears in the list response.
Watch out for: This removes the user from all teams in the org simultaneously. If the user is an owner, ensure another owner exists before removal to avoid losing org access.
Sync team membership from an external directory (no SCIM)
- Retrieve current team members: GET /api/v2/teams/{team_id}/users.
- Retrieve desired members from your directory source.
- For users to add: POST /api/v2/teams/{team_id}/relationships/users (users must already be org members; invite first if not).
- For users to remove: DELETE /api/v2/teams/{team_id}/relationships/users with the list of usernames to remove.
- Schedule this sync job periodically since no webhooks exist for user-management events.
Watch out for: SCIM is not available; all sync logic must be implemented in custom tooling. SSO/SAML team sync (if configured) may override API-set memberships on user login for Plus/Enterprise plans.
Why building this yourself is a trap
SCIM is not natively available at any plan tier in Terraform Cloud. What exists on Plus and Enterprise is SAML-based just-in-time (JIT) provisioning, which creates accounts on first login but does not support automated deprovisioning. Removing access requires explicit API calls or manual admin action;
there is no SCIM DELETE path.
For teams building an identity graph across their SaaS stack, Terraform Cloud's user model introduces two resolution challenges: identity is keyed on username strings rather than stable UUIDs, and there is no direct email-to-user lookup endpoint.
Any pipeline that needs to correlate Terraform Cloud membership with an IdP directory must maintain its own email-to-username mapping, refreshed by polling the memberships list.
Webhooks are absent for all user lifecycle events; run-event notifications are the only webhook surface. This means any automated access review or joiner-mover-leaver workflow must be built on scheduled polling, with the 30 req/s rate limit as the binding constraint for large organizations.
An MCP server with 60+ deep IT/identity integrations can abstract this polling complexity, but the underlying API limitations remain regardless of the integration layer.
Automate Terraform Cloud workflows without one-off scripts
Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.