docs: add Users API reference page#586
Draft
danielfsousa wants to merge 3 commits into
Draft
Conversation
Document the option-gated Users API exposed by the api_key-role `users/manage-programmatic` grant (label "Users: Manage"): list users, invite a user, list/revoke pending invites, update and delete a user, and manage a user's subaccount mappings. Covers the prerequisites (the `allow_user_management_via_api` account option enabled by support, plus an admin attaching the grant via the web app), the invite-based-only creation flow and treating the invite token as a credential, the update field deny-list (password, tfa_enabled, is_sso, email), and the role-ceiling / last-admin / key-revocation constraints. Registered under the Accounts category in the table of contents. Closes #584 Part of SparkPost/access#121 Do not merge/publish until the feature ships. Implementation PRs: SparkPost/access#124, SparkPost/accusers-api#1294, SparkPost/auth-api#319. Claude-Session: https://claude.ai/code/session_01AE1PyieVr2SdnCwF5A4xxU
Remove non-public details: the account option key, the internal auth_migrated user field, and the PUT restricted-fields enumeration. Prerequisites and the update endpoint are now worded positively (what is enabled / what is updatable) rather than naming internal config or forbidden fields. Add the invite lifecycle (3-day expiry, expired invites drop off the pending list automatically, no resend — re-invite instead, 429 when rate limited), the Retrieve a User and List a User's Subaccounts endpoints, and the canonical GET /v1/users/pending-invites listing (replacing the legacy /all variant). The Invite object now carries access_level and expires. Claude-Session: https://claude.ai/code/session_01AE1PyieVr2SdnCwF5A4xxU
These docs publish at GA, after the account-option gate is removed, so there is no "enabled by SparkPost support" prerequisite at publish time; remove that wording and the contact-support banner. Document both grants: `Users: View` (read-only, GET endpoints) and `Users: Manage` (full management; required by every write endpoint). The only remaining prerequisite is that an admin attaches the grant to the key via the web app. Claude-Session: https://claude.ai/code/session_01AE1PyieVr2SdnCwF5A4xxU
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a public API reference page for the Users API — the endpoints exposed by the option-gated,
api_key-role user-management grants that let an API key manage the users on an account.New page
content/api/users.apib, registered under the Accounts category incontent/api/table-of-contents.json(aftersubaccounts.apib).Everything in the
.apibis public. Implementation-internal details are intentionally kept out of the page and captured in Reviewer notes below instead.Endpoints documented
GET /api/v1/users— list usersGET /api/v1/users/:username— retrieve a userPOST /api/v1/users/invite— invite a user (creation is invite-based only)GET /api/v1/users/pending-invites— list pending invites (canonical path)DELETE /api/v1/users/pending-invites/:id— revoke a pending invitePUT /api/v1/users/:username— update a userDELETE /api/v1/users/:username— delete a userGET /api/v1/users/:username/subaccounts— list a user's subaccount mappingsPOST /api/v1/users/:username/subaccounts/DELETE /api/v1/users/:username/subaccounts/:subaccountId— manage subaccount mappingsAlso covered
api_key-role grants, attached by an admin through the web app (never by another API key):Users: Viewfor read-only access (theGETendpoints) orUsers: Managefor full management (required by every write endpoint). No account-option / support-enablement step is documented — see the GA rollout note under Reviewer notes.token, documented as a credential. Invitations expire after 3 days; expired invites drop off the pending list automatically; there is no resend (re-invite the same email to create a new independent invitation); invite creation is rate limited (429).first_name,last_name,access_level,access_policies, andoptionsare updatable.Reviewer notes (not in public docs)
These are the implementation-internal details behind the public wording, for reviewer context only. They are deliberately not in the
.apib:allow_user_management_via_api(set only by SparkPost support) — that gate is expected to be gone by the time these docs go live.password,tfa_enabled, andis_ssoare rejected with403;emailis currently silently ignored (generateUpdateFields, "Don't update email address, see AC-297") and is slated to also become a403via the deny-list once the accusers-api change lands (see verification note below).429is driven byinviteThrottle(config.emailThrottling.invite):maxPerRecipient= 3,maxPerRequester= 20, over attlof 3600s (1-hour window). The public docs intentionally omit these numbers.config.emailInvite.ttl= 259200s. Expired invites are filtered out of the listing (invite-dynamo.getInvitesisExpiredcheck) and DynamoDB TTL reaps the records.Verification status
Request/response shapes and error bodies were verified against the accusers-api source (
resources/user-controller.js,resources/user-endpoints.js,lib/validator/*,lib/models/*,config/default.json) and the hardening PR SparkPost/accusers-api#1294, and the grant patterns/labels against@sparkpost/access(lib/token-access.js) and access PR SparkPost/access#124.Verified against current code: list users, invite (request/response incl.
token,400,429), revoke invite, update user (updatable fields,Successfully modified user <username>, last-admin400), delete user, add/remove subaccount mapping, the invite lifecycle facts (3-day TTL, expired-invite filtering, throttle →429), and the existing role-ceiling / last-admin / key-revocation constraints. TheUsers: Viewgrant (label "Users: View", read-only GET surface: list, single-user, subaccount mappings, pending invites) is present in the accessusers-manage-programmaticworking branch.Could NOT yet verify against the pushed PR #1294 / access #124 — documented per the agreed spec and flagged as forthcoming. As of their latest pushes, the pushed PRs don't yet contain these; they line up with the accusers-api work items still in progress (retrieve-user hardening, new subaccounts-list endpoint, pending-invites enrichment + canonical alias, email deny-list) and the access grant additions:
Users: Viewgrant — defined in the access working branch but not yet in the pushed access Add redirects for subaccounts docs changes #124 diff. Confirm it ships in the release.GET /api/v1/users/:usernamegrant coverage and a grant-scoped response shape. The page documents it returning the list-item shape; the currentgetUserhandler returns a richer object (includingtokensandauth_connection) that must not be exposed under these grants. Confirm the grant-scoped response matches the documented list-item shape.GET /api/v1/users/:username/subaccounts— no such route exists yet; response shape (subaccount_id,subaccount_name,access_level,status) is modeled on the existing per-user subaccounts array. Confirm route + shape.GET /api/v1/users/pending-invitescanonical path — only/pending-invites/allexists today. Confirm the canonical path is added to the route and both grants.access_level+expires(epoch seconds) — the current listing returns only{id, email, from}. Confirm the enriched fields.emailon PUT →403— current deny-list ispassword/tfa_enabled/is_ssoonly;emailis silently ignored. Confirm email is added to the deny-list.Users: ManageGET coverage — the manage grant's currentget_grantscover only/usersand/pending-invites/all; single-user and subaccounts-list GETs are covered byUsers: View. Confirm manage keys can also reach those read endpoints (or that the docs should scope those reads toUsers: View).This documents an unreleased feature. Hold until the implementation ships:
@sparkpost/access4.13.0)Closes #584
Part of SparkPost/access#121
https://claude.ai/code/session_01AE1PyieVr2SdnCwF5A4xxU