Introduction
This article describes how to programmatically send an organization invitation and have the invitee accept it using the Terraform Enterprise API.
Expected Outcome
An administrator generates an organization invitation via an automated process, and the invitee programmatically becomes a member of the organization through an automated workflow.
Prerequisites
To complete this procedure, you need the following:
- The user must already exist in Terraform Enterprise.
- The user must not already be a member of the target organization.
- An API token for the invitee user.
- The email address of the invitee registered in Terraform Enterprise.
- The ID of the target team for the invitee (e.g.,
team-xxxxxxxxx). - An API token for the host organization with permissions to generate invitations.
- The name of the target organization.
Note: For users associated with a SAML provider, the API token may expire, causing a 401 Unauthorized error. This is a result of the API token expiration policy. A 401 error can also occur if the API token is invalid and needs to be regenerated.
Use Case
Terraform Enterprise administrators may need to automate user membership management for organizations.
Procedure
- Prepare the required information. This guide uses placeholder variables for the following data:
- Invitee API Token:
INVITEE_API_TOKEN - Invitee Email Address:
INVITEE_EMAIL - Team ID:
TEAM_ID - Host Organization API Token:
HOST_API_TOKEN - Organization Name:
ORGANIZATION_NAME - Terraform Enterprise Hostname:
tfe.example.com
- Invitee API Token:
-
Create a JSON payload file named
invite-payload.jsonwith the invitee's email and target team ID. Refer to the user invitation API documentation for payload structure.{ "data": { "attributes": { "email": "INVITEE_EMAIL" }, "relationships": { "teams": { "data": [ { "type": "teams", "id": "TEAM_ID" } ] } }, "type": "organization-memberships" } } -
Send the invitation using the user invitation API. This command sends the request and extracts the new membership ID from the response.
$ TOKEN=HOST_API_TOKEN $ MEMBERSHIP_ID=$(curl \ --header "Authorization: Bearer $TOKEN" \ --header "Content-Type: application/vnd.api+json" \ --request POST \ --data @invite-payload.json \ https://tfe.example.com/api/v2/organizations/ORGANIZATION_NAME/organization-memberships | jq -r '.data.id') - The previous command returns the organization membership ID (e.g.,
ou-xxxxxxxxxxxxxx) and stores it in theMEMBERSHIP_IDvariable. -
Create a second payload file named
accept-payload.jsonto accept the invitation. This payload sets the membership status toactive.{ "data": { "id": "MEMBERSHIP_ID", "type": "organization-memberships", "attributes": { "status": "active" } } } - Replace
MEMBERSHIP_IDinaccept-payload.jsonwith the value captured in step 3. -
As the invitee, accept the invitation by sending a
PATCHrequest to the membership update API endpoint using the invitee's API token.$ TOKEN=INVITEE_API_TOKEN $ curl \ --header "Authorization: Bearer $TOKEN" \ --header "Content-Type: application/vnd.api+json" \ --request PATCH \ --data @accept-payload.json \ https://tfe.example.com/api/v2/organization-memberships/$MEMBERSHIP_ID
Outcome
A successful operation returns a JSON response confirming the membership status is active.
{
"data": {
"id": "ou-SHARn8cSA1vG3NBP",
"type": "organization-memberships",
"attributes": {
"status": "active",
"email": "testuser@myorg.com",
"created-at": "2021-10-27T02:37:12.067Z"
},
"relationships": {
"teams": {
"data": []
},
"user": {
"data": {
"id": "user-CDwbMfJ2nruR7AwK",
"type": "users"
}
},
"organization": {
"data": {
"id": "myorg",
"type": "organizations"
}
}
},
"included": []
}
}Common Errors
-
401 Unauthorized: If the API returns a
401error, the user's API token may be invalid or expired, especially for SAML users. The user may need to sign in to Terraform Enterprise again to refresh their session.{ "errors": [ { "status": "401", "title": "unauthorized" } ] } -
403 Forbidden: If the API returns a
403error with the detailYou cannot update a membership for different user, the API token used in the acceptance step does not match the user associated with the membership ID.{ "errors": [ { "status": "403", "title": "forbidden", "detail": "You cannot update a membership for different user" } ] }