Create an invite
/api/v1/identity-invitesAuthentication
- Bearer Token
AuthorizationJWT access token
- API Key
X-API-KeyAPI key for management-tier access
Request body
client_idstringOAuth client ID — determines which app the invite links to. If omitted, uses Canopy hosted fallback.
intentenum: "activate" | "password_reset" | "onboard"Optional. `activate` (default) creates a net-new identity OR — if an identity with this email already exists in the Account but has no active membership in this App — auto-derives an `add_to_app` invite that adds them to this App without touching their existing password. `password_reset` is the explicit admin-driven credential-rotation flow for an existing identity; it cannot carry a role/node assignment. The legacy `onboard` value is accepted and treated as `activate`.
email *stringfirst_namestringRequired for `activate`. Ignored for `add_to_app` (the existing identity's name wins) and for `password_reset`.
last_namestringRequired for `activate`. Ignored for `add_to_app` and `password_reset`.
role_idstringRole ID — required if node_id is provided
node_idstringNode ID — required if role_id is provided
send_emailbooleanWhether Canopy should send the invite email. Set false to suppress delivery and handle it yourself — the API response includes accept_url with the tokenized link. Defaults to true.
Code samples
curl -X POST "https://api.canopy.dev/api/v1/identity-invites" \
-H "X-API-Key: $CANOPY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"client_id": "string",
"intent": "activate",
"email": "string",
"first_name": "string",
"last_name": "string",
"role_id": "string",
"node_id": "string",
"send_email": true
}'const response = await fetch("https://api.canopy.dev/api/v1/identity-invites", {
method: "POST",
headers: {
"X-API-Key": "$CANOPY_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
"client_id": "string",
"intent": "activate",
"email": "string",
"first_name": "string",
"last_name": "string",
"role_id": "string",
"node_id": "string",
"send_email": true
}),
});
const data = await response.json();import requests
response = requests.post(
"https://api.canopy.dev/api/v1/identity-invites",
headers={
"X-API-Key": "$CANOPY_API_KEY",
"Content-Type": "application/json"
},
json={
"client_id": "string",
"intent": "activate",
"email": "string",
"first_name": "string",
"last_name": "string",
"role_id": "string",
"node_id": "string",
"send_email": True,
},
)
data = response.json()package main
import (
"bytes"
"encoding/json"
"net/http"
)
func main() {
payload := map[string]interface{}{
"client_id": "string",
"intent": "activate",
"email": "string",
"first_name": "string",
"last_name": "string",
"role_id": "string",
"node_id": "string",
"send_email": true,
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", "https://api.canopy.dev/api/v1/identity-invites", bytes.NewBuffer(body))
req.Header.Set("X-API-Key", "$CANOPY_API_KEY")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
{
"id": "string",
"email": "string",
"intent": "activate",
"first_name": "string",
"last_name": "string",
"name": "string",
"role_id": "string",
"node_id": "string",
"has_initial_assignment": false,
"status": "pending",
"expires_at": "2026-04-20T12:00:00.000Z",
"invited_by": "string",
"created_at": "2026-04-20T12:00:00.000Z",
"accept_url": "string"
}application/json
id *stringemail *stringintent *enum: "activate" | "add_to_app" | "password_reset" | "onboard"Final intent stamped on the invite at create time. `activate` is the auto-derived default for net-new identities; `add_to_app` is auto-derived when the email matches an existing account-level identity that has no membership in this App; `password_reset` is admin-explicit. `onboard` may appear on rows created before the rename.
first_name *stringlast_name *stringname *stringrole_idstringnode_idstringhas_initial_assignment *booleanstatus *enum: "pending" | "accepted" | "revoked" | "expired"expires_at *string (date-time)invited_by *stringcreated_at *string (date-time)accept_url *stringTokenized URL the invitee would land on. Returned so callers that pass send_email=false can deliver it themselves.