Users API

Provision accounts on Bodek programmatically: create individual end-users, create business accounts with their own organization workspace, invite people to organization workspaces, and send password-reset emails for accounts you created. It's the building block for partners who onboard their own customers onto the platform.

Base   https://dev.bodek.us/v1/users
Auth   Authorization: Bearer bf_live_…   (a key created for the "users" service)
EndpointScopePurpose
POST /v1/users
users:createCreate an individual account
POST /v1/users/business
business:createCreate a business account + organization workspace
POST /v1/users/workspace-members
workspace:inviteInvite someone to an organization workspace
POST /v1/users/password-reset
users:password_resetEmail a password-reset link to an account you created

This reference is public. Creating a key, however, requires approval — see below. The Users API is served by dev.bodek.us itself; there is no separate direct host.

Getting access

The Users API is approval-gated to keep account creation safe. Before you can create a key:

  1. Submit the API access request with your use case.
  2. Once an administrator approves it (you'll be emailed), a Users option appears when you create a key in the console.
  3. Create a Users key, choosing the scopes it needs, and start calling the API.

Approval is checked on every request: if it's revoked, existing keys stop working immediately with 403 access_not_granted. Every call is recorded in your key's audit log.

Authentication

Send your key as a bearer token. Keys for other services (files, forms, iam) are rejected with 403 wrong_service.

Authorization: Bearer bf_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json

If the key has an allowed-domains list, browser requests must come from one of those origins (see Origin restrictions). Because these endpoints create accounts and send email, call them from your server, never from a browser.

Requests & responses

  • Every endpoint is POST with a JSON body. Other methods return 405 method_not_allowed (after the key is authenticated).
  • Successful calls return 201 with the result object at the top level — unlike the Files and Forms APIs, there is no data envelope.
  • Errors use the standard envelope: {"error": {"code": "…", "message": "…", "details": {…}}}.
  • Each response carries an X-Request-Id header; quote it in support requests.
  • Emails are trimmed and lower-cased. Names are trimmed and truncated to 80 characters, phone to 40.
  • There are no read, list, update or delete endpoints.

Create a user

POST /v1/users

Scope users:create. Creates an individual account plus its personal workspace (5 GB storage) and queues a welcome email plus a single-use link (valid 72 hours) for the user to choose their own password. Integrations never set or see end-user passwords.

FieldDescription
emailrequiredValid, unique email address.
first_namerequiredUp to 80 characters.
last_namerequiredUp to 80 characters.
phoneoptionalFree text, up to 40 characters (stored encrypted).
curl -X POST https://dev.bodek.us/v1/users \
  -H "Authorization: Bearer bf_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jordan@example.com",
    "first_name": "Jordan",
    "last_name": "Lee",
    "phone": "+1 555 0100"
  }'

Returns 201:

{
  "id": 8123,
  "email": "jordan@example.com",
  "first_name": "Jordan",
  "last_name": "Lee",
  "account_type": "individual",
  "workspace_id": 9001,
  "email_verified": false
}

Sending a password field is rejected with 400 unsupported_parameter. If the link expires, use Send a password reset.

Accounts created through the API are recorded as created by your account (which is what allows you to send them password-reset emails later) and are sponsored by the key owner for billing purposes.

Create a business

POST /v1/users/business

Scope business:create. Creates a business account with its personal workspace, plus an organization workspace (10 GB storage) owned by that user, and queues a welcome email. Takes the same fields as Create a user, plus:

FieldDescription
organization_namerequiredName of the organization workspace. org_name is accepted as an alias.
einoptionalUS employer id, 12-3456789 or 123456789.
curl -X POST https://dev.bodek.us/v1/users/business \
  -H "Authorization: Bearer bf_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "owner@acme.co",
    "first_name": "Sam",
    "last_name": "Rivera",
    "organization_name": "Acme Co",
    "ein": "12-3456789"
  }'
{
  "id": 8124,
  "email": "owner@acme.co",
  "first_name": "Sam",
  "last_name": "Rivera",
  "account_type": "business",
  "organization": { "workspace_id": 9002, "name": "Acme Co" },
  "email_verified": false
}

Add a user to a workspace

POST /v1/users/workspace-members

Scope workspace:invite. Emails an invitation to join an organization workspace. The key's owner must be the owner or an admin of the target workspace, and it must be an organization (not a personal) workspace. The invitee doesn't need a Bodek account yet.

FieldDescription
workspace_idrequiredOrganization workspace id (e.g. organization.workspace_id from Create a business).
emailrequiredInvitee's email.
roleoptionalmember (default) or admin.
curl -X POST https://dev.bodek.us/v1/users/workspace-members \
  -H "Authorization: Bearer bf_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "workspace_id": 9002,
    "email": "teammate@acme.co",
    "role": "member"
  }'
{
  "workspace_id": 9002,
  "email": "teammate@acme.co",
  "role": "member",
  "status": "invited",
  "invited_at": "2026-09-23T15:40:00+00:00"
}

The invitation link is valid for 14 days; the person joins when they accept it. Inviting an email that already has a pending (or removed) membership re-issues the invitation with the new role and a fresh link. An email that is already an active member returns 409 already_member.

Send a password reset

POST /v1/users/password-reset

Scope users:password_reset. Emails a single-use link (valid 30 minutes) to an account that your integration created (any key belonging to your developer account), so the user can choose a new password. The API never sets or returns passwords, and you can't target arbitrary Bodek accounts. At most 5 reset emails per address per hour.

FieldDescription
user_idone ofThe account id returned at creation.
emailone ofUsed when user_id isn't given.
curl -X POST https://dev.bodek.us/v1/users/password-reset \
  -H "Authorization: Bearer bf_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "email": "jordan@example.com" }'
{ "id": 8123, "email": "jordan@example.com", "reset": "email_sent" }

An account that doesn't exist and one your integration didn't create both return 404 user_not_found. Sending new_password is rejected with 400 unsupported_parameter.

Scopes

Choose scopes when creating a Users key in the console.

ScopeGrants
users:createPOST /v1/users — create individual end-user accounts.
business:createPOST /v1/users/business — create business accounts with an organization workspace.
workspace:invitePOST /v1/users/workspace-members — invite members to organization workspaces the key owner administers.
users:password_resetPOST /v1/users/password-reset — email a password-reset link to accounts your integration created.

Rate limits

Each key has the per-minute and per-day caps chosen when it was created, but Users keys are capped at 30/minute and 1,000/day. On top of that, all Users keys of one developer account share a ceiling of 60 requests/minute and 2,000/day, and at most 200 new accounts per rolling 24 hours (429 account_creation_limit). Re-inviting the same address to the same workspace within 10 minutes returns 429 invite_recently_sent. Every request, including failed ones, counts. Responses that pass authentication carry X-RateLimit-Limit-Minute, X-RateLimit-Remaining-Minute, X-RateLimit-Limit-Day and X-RateLimit-Remaining-Day. Over the limit you get 429 rate_limited with error.details.limit.

Errors

StatusCodeMeaning
400invalid_jsonThe body isn't valid JSON.
400invalid_emailMissing or invalid email.
400missing_namefirst_name and last_name are required.
400unsupported_parameterpassword / new_password sent — users set their own passwords via the emailed link.
400missing_organizationorganization_name is required for a business.
400invalid_einEIN isn't 9 digits (12-3456789).
400missing_workspaceworkspace_id is required.
400invalid_rolerole must be member or admin.
400not_an_org_workspaceMembers can only be added to organization workspaces.
400missing_userProvide user_id or email.
401missing_authorizationNo Authorization: Bearer header.
401invalid_token_formatNot a bf_live_… / bf_test_… key.
401invalid_tokenKey not recognized.
401token_revoked / token_expiredThe key was revoked or has expired.
403wrong_serviceThe key belongs to another service.
403origin_blocked / origin_required / ip_blockedThe request doesn't satisfy the key's origin restrictions.
403access_not_grantedNo active Users API approval (not approved yet, or revoked).
403insufficient_scopeThe key lacks the endpoint's scope (error.details.required).
404not_foundNo Users API endpoint at that path.
404workspace_not_foundThe workspace doesn't exist, or the key owner isn't an owner/admin of it.
404user_not_foundNo account created by your integration matches (also returned for accounts you didn't create).
405method_not_allowedOnly POST is accepted.
409email_takenAn account with that email already exists.
409already_memberThat email is already an active member of the workspace.
413payload_too_largeRequest body over 64 KB.
429rate_limitedPer-key or account-wide per-minute / per-day limit exceeded (also: too many reset emails for one address).
429account_creation_limit / invite_recently_sentDaily new-account ceiling reached, or the same invite was sent in the last 10 minutes.
503rate_limit_unavailableLimits couldn't be checked; nothing was done — retry.
500server_errorUnexpected error — retry, and quote the X-Request-Id if it persists.