Appearance
Users & profile
The signed-in user's profile, preferences and per-user key/value metadata, the organisation directory used for sharing, linked sign-in providers, and account deletion.
Endpoints
| Method | Path | Purpose |
|---|---|---|
| GET | /users/me/ | Who am I |
| POST | /users/me/ | Acknowledge the first-run flow |
| POST | /users/profile/ | Update profile fields |
| GET | /users/models/ | List available language models |
| GET | /users/metadata/ | Read my metadata |
| POST | /users/metadata/push/ | Append a metadata object |
| POST | /users/metadata/pop/ | Remove matching metadata objects |
| POST | /users/encryption/migrate-chats/ | Encrypt my legacy plaintext chats |
| GET | /users/sso/connected/ | Linked sign-in providers |
| POST | /users/sso/link/ | Link a sign-in provider |
| POST | /users/sso/unlink/ | Unlink a sign-in provider |
| GET | /users/userslist/ | Users in my organisation |
| GET | /users/file_writer_files/ | Files written by the File Writer tool |
| DELETE | /users/delete/ | Delete my account |
GET /users/me/
Who am I
The signed-in user's profile, sign-in methods, roles, subscription plan and entitlements. The app calls this right after sign-in and caches the result; call it again after linking an account or changing organisation.
Auth: Session token · In the app: Loaded at sign-in; Profile page
Response 200 — The current user.
| Field | Type | Description |
|---|---|---|
info | object | The signed-in user, as returned by GET /users/me/ (info) and SSO callbacks (user). |
info.id | integer | |
info.first_name | string | |
info.last_name | string | |
info.username | string | |
info.email | string (email) | |
info.phone | integer, nullable | Mobile number as digits, or null. |
info.llm_model | string | Display name of the user's default model ("" when unset). |
info.language | string ("en", "ar") | UI language. |
info.organization | string | Organisation name ("" when the user has none). |
info.last_login | string (date-time), nullable | |
info.verified | boolean | Account approved (true for promo-code sign-ups, otherwise set by an admin). |
info.email_verified | boolean | |
info.use_cloud | boolean | |
info.number_verified | boolean | |
info.is_first_time | boolean | True until the app's first-run flow has been acknowledged (POST /users/me/). |
info.roles | string[] | Role names, e.g. ["General"]. |
info.is_staff | boolean | Django staff flag — what admin-only endpoints check. |
info.dedicated_agent | boolean | Whether the Dedicated Agent module is enabled for this user. |
info.date_joined | string (date-time) | |
info.is_sso | boolean | True when the account has no usable password (SSO-only). |
info.auth_methods | object | Which sign-in methods the account currently has. |
info.auth_methods.password | boolean | |
info.auth_methods.passkey | boolean | |
info.auth_methods.microsoft | boolean | |
info.plan | object, nullable | Active subscription plan (hosted deployments only; null when there is none). |
info.plan.name | string | |
info.plan.code | string | Stable plan key, e.g. pro. |
info.plan.monthly_price | string | |
info.plan.yearly_price | string | |
info.plan.active_until | string (date-time), nullable | |
info.plan.owner | boolean | Present on organisation plans — true if this user owns the subscription. |
info.entitlements | object | Limits resolved from the plan, plus usage counters. null in any quota means unlimited (and is different from 0, a real limit of none). Accounts with no subscription resolve to the built-in free tier with owner_type: none. |
info.entitlements.plan_code | string | Resolved plan key ("" on the free tier). |
info.entitlements.plan_name | string | |
info.entitlements.owner_type | string ("organization", "user", "none") | Who the quota pool belongs to. |
info.entitlements.is_active | boolean | |
info.entitlements.seats | integer | |
info.entitlements.support_tier | string | |
info.entitlements.features | object | Capability flags, e.g. flowapps, dashboards, chat_ai, aes256, byo_llm, rbac, global_secrets, sso_scim, audit_logs, on_prem, data_integration. |
info.entitlements.monthly_workflow_runs | integer, nullable | |
info.entitlements.included_tokens | integer, nullable | |
info.entitlements.storage_bytes | integer, nullable | |
info.entitlements.run_history_days | integer, nullable | |
info.entitlements.live_build_sessions | integer, nullable | |
info.entitlements.workflow_runs_used | integer | Runs this calendar month. |
info.entitlements.storage_used_bytes | integer | Last crawl plus bytes reserved since. |
info.entitlements.storage_breakdown | object | Measured footprint from the last storage crawl. |
info.entitlements.storage_breakdown.media | integer | |
info.entitlements.storage_breakdown.vector | integer | |
info.entitlements.storage_breakdown.workflow | integer | |
info.entitlements.storage_breakdown.total | integer | |
info.entitlements.storage_breakdown.limit | integer, nullable | |
info.entitlements.storage_breakdown.measured_at | string (date-time), nullable | |
info.entitlements.enforced | boolean | Whether the feature gate is switched on for this deployment. |
info.entitlements.storage_enforced | boolean | Whether exceeding storage_bytes actually blocks uploads. |
info.plan_required | boolean | True when the deployment requires an active plan and the app should send plan-less users to the plans page. |
json
{
"info": {
"id": 42,
"first_name": "Jane",
"last_name": "Doe",
"username": "jane",
"email": "jane@acme.com",
"phone": 966500000000,
"llm_model": "GPT-4.1",
"language": "en",
"organization": "Acme Corp",
"last_login": "2026-09-22T06:41:12.318Z",
"verified": true,
"email_verified": true,
"use_cloud": false,
"number_verified": false,
"is_first_time": false,
"roles": [
"General"
],
"is_staff": false,
"dedicated_agent": false,
"date_joined": "2026-03-02T09:12:44Z",
"is_sso": false,
"auth_methods": {
"password": true,
"passkey": true,
"microsoft": false
},
"plan": {
"name": "Pro",
"code": "pro",
"monthly_price": "49.00",
"yearly_price": "490.00",
"active_until": "2026-12-31T00:00:00Z",
"owner": true
},
"entitlements": {
"plan_code": "pro",
"plan_name": "Pro",
"owner_type": "organization",
"is_active": true,
"seats": 5,
"support_tier": "standard",
"features": {
"flowapps": true,
"dashboards": true,
"chat_ai": true,
"aes256": true,
"byo_llm": false,
"rbac": true,
"global_secrets": false,
"sso_scim": true,
"audit_logs": true,
"on_prem": false,
"data_integration": true
},
"monthly_workflow_runs": 15000,
"included_tokens": 5000000,
"storage_bytes": 10737418240,
"run_history_days": 90,
"live_build_sessions": 10,
"workflow_runs_used": 122,
"storage_used_bytes": 2603276926,
"storage_breakdown": {
"media": 2393516655,
"vector": 191292894,
"workflow": 18467377,
"total": 2603276926,
"limit": 10737418240,
"measured_at": "2026-09-22T04:00:02.545639Z"
},
"enforced": false,
"storage_enforced": false
},
"plan_required": false
}
}Response 401 — Missing, invalid, expired or revoked access token.
json
{
"detail": "Invalid or expired token."
}Example
bash
curl -X GET "https://api.example.com/users/me/" \
-H "Authorization: Token $FINBLADE_TOKEN"POST /users/me/
Acknowledge the first-run flow
Clears is_first_time after the app's welcome tour. Returns whether the flag was set before the call.
Auth: Session token · In the app: Welcome tour (first sign-in)
Response 200 — Flag state before the call.
| Field | Type | Description |
|---|---|---|
is_first_time | boolean |
json
{
"is_first_time": true
}Example
bash
curl -X POST "https://api.example.com/users/me/" \
-H "Authorization: Token $FINBLADE_TOKEN"POST /users/profile/
Update profile fields
Partial update: only the fields present in the body change. llm_model_id selects the user's default chat model (see GET /users/models/).
Auth: Session token · In the app: Profile → Personal details
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
first_name | string | no | |
last_name | string | no | |
username | string | no | Must stay unique. |
phone | integer, nullable | no | |
language | string ("en", "ar") | no | |
llm_model_id | integer | no | Id of an entry from GET /users/models/. |
json
{
"first_name": "Jane",
"language": "ar",
"llm_model_id": 3
}Response 200 — The six updatable fields after the change.
| Field | Type | Description |
|---|---|---|
info | object | |
info.first_name | string | |
info.last_name | string | |
info.username | string | |
info.phone | integer, nullable | |
info.language | string | |
info.llm_model_id | integer, nullable |
json
{
"info": {
"first_name": "Jane",
"last_name": "Doe",
"username": "jane",
"phone": 966500000000,
"language": "ar",
"llm_model_id": 3
}
}Example
bash
curl -X POST "https://api.example.com/users/profile/" \
-H "Authorization: Token $FINBLADE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"first_name":"Jane","language":"ar","llm_model_id":3}'GET /users/models/
List available language models
The models configured on this deployment. Use id as llm_model_id when updating the profile.
Auth: Session token
Response 200 — Array of models.
Array of:
| Field | Type | Description |
|---|---|---|
id | integer | |
name | string | Display name. |
value | string | Provider model identifier, e.g. gpt-4.1. |
provider | string, nullable | |
capability | string ("chat", "vision", "image_gen", "embed") | |
description | string, nullable | |
details | string[] | Free-form notes shown in the model picker. |
on_premises | boolean | |
created_at | string (date-time) |
json
[
{
"id": 3,
"name": "GPT-4.1",
"value": "gpt-4.1",
"provider": "openai",
"capability": "chat",
"description": null,
"details": [],
"on_premises": false,
"created_at": "2026-01-10T08:00:00Z"
}
]Example
bash
curl -X GET "https://api.example.com/users/models/" \
-H "Authorization: Token $FINBLADE_TOKEN"GET /users/metadata/
Read my metadata
Per-user list of JSON objects the app uses to persist client-side state: encryption key envelopes (rek_password_encrypted, dek_encrypted, …), UI preferences, saved filters. Stored encrypted at rest. Filter with key to return only the objects that contain that key.
Auth: Session token · In the app: Loaded at sign-in (encryption keys), Preferences
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
key | string | no | Return only objects that have this top-level key. |
Response 200 — The list (possibly filtered).
| Field | Type | Description |
|---|---|---|
metadata | object[] |
json
{
"metadata": [
{
"preferences": {
"theme": "dark",
"sidebar": "collapsed"
}
}
]
}Example
bash
curl -X GET "https://api.example.com/users/metadata/?key=preferences" \
-H "Authorization: Token $FINBLADE_TOKEN"POST /users/metadata/push/
Append a metadata object
Auth: Session token · In the app: Preferences; encryption setup
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
data | object | yes | The object to append (a JSON string is also accepted). |
json
{
"data": {
"preferences": {
"theme": "dark"
}
}
}Response 200 — Appended.
| Field | Type | Description |
|---|---|---|
message | string |
json
{
"message": "Metadata updated successfully"
}Response 400 — data missing, not valid JSON, or not an object.
json
{
"error": "Data must be a dictionary"
}Example
bash
curl -X POST "https://api.example.com/users/metadata/push/" \
-H "Authorization: Token $FINBLADE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"data":{"preferences":{"theme":"dark"}}}'POST /users/metadata/pop/
Remove matching metadata objects
Removes every object whose values equal all the key/value pairs in data.
Auth: Session token · In the app: Preferences
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
data | object | yes | Match criteria (exact equality on each key). |
json
{
"data": {
"preferences": {
"theme": "dark"
}
}
}Response 200 — Removed.
| Field | Type | Description |
|---|---|---|
message | string |
json
{
"message": "Item(s) removed from metadata"
}Response 400 — No criteria, invalid JSON, or the metadata list is empty.
json
{
"error": "path is required"
}Response 404 — Nothing matched.
json
{
"message": "No matching items found"
}Example
bash
curl -X POST "https://api.example.com/users/metadata/pop/" \
-H "Authorization: Token $FINBLADE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"data":{"preferences":{"theme":"dark"}}}'POST /users/encryption/migrate-chats/
Encrypt my legacy plaintext chats
Encrypts every message of the user that is still stored in plaintext (is_encrypted: false) with the supplied data encryption key. Idempotent — the app calls it at sign-in and it is a no-op once everything is encrypted. The key travels over TLS and is not stored.
Auth: Session token · In the app: Automatic at sign-in
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
dek | string | yes | Data encryption key as a hex string (32 bytes). |
json
{
"dek": "9a3f2c…"
}Response 200 — Number of messages converted.
| Field | Type | Description |
|---|---|---|
migrated | integer |
json
{
"migrated": 12
}Response 400 — dek missing or not hex.
json
{
"error": "path is required"
}Example
bash
curl -X POST "https://api.example.com/users/encryption/migrate-chats/" \
-H "Authorization: Token $FINBLADE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"dek":"9a3f2c…"}'GET /users/sso/connected/
Linked sign-in providers
Auth: Session token · In the app: Profile → Connected accounts
Response 200 — Link state per provider and whether unlinking is safe.
| Field | Type | Description |
|---|---|---|
providers | object | Keyed by provider (google, microsoft). |
has_password | boolean | |
passkey_count | integer | |
auth_method_count | integer | When 1, the remaining method cannot be removed. |
json
{
"providers": {
"google": {
"linked": true,
"email": "jane@acme.com",
"has_encryption_key": true,
"linked_at": "2026-05-01T10:00:00Z"
},
"microsoft": {
"linked": false,
"email": "",
"has_encryption_key": false
}
},
"has_password": true,
"passkey_count": 1,
"auth_method_count": 3
}Example
bash
curl -X GET "https://api.example.com/users/sso/connected/" \
-H "Authorization: Token $FINBLADE_TOKEN"POST /users/sso/link/
Link a sign-in provider
Attaches a Google or Microsoft identity (obtained from the provider's link-exchange endpoint) to the signed-in account. Optionally stores the user's recovery key wrapped for that identity so it becomes an unlock factor. Re-linking the same provider replaces the previous identity.
Auth: Session token · In the app: Profile → Connected accounts → Link
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
provider | string ("google", "microsoft") | yes | |
sub | string | yes | Provider account id from link-exchange. |
email | string | no | |
rek_wrapped | string | no | Client-wrapped recovery key (opaque). |
json
{
"provider": "google",
"sub": "112233445566778899000",
"email": "jane@acme.com"
}Response 200 — Linked.
| Field | Type | Description |
|---|---|---|
message | string | |
provider | string | |
email | string | |
has_encryption_key | boolean |
json
{
"message": "Account linked.",
"provider": "google",
"email": "jane@acme.com",
"has_encryption_key": true
}Response 400 — Unknown provider or missing sub.
json
{
"detail": "Authentication credentials were not provided."
}Response 409 — That provider account is already linked to a different user.
json
{
"detail": "That account is already linked to a different Finblade user."
}Example
bash
curl -X POST "https://api.example.com/users/sso/link/" \
-H "Authorization: Token $FINBLADE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"provider":"google","sub":"112233445566778899000","email":"jane@acme.com"}'POST /users/sso/unlink/
Unlink a sign-in provider
Refused when it is the account's last remaining sign-in method.
Auth: Session token · In the app: Profile → Connected accounts → Unlink
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
provider | string ("google", "microsoft") | yes |
json
{
"provider": "microsoft"
}Response 200 — Unlinked.
| Field | Type | Description |
|---|---|---|
message | string | |
provider | string |
json
{
"message": "Account unlinked.",
"provider": "microsoft"
}Response 404 — Provider not linked.
json
{
"detail": "Authentication credentials were not provided."
}Response 409 — Last sign-in method.
json
{
"detail": "This is your only way to sign in. Set a password or link another provider before removing it."
}Example
bash
curl -X POST "https://api.example.com/users/sso/unlink/" \
-H "Authorization: Token $FINBLADE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"provider":"microsoft"}'GET /users/userslist/
Users in my organisation
The directory used by every "share with…" picker. Empty when the user has no organisation.
Auth: Session token · In the app: Share dialogs (documents, workflows, tables)
Response 200 — Directory.
| Field | Type | Description |
|---|---|---|
users | object[] | |
users[].id | integer | |
users[].username | string | |
users[].display_name | string | Full name, or the username when no name is set. |
users[].email | string | |
users[].roles | string[] |
json
{
"users": [
{
"id": 42,
"username": "jane",
"display_name": "Jane Doe",
"email": "jane@acme.com",
"roles": [
"General"
]
},
{
"id": 57,
"username": "omar",
"display_name": "Omar Al-Rashid",
"email": "omar@acme.com",
"roles": [
"General",
"Admin"
]
}
]
}Example
bash
curl -X GET "https://api.example.com/users/userslist/" \
-H "Authorization: Token $FINBLADE_TOKEN"GET /users/file_writer_files/
Files written by the File Writer tool
Paths (relative to the user's file_writer folder) of every file that the workflow File Writer tool has produced for this user. Sign them with the media endpoint to download.
Auth: Session token · In the app: Workflow AI → toolbar → Files
Response 200 — Sorted relative paths.
| Field | Type | Description |
|---|---|---|
files | string[] |
json
{
"files": [
"/doc/quarterly-summary.docx",
"/pdf/board-pack.pdf"
]
}Response 400 — The user has no organisation.
json
{
"error": "path is required"
}Example
bash
curl -X GET "https://api.example.com/users/file_writer_files/" \
-H "Authorization: Token $FINBLADE_TOKEN"DELETE /users/delete/
Delete my account
Permanently deletes the user and everything they own (sessions, chats, documents, workflows). An erasure audit record is written first. There is no undo.
Auth: Session token · In the app: Profile → Danger zone → Delete account
Response 200 — Deleted.
| Field | Type | Description |
|---|---|---|
message | string |
json
{
"message": "User and all related data deleted successfully."
}Response 500 — Deletion failed part-way; contact support.
json
{
"error": "path is required"
}Example
bash
curl -X DELETE "https://api.example.com/users/delete/" \
-H "Authorization: Token $FINBLADE_TOKEN"