Invite console users

Queues console invites for up to 100 email addresses on a rooftop.

POSThttps://api.spyne.ai/console/v1/user/send-invite-links
AuthBearer token (console user) with CREATE_USER_MULTIPLE permissionRate limitNot documentedProcessingSynchronous database writes; emails are sent later by a scheduled job.

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, teamId and teamName.

  • Each user needs email and roleName.

  • The UI offers these role values: TEAM_STANDARD (Member), TEAM_ADMIN (Admin) and ENTERPRISE_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

Requiredalways sendOptionalcan be left out
Request checklist
  1. Build userList with 1 to 100 entries. Each entry needs email and roleName.
  2. Avoid apostrophes in userName and designation (see Errors).
  3. On 2xx, call Mark a checklist task done with {teamId, taskName: "User Setup", isCompleted: true}.
ParameterTypeRequiredDescription
Headers——Sent as HTTP headers.
AuthorizationstringRequiredBearer YOUR_TOKEN
Content-TypestringRequiredapplication/json
Body——Sent as a JSON body.
enterpriseIdstringRequiredDealer group ID.
teamIdstringRequiredRooftop ID.
teamNamestringRequiredRooftop name. It is echoed back in the response.
resellerIdstringOptionalReseller flow only (see the top of this page).
userListarrayRequired1 to 100 users.
userList[].emailstringRequiredEmail. It is trimmed and lower-cased.
userList[].roleNamestringRequiredRole for the invite token, e.g. TEAM_STANDARD.
userList[].teamRolestringOptionalRooftop role stored on the invite. The UI sends the same value as roleName.
userList[].userNamestringOptionalDisplay name.
userList[].designationstringOptionalJob title.
userList[].isdCodestringOptionale.g. +1.
userList[].contactNostringOptionalPhone number.
userList[].departmentstringOptionalDepartment. The UI sends "".
userList[].communicationPreferenceobjectOptional{studioAi: {email: [...]}, conversationAi: {email: [...], sms: [...], department: [...]}}.
userList[].registeredbooleanOptionalSent 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

ParameterTypeDescription
data.teamId, teamName, enterpriseIdstringEcho of the request.
data.invitedEmailsstring[]Emails that now have a pending invite.
data.addedDirectlyEmailsstring[]Reseller flow only: existing users granted access without an invite. Otherwise [].

Errors

StatusReasonWhen it happens
400Invalid inputenterpriseId, teamId, teamName or userList is missing, or userList breaks the rules above.
401ERROR_AUTH_401Missing or invalid token.
403ERROR_FORBIDDEN_403Reseller flow: the caller does not own the reseller, or the dealer group does not belong to it.
404No suitable permission foundCaller lacks CREATE_USER_MULTIPLE on this rooftop. Note that this returns 404, not 403.
408ERROR_REQ_TIMEOUT_408Token check could not reach the auth service.
500ERROR_SERVER_500Database 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".


Did this page help you?