Introduction
The Bodek API gives you one base URL for every service. Each service has its own per-service key, created in the developer console.
Base URL https://dev.bodek.us/v1/<service>/…
Services files · forms · iam · users
Format JSON over HTTPS (file uploads use multipart/form-data)
Signing people in to your app with their Bodek account is a separate product — Bodek OAuth — that uses OAuth client credentials instead of API keys.
API catalog
| API | Base | Endpoint groups | Credentials |
|---|---|---|---|
| Files | /v1/files |
Files (move, copy, compress, unzip), Folders, Chunked uploads, Search, Shares, Storage, Workspaces, Members & invites, Webhooks | Files key |
| Forms | /v1/forms |
Forms (create, update, duplicate, open/close, delete), Stats, Responses (filter, export), Uploads & file download, Invitations, Embed | Forms key |
| IAM | /v1/iam |
Workspace members — list, invite, change role, remove; pending invites and invite codes | IAM key |
| Users | /v1/users |
Create users, Create businesses, Workspace invitations, Password resets | Users key (approval required) |
| SMS | sms.bodek.us/api/v1 |
Send & read messages, Numbers, Groups | SMS key (plan add-on) — manage |
| Bodek OAuth | accounts.bodek.us |
Discovery, Authorize, Token, UserInfo, Introspection, Revocation | OAuth client id / secret |
Each API page is the authoritative, endpoint-by-endpoint reference.
For uptime checks, GET /v1/files/health and GET /v1/forms/health answer
without a key.
Getting access
- Sign in to this site with your Bodek account (Sign in). The developer console requires an active Bodek plan; if yours isn't active you'll be sent to billing.
- Create a key in the console for the service you want — Files, Forms and IAM keys are self-service.
- Users API only: submit an access request first. After an administrator approves it, the Users service becomes available when creating keys.
- For “Sign in with Bodek”, register an app in the OAuth apps console instead — see the OAuth guide.
API keys
Keys look like bf_live_ or bf_test_ followed by 32 characters
(A–Z, 2–7). The full key is shown once, when you create it;
only a hash is stored, and the console shows its prefix afterwards. When creating a key you choose:
| Setting | Details |
|---|---|
| Service | files, forms, iam or users. A key can only call its own service. |
| Name | Your label, up to 120 characters. |
| Environment | live or test. Both hit the same data and behave identically; the prefix just makes them easy to tell apart. |
| Scopes | At least one, from that service's list (see Scopes & services). |
| Workspace | Files keys act on your personal workspace or one organization workspace you belong to. IAM keys must target an organization workspace. Forms and Users keys aren't tied to a workspace. |
| Folder lock | Files keys only, optional: restrict the key to one folder (and everything inside it). The folder
becomes the key's root — listings without a parent start there, uploads without a parent land there, and
anything outside it returns 404. |
| Allowed origins | Optional comma-separated domains, e.g. app.example.com, *.example.com. Blank means any origin.
See Origin restrictions. |
| Rate limits | Requests per minute (default 120, max 1,000) and per rolling 24 hours (default 20,000, max 1,000,000). Users API keys are capped lower (30/minute, 1,000/day). See Rate limits. |
Scopes, workspace and origins can't be edited later — create a new key instead. Revoke
disables a key immediately (requests then fail with 401 token_revoked) but keeps it
listed; Delete removes it entirely.
Authentication
Send your key as a bearer token on every request:
curl https://dev.bodek.us/v1/files/files \
-H "Authorization: Bearer bf_live_ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
Treat keys like passwords: keep them server-side, and if one leaks, revoke it in the console and create a new one.
Sign in with Bodek
Separate from API keys, Bodek Accounts is an OAuth 2.0 provider with OpenID Connect–style discovery and UserInfo. Add a “Sign in with Bodek” button so people log into your site with their Bodek account — password and two-factor handled entirely by Bodek, no credentials touching your app.
Register a client in the OAuth apps console and follow the integration guide for the authorization-code and PKCE flows, refresh tokens, UserInfo, introspection and revocation.
Scopes & services
These are all the scopes you can pick when creating a key. A request needs the scope listed on its
endpoint in the API reference; otherwise it fails with 403 insufficient_scope.
| Service | Base | Scopes |
|---|---|---|
| Files | /v1/files |
files:read files:write files:delete folders:write
share:read share:write workspace:read workspace:write
webhooks:read webhooks:write |
| Forms | /v1/forms |
forms.read forms.submit forms.write |
| IAM | /v1/iam |
iam:read iam:write |
| Users | /v1/users |
users:create business:create workspace:invite users:password_reset |
| SMS | sms.bodek.us/api/v1 |
messages:send messages:read numbers:read groups:read groups:write |
Note the separators: Forms scopes use a dot (forms.read), the others a colon.
Response format
Files, Forms and IAM wrap successful payloads in data, with optional
meta for pagination:
{
"data": [ … ],
"meta": { "limit": 25, "offset": 0, "total": 134 }
}
The Users API returns its result object at the top level (see Users conventions).
Deletes may return 204 No Content with an empty body. File downloads return the raw
bytes with the file's Content-Type and a Content-Disposition header.
Errors, on every service, carry a stable machine code and a human message, plus
details when there's more to say:
{
"error": {
"code": "insufficient_scope",
"message": "This key needs the scope: forms.submit",
"details": { "required": ["forms.submit"] }
}
}
Branch on error.code, not on the message text.
Pagination
List endpoints return a page at a time and describe it in meta:
- Forms —
limitandoffsetquery parameters;metahaslimit,offsetandtotal. Request the next page withoffset = offset + limituntil you've readtotalitems. - Files —
limit(1–100, default 20) andoffset, with an optionalsort(name_ascorname_desc). Followmeta.next_offsetand stop when it isnull; the Files reference lists exactly which parameters each list endpoint takes.
Errors
| Status | Meaning | Common codes |
|---|---|---|
| 400 | Bad request — malformed or invalid input. | invalid_json, endpoint-specific validation codes |
| 401 | Missing, malformed, unknown, expired or revoked key. | missing_authorization invalid_token_format invalid_token token_expired token_revoked |
| 403 | Wrong service, missing scope, blocked origin, or no access. | wrong_service insufficient_scope origin_blocked origin_required ip_blocked access_not_granted |
| 404 | No such endpoint or resource (or it's outside the key's workspace or folder). | not_found unknown_service |
| 405 | The endpoint exists but not with that method. | method_not_allowed |
| 409 | Conflict — e.g. a closed form, a taken email, an existing member. | endpoint-specific |
| 413 | Upload too large. | upload_failed |
| 422 | Validation failed (see error.details). | endpoint-specific |
| 429 | Rate limit exceeded. | rate_limited |
| 500 | Unexpected server error — retry, then report it with the request id. | internal_error server_error |
| 502 | The gateway couldn't reach the service backend — retry with backoff. | upstream_unreachable |
Request IDs
Every response from a service includes an X-Request-Id header (a 20-character hex string). It is
also recorded in your key's audit log. Log it on your side and quote it in bug reports so we can find
the exact request.
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Request-Id: 8069ab8f752950692a1c
X-RateLimit-Limit-Minute: 120
X-RateLimit-Remaining-Minute: 119
X-RateLimit-Reset-Seconds: 42
X-RateLimit-Limit-Day: 20000
X-RateLimit-Remaining-Day: 19873
Rate limits
Each key has a per-minute cap and a rolling 24-hour cap, set when you create it (defaults 120/minute, 20,000/day). Every authenticated request counts, including ones that fail. Responses carry:
| Header | Meaning |
|---|---|
X-RateLimit-Limit-Minute / X-RateLimit-Remaining-Minute | Per-minute cap and what's left of it. |
X-RateLimit-Reset-Seconds | Seconds until the per-minute window resets (Files, Forms, IAM). |
X-RateLimit-Limit-Day / X-RateLimit-Remaining-Day | 24-hour cap and what's left of it. |
Over a cap you get 429 rate_limited with error.details.limit. Wait for the reset
(or back off exponentially) before retrying. The console shows each key's usage over the last 24 hours.
Origin restrictions
A key with no allowed origins works from anywhere. A key with allowed origins only
accepts requests whose Origin header — or, if absent, Referer — has a
matching host:
app.example.commatches that host exactly.*.example.commatches any subdomain andexample.comitself.- A request with neither header (a typical server-side call) is rejected with
403 origin_required; a non-matching host gets403 origin_blocked.
So use origin-restricted keys for browser code (publishable keys) and unrestricted keys, kept secret, on your servers. Origin checks happen in the service and work the same through the gateway and the direct hosts. (Server-only and IP-allow-list modes exist for keys provisioned by Bodek; IP-restricted keys should call the direct hosts rather than the gateway.)
CORS & browsers
The gateway supports cross-origin calls from browsers. It answers OPTIONS preflights with
204 and echoes the caller's Origin in Access-Control-Allow-Origin
(no cookies — Access-Control-Allow-Credentials: false; authenticate with the
Authorization header).
Access-Control-Allow-Methods: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type, Accept, Range, If-Range, If-None-Match,
If-Modified-Since, If-Match, Idempotency-Key, X-Requested-With
Access-Control-Expose-Headers: X-Request-Id, X-RateLimit-*, Content-Disposition, Content-Range,
Accept-Ranges, ETag, Last-Modified, Location, Retry-After
Access-Control-Max-Age: 86400
CORS only lets the browser read the response; whether the request is allowed is decided by the key's origin restrictions. Never ship an unrestricted key in browser code.
Gateway & direct hosts
Use the gateway unless you have a reason not to. It forwards the method, path, query string, body
(JSON, raw, or multipart uploads), Authorization, and download/caching headers
(Range, If-None-Match…) to the service, and relays the status code, body and
response headers such as Content-Type, Content-Disposition,
Content-Range, ETag, Location, X-Request-Id and
X-RateLimit-*. Redirects (for example a 302 to a temporary download URL) are
passed back to your client to follow.
| Service | Gateway | Direct host |
|---|---|---|
| Files | https://dev.bodek.us/v1/files/… | https://files.bodek.us/api/v1/… |
| IAM | https://dev.bodek.us/v1/iam/… | https://files.bodek.us/api/v1/iam/… |
| Forms | https://dev.bodek.us/v1/forms/… | https://forms.bodek.us/api/v1/forms/… (service endpoints /health, /me at https://forms.bodek.us/api/v1/…) |
| Users | https://dev.bodek.us/v1/users/… | — (served by the gateway itself) |
The same key works on both. The gateway allows up to 60 seconds per request; for very large uploads or
downloads, or IP-restricted keys, call the direct host. An unknown service name returns
404 unknown_service.
Migrating from file.bodeksolutions.com
Existing Files API keys keep working. Swap the base URL —
https://file.bodeksolutions.com/api/v1 →
https://dev.bodek.us/v1/files — and leave every resource path
(/files, /folders, /shares/{id}) unchanged.