Invite console users
Queues console invites for up to 100 email addresses on a rooftop.
Checks that the caller has permission to create users on the rooftop. Then, for each email, it creates or refreshes a pending invite for the rooftop with a signed invite token, role, designation, phone and communication preferences. An email that already has an invite for this rooftop and dealer group gets that invite refreshed. Emails are not sent by this call. A scheduled job sends emails for pending invites later.
With resellerId, existing users are given reseller ownership directly instead of being invited. Only callers who own that reseller can do this.
Before you call it
-
You need
enterpriseId,teamIdandteamName. -
Each user needs
emailandroleName. -
The UI offers these role values:
TEAM_STANDARD(Member),TEAM_ADMIN(Admin) andENTERPRISE_OWNER(shown, but disabled). -
Authentication: Bearer token of a signed-in console user, plus the
CREATE_USER_MULTIPLE(write) permission on the rooftop and dealer group. -
Rate limit: Not documented
-
Processing: Synchronous for the database writes. Emails are sent later by a scheduled job.
Request
- Build
userListwith 1 to 100 entries. Each entry needsemailandroleName. - Avoid apostrophes in
userNameanddesignation(see Errors). - On 2xx, call Mark a checklist task done with
{teamId, taskName: "User Setup", isCompleted: true}.
| Parameter | Type | Required | Description |
|---|---|---|---|
| Headers | — | — | Sent as HTTP headers. |
Authorization | string | Required | Bearer YOUR_TOKEN |
Content-Type | string | Required | application/json |
| Body | — | — | Sent as a JSON body. |
enterpriseId | string | Required | Dealer group ID. |
teamId | string | Required | Rooftop ID. |
teamName | string | Required | Rooftop name. It is echoed back in the response. |
resellerId | string | Optional | Reseller flow only (see the top of this page). |
userList | array | Required | 1 to 100 users. |
userList[].email | string | Required | Email. It is trimmed and lower-cased. |
userList[].roleName | string | Required | Role for the invite token, e.g. TEAM_STANDARD. |
userList[].teamRole | string | Optional | Rooftop role stored on the invite. The UI sends the same value as roleName. |
userList[].userName | string | Optional | Display name. |
userList[].designation | string | Optional | Job title. |
userList[].isdCode | string | Optional | e.g. +1. |
userList[].contactNo | string | Optional | Phone number. |
userList[].department | string | Optional | Department. The UI sends "". |
userList[].communicationPreference | object | Optional | {studioAi: {email: [...]}, conversationAi: {email: [...], sms: [...], department: [...]}}. |
userList[].registered | boolean | Optional | Sent by the UI (false). Not read by the endpoint. |
Sample request
curl -X POST "https://api.spyne.ai/console/v1/user/send-invite-links" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"enterpriseId": "<enterpriseId>",
"teamId": "<teamId>",
"teamName": "Example Motors",
"userList": [
{
"email": "[email protected]",
"roleName": "TEAM_STANDARD",
"teamRole": "TEAM_STANDARD",
"userName": "John Roe",
"designation": "BDC Manager",
"isdCode": "+1",
"contactNo": "5550100001",
"department": "",
"communicationPreference": { "studioAi": { "email": [] }, "conversationAi": { "email": ["Daily", "Post Call", "Campaign"], "sms": ["Post Call"], "department": [] } }
}
]
}'import requests
payload = {
"enterpriseId": "<enterpriseId>",
"teamId": "<teamId>",
"teamName": "Example Motors",
"userList": [{
"email": "[email protected]",
"roleName": "TEAM_STANDARD",
"teamRole": "TEAM_STANDARD",
"userName": "John Roe",
"designation": "BDC Manager",
"isdCode": "+1",
"contactNo": "5550100001",
"department": "",
"communicationPreference": {"studioAi": {"email": []},
"conversationAi": {"email": ["Daily", "Post Call", "Campaign"], "sms": ["Post Call"], "department": []}},
}],
}
res = requests.post(
"https://api.spyne.ai/console/v1/user/send-invite-links",
json=payload,
headers={"Authorization": "Bearer YOUR_TOKEN"},
)
print(res.status_code, res.json())const res = await fetch("https://api.spyne.ai/console/v1/user/send-invite-links", {
method: "POST",
headers: { Authorization: "Bearer YOUR_TOKEN", "Content-Type": "application/json" },
body: JSON.stringify({
enterpriseId: "<enterpriseId>",
teamId: "<teamId>",
teamName: "Example Motors",
userList: [{
email: "[email protected]",
roleName: "TEAM_STANDARD",
teamRole: "TEAM_STANDARD",
userName: "John Roe",
designation: "BDC Manager",
isdCode: "+1",
contactNo: "5550100001",
department: "",
communicationPreference: { studioAi: { email: [] }, conversationAi: { email: ["Daily", "Post Call", "Campaign"], sms: ["Post Call"], department: [] } },
}],
}),
});
console.log(res.status, await res.json());Sample response
{
"message": "Invite sent successfully",
"error": false,
"code": "",
"details": null,
"data": {
"teamId": "<teamId>",
"teamName": "Example Motors",
"enterpriseId": "<enterpriseId>",
"invitedEmails": ["[email protected]"],
"addedDirectlyEmails": []
}
}{ "message": "Invalid Token", "error": true, "code": "ERROR_AUTH_401", "details": "", "data": null }{ "message": "Not authorized to invite on behalf of reseller <resellerId>", "error": true, "code": "ERROR_FORBIDDEN_403", "details": null, "data": null }{ "message": "No suitable permission found", "error": true, "code": "ERROR_NOT_FOUND_404", "details": null, "data": null }{ "message": "...", "error": true, "code": "ERROR_REQ_TIMEOUT_408", "details": "", "data": null }{ "message": "General API error. Please try again later.", "error": true, "code": "ERROR_SERVER_500", "details": { "name": "Error", "message": "..." } }400 Bad Request (missing field, empty list, more than 100 users, or an entry without email/roleName). The response body is the framework's default input-validation error. Its shape is not defined in the code. The validation messages are User list cannot be empty, User list size must be less than 100 and User list must follow [{email, roleName}].
Response fields
| Parameter | Type | Description |
|---|---|---|
data.teamId, teamName, enterpriseId | string | Echo of the request. |
data.invitedEmails | string[] | Emails that now have a pending invite. |
data.addedDirectlyEmails | string[] | Reseller flow only: existing users granted access without an invite. Otherwise []. |
Errors
| Status | Reason | When it happens |
|---|---|---|
400 | Invalid input | enterpriseId, teamId, teamName or userList is missing, or userList breaks the rules above. |
401 | ERROR_AUTH_401 | Missing or invalid token. |
403 | ERROR_FORBIDDEN_403 | Reseller flow: the caller does not own the reseller, or the dealer group does not belong to it. |
404 | No suitable permission found | Caller lacks CREATE_USER_MULTIPLE on this rooftop. Note that this returns 404, not 403. |
408 | ERROR_REQ_TIMEOUT_408 | Token check could not reach the auth service. |
500 | ERROR_SERVER_500 | Database error. Values are placed into the database query as text, so an apostrophe in userName, designation, isdCode or contactNo (for example "O'Brien") makes the write fail with 500. |
Next
On 2xx, call Mark a checklist task done with {teamId: "<teamId>", taskName: "User Setup", isCompleted: true}. The UI does this without waiting for the result. Refresh the list with List console users; new invites show active: "Invite Sent".
- Called before: List console users.
- Called after: Mark a checklist task done.
Updated about 2 hours ago
