diff --git a/content/api/table-of-contents.json b/content/api/table-of-contents.json index 29480c5b..506efb6d 100644 --- a/content/api/table-of-contents.json +++ b/content/api/table-of-contents.json @@ -42,6 +42,7 @@ "pages": [ "account.apib", "subaccounts.apib", + "users.apib", "data-privacy.apib" ] }, diff --git a/content/api/users.apib b/content/api/users.apib new file mode 100644 index 00000000..4391a8df --- /dev/null +++ b/content/api/users.apib @@ -0,0 +1,593 @@ +FORMAT: 1A +title: Users API +description: Manage the users on your SparkPost account. +label: New + +# Group Users + +Manage the users on your account: list, invite new ones, change their roles, and remove them. + +### Roles + +The `access_level` field sets a user's role when you invite or update them. The primary-account roles are: + +| Role | Description | +|-------------|-------------------------------------------------------------| +| `admin` | Full access to the account, including user and billing management. | +| `developer` | Access to sending and configuration APIs, without account administration. | +| `reporting` | Read-only access to reporting and analytics. | +| `templates` | Access limited to managing templates. | +| `custom` | A role whose permissions are defined by the `access_policies` you supply. | + +Subaccount-scoped users take `subaccount_reporting` or `subaccount_developer` instead. + +### Access policies + +A user with the `custom` role gets exactly the policies you list in `access_policies`, and nothing else. Supply them as an array of policy names. The field is only valid when `access_level` is `custom`. + +| Policy | Grants | +|--------|--------| +| `alerts/full` | View and manage alerts. | +| `api_keys/full` | Manage API keys. | +| `ab_testing/full` | View and manage A/B tests of email templates. | +| `domains/full` | View and manage sending, bounce, and tracking domains. | +| `events/read` | View and search message events. | +| `ip_pools/full` | View and manage IP pools. | +| `recipient_lists/full` | View and manage recipient lists. | +| `recipient_validation/full` | Validate email addresses and view recent validations. | +| `seeds/full` | View seeding activity and manage seed list settings. | +| `signals_analytics/full` | View and manage analytics metrics, dashboards, and reports, including health score, spam traps, engagement recency, and blocklist incidents. | +| `subaccounts/full` | Manage subaccounts. | +| `suppressions/full` | View and manage suppressions. | +| `templates/full` | View, manage, and preview email templates and snippets. | +| `users/full` | View and manage users. | +| `webhooks/full` | View, manage, and test webhooks. | + +### User object + +Returned by [List Users](#header-list-users) and [Retrieve a User](#header-retrieve-a-user). + +Every user is scoped either to the account or to one or more subaccounts. An account-scoped user has an `access_level`. A subaccount-scoped user has none, and holds a role on each subaccount instead. + ++ Data Structure: Attributes + + username (string) - Unique username that identifies the user. + + first_name (string, nullable) - The user's first name. + + last_name (string, nullable) - The user's last name. + + email (string) - The user's email address. + + access_level (enum) - The user's [role](#header-roles) on the account. Absent for subaccount-scoped users. + + `admin` + + `developer` + + `reporting` + + `templates` + + `custom` + + `access_policies` (array[string]) - The [access policies](#header-access-policies) granted to the user when `access_level` is `custom`. + + is_sso (boolean) - Whether the user signs in via single sign-on. + + email_verified (boolean) - Whether the user has verified their email address. + + tfa_enabled (boolean) - Whether the user has two-factor authentication enabled. + + last_login (string, nullable) - ISO 8601 timestamp of the user's last sign-in, or `null` if they have never signed in. + + subaccounts (array[object]) - The subaccounts the user can reach, and their role on each. See [Subaccount access object](#header-subaccount-access-object). Returned by [Retrieve a User](#header-retrieve-a-user) only, and empty for an account-scoped user. + + created (string) - ISO 8601 timestamp of when the user was created. Returned by [Retrieve a User](#header-retrieve-a-user) only. + + updated (string) - ISO 8601 timestamp of when the user was last updated. Returned by [Retrieve a User](#header-retrieve-a-user) only. ++ Sample + ``` + { + "username": "grace", + "first_name": "Grace", + "last_name": "Hopper", + "email": "grace@example.com", + "access_level": "reporting", + "is_sso": false, + "email_verified": true, + "tfa_enabled": true, + "last_login": "2026-08-02T09:14:05.000Z", + "created": "2015-01-11T08:00:00.000Z", + "updated": "2018-04-11T08:00:00.000Z", + "subaccounts": [] + } + ``` + +### Subaccount access object + +Each object in a user's `subaccounts` array describes one subaccount the user can reach, and the access level they hold on it. + ++ Data Structure: Attributes + + subaccount_id (number) - The subaccount ID. + + subaccount_name (string) - The subaccount's display name. + + access_level (enum) - The user's access level on the subaccount. + + `subaccount_reporting` + + `subaccount_developer` + + status (enum) - The subaccount's status. Returned by [Retrieve a User](#header-retrieve-a-user) only. + + active + + suspended + + terminated ++ Sample + ``` + { + "subaccount_id": 123, + "subaccount_name": "Joe's Garage", + "access_level": "subaccount_reporting", + "status": "active" + } + ``` + +### Invite object + +Returned by [List Pending Invites](#header-list-pending-invites). + ++ Data Structure: Attributes + + id (string) - Unique ID for the pending invite. Use it to revoke the invite. + + email (string) - The email address the invite was sent to. + + from (string) - The email address of the user who created the invite. + + access_level (enum) - The [role](#header-roles) the user takes on when they register. + + `admin` + + `developer` + + `reporting` + + `templates` + + `custom` + + `access_policies` (array[string]) - The [access policies](#header-access-policies) the user takes on when they register, if `access_level` is `custom`. + + subaccounts (array[object]) - The subaccount access the user takes on when they register. See [Subaccount invite object](#header-subaccount-invite-object). Present instead of `access_level` for a subaccount-scoped invite. + + expires (number) - Unix timestamp, in epoch seconds, at which the invitation expires. ++ Sample + ``` + { + "id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301", + "email": "newuser@example.com", + "from": "ada@example.com", + "access_level": "reporting", + "expires": 1720656000 + } + ``` + +### Subaccount invite object + +Each object in the `subaccounts` array of an [Invite a User](#header-invite-a-user) request grants the invitee access to one subaccount. + ++ Data Structure: Attributes + + subaccount_id (number, required) - The subaccount ID. + + access_level (enum, required) - The access level to grant on the subaccount. + + `subaccount_reporting` + + `subaccount_developer` ++ Sample + ``` + { + "subaccount_id": 123, + "access_level": "subaccount_reporting" + } + ``` + +### Invite lifecycle + +Invitations expire after three days. The registration link then stops working and the invitation drops off [List Pending Invites](#header-list-pending-invites). + +To give someone another chance to register, [invite the same email address again](#header-invite-a-user). That creates a second, independent invitation with its own expiry, and the first one keeps working until it expires or you [revoke it](#header-revoke-a-pending-invite). + +### Invite a User [POST /v1/users/invite] + +Creates an invitation and emails a registration link to the address you supply. The invitee follows the link, sets their own password, and takes the role you named in `access_level`. + +Send `access_level` to invite a primary-account user, or `subaccounts` to invite a subaccount-scoped one. A `subaccounts` invite does not need a top-level `access_level`. + +The response contains the invite `id`, which you use to [list](#header-list-pending-invites) and [revoke](#header-revoke-a-pending-invite) the invitation. + ++ Data Structure + + email (string, required) - Email address of the person to invite. Maximum 512 characters. + + access_level (enum) - The primary-account [role](#header-roles) to assign. Required unless `subaccounts` is supplied. `admin` is not available to API keys. + + `developer` + + `reporting` + + `templates` + + `custom` + + `access_policies` (array[string]) - The [access policies](#header-access-policies) to grant. Only valid when `access_level` is `custom`. + + subaccounts (array[object]) - Invite the user with access to one or more subaccounts instead of the primary account. Between 1 and 25 entries. For a full description, see [Subaccount invite object](#header-subaccount-invite-object). + + ++ Request (application/json) + + + Headers + + Authorization: 14ac5499cfdd2bb2859e4476d2e5b1d2bad079bf + + + Body + + { + "email": "newuser@example.com", + "access_level": "reporting" + } + ++ Response 200 (application/json) + + { + "results": { + "id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301" + } + } + ++ Response 400 (application/json) + + { + "errors": [ + { + "message": "email is a required parameter", + "param": "email", + "value": null + } + ] + } + ++ Response 403 (application/json) + + { + "errors": [ + { + "message": "API keys cannot invite an admin-level user." + } + ] + } + ++ Response 429 (application/json) + + { + "errors": [ + { + "message": "Too many invite requests. Please try again later." + } + ] + } + +### List Pending Invites [GET /v1/users/pending-invites] + +Returns the invitations on your account that nobody has accepted yet. Expired invitations do not appear. + ++ Request + + + Headers + + Authorization: 14ac5499cfdd2bb2859e4476d2e5b1d2bad079bf + Accept: application/json + ++ Response 200 (application/json) + + { + "results": [ + { + "id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301", + "email": "newuser@example.com", + "from": "ada@example.com", + "access_level": "reporting", + "expires": 1720656000 + } + ] + } + +### Revoke a Pending Invite [DELETE /v1/users/pending-invites/{id}] + +Revokes a pending invitation. Its registration link stops working immediately. + ++ Parameters + + id (required, string, `3f2504e0-4f89-41d3-9a0c-0305e82c3301`) - The invite ID from [List Pending Invites](#header-list-pending-invites). + ++ Request + + + Headers + + Authorization: 14ac5499cfdd2bb2859e4476d2e5b1d2bad079bf + ++ Response 204 + ++ Response 404 (application/json) + + { + "errors": [ + { + "message": "User invite ID does not exist" + } + ] + } + +### List Users [GET /v1/users] + +Returns the users on your account. + ++ Request + + + Headers + + Authorization: 14ac5499cfdd2bb2859e4476d2e5b1d2bad079bf + Accept: application/json + ++ Response 200 (application/json) + + { + "results": [ + { + "username": "ada", + "first_name": "Ada", + "last_name": "Lovelace", + "email": "ada@example.com", + "access_level": "admin", + "is_sso": false, + "email_verified": true, + "tfa_enabled": false, + "last_login": "2026-07-28T14:22:31.000Z" + }, + { + "username": "katherine", + "first_name": "Katherine", + "last_name": "Johnson", + "email": "katherine@example.com", + "access_level": "custom", + "access_policies": [ + "templates/full", + "events/read", + "signals_analytics/full" + ], + "is_sso": false, + "email_verified": true, + "tfa_enabled": true, + "last_login": null + } + ] + } + ++ Response 403 (application/json) + + { + "errors": [ + { + "message": "Forbidden" + } + ] + } + +### Retrieve a User [GET /v1/users/{username}] + +Returns one user by username, in the [User object](#header-user-object) shape. + ++ Parameters + + username (required, string, `grace`) - The username of the user to retrieve. + ++ Request + + + Headers + + Authorization: 14ac5499cfdd2bb2859e4476d2e5b1d2bad079bf + Accept: application/json + ++ Response 200 (application/json) + + { + "results": { + "username": "grace", + "first_name": "Grace", + "last_name": "Hopper", + "email": "grace@example.com", + "access_level": "reporting", + "is_sso": false, + "email_verified": true, + "tfa_enabled": true, + "last_login": "2026-08-02T09:14:05.000Z", + "created": "2015-01-11T08:00:00.000Z", + "updated": "2018-04-11T08:00:00.000Z", + "subaccounts": [] + } + } + ++ Response 200 (application/json) + + A subaccount-scoped user. + + { + "results": { + "username": "joe", + "first_name": "Joe", + "last_name": "Mechanic", + "email": "joe@example.com", + "is_sso": false, + "email_verified": true, + "tfa_enabled": false, + "last_login": null, + "created": "2015-01-11T08:00:00.000Z", + "updated": "2018-04-11T08:00:00.000Z", + "subaccounts": [ + { + "subaccount_id": 123, + "subaccount_name": "Joe's Garage", + "access_level": "subaccount_reporting", + "status": "active" + } + ] + } + } + ++ Response 404 (application/json) + + { + "errors": [ + { + "message": "User not found." + } + ] + } + +### Update a User's Role [PUT /v1/users/{username}] + ++ Data Structure + + access_level (enum) - The primary-account [role](#header-roles) to assign. + + `developer` + + `reporting` + + `templates` + + `custom` + + `access_policies` (array[string]) - The [access policies](#header-access-policies) to grant. Only valid when `access_level` is `custom`. + + ++ Parameters + + username (required, string, `grace`) - The username of the user to update. + ++ Request (application/json) + + + Headers + + Authorization: 14ac5499cfdd2bb2859e4476d2e5b1d2bad079bf + + + Body + + { + "access_level": "developer" + } + ++ Response 200 (application/json) + + { + "results": { + "message": "Successfully modified user grace" + } + } + ++ Response 400 (application/json) + + { + "errors": [ + { + "message": "Cannot change the access level of the last admin user on the account." + } + ] + } + ++ Response 403 (application/json) + + { + "errors": [ + { + "message": "API keys may only modify access_level and access_policies." + } + ] + } + +### Delete a User [DELETE /v1/users/{username}] + +Deletes a user from your account. + ++ Parameters + + username (required, string, `grace`) - The username of the user to delete. + ++ Request + + + Headers + + Authorization: 14ac5499cfdd2bb2859e4476d2e5b1d2bad079bf + ++ Response 204 + ++ Response 400 (application/json) + + { + "errors": [ + { + "message": "Cannot delete the last admin user on the account." + } + ] + } + ++ Response 403 (application/json) + + { + "errors": [ + { + "message": "Cannot delete current user." + } + ] + } + ++ Response 404 (application/json) + + { + "errors": [ + { + "message": "User does not exist" + } + ] + } + +### Grant Subaccount Access [POST /v1/users/{username}/subaccounts] + +Grants a user access to a subaccount at the given access level. If the user already has access to that subaccount, the call updates their access level instead. + +The user must already have access to at least one subaccount. Calling this endpoint for a primary-account user returns `400`. To give a new person subaccount access, [invite them](#header-invite-a-user) with a `subaccounts` array. + ++ Data Structure + + subaccount_id (number, required) - The subaccount to grant access to. + + access_level (enum, required) - The access level to grant on the subaccount. + + `subaccount_reporting` + + `subaccount_developer` + + ++ Parameters + + username (required, string, `grace`) - The username of the user. + ++ Request (application/json) + + + Headers + + Authorization: 14ac5499cfdd2bb2859e4476d2e5b1d2bad079bf + + + Body + + { + "subaccount_id": 123, + "access_level": "subaccount_reporting" + } + ++ Response 200 (application/json) + + { + "results": { + "message": "Subaccount access granted" + } + } + ++ Response 400 (application/json) + + { + "errors": [ + { + "message": "access_level must be one of: subaccount_reporting, subaccount_developer" + } + ] + } + ++ Response 400 (application/json) + + The user is not subaccount-scoped. + + { + "errors": [ + { + "message": "Invalid user" + } + ] + } + +### Revoke Subaccount Access [DELETE /v1/users/{username}/subaccounts/{subaccountId}] + +Removes a user's access to a subaccount. + ++ Parameters + + username (required, string, `grace`) - The username of the user. + + subaccountId (required, number, `123`) - The subaccount to revoke access to. + ++ Request + + + Headers + + Authorization: 14ac5499cfdd2bb2859e4476d2e5b1d2bad079bf + ++ Response 204 + ++ Response 400 (application/json) + + { + "errors": [ + { + "message": "Cannot delete last subaccount for user" + } + ] + }