Skip to content

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

MethodPathPurpose
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.

FieldTypeDescription
infoobjectThe signed-in user, as returned by GET /users/me/ (info) and SSO callbacks (user).
info.idinteger
info.first_namestring
info.last_namestring
info.usernamestring
info.emailstring (email)
info.phoneinteger, nullableMobile number as digits, or null.
info.llm_modelstringDisplay name of the user's default model ("" when unset).
info.languagestring ("en", "ar")UI language.
info.organizationstringOrganisation name ("" when the user has none).
info.last_loginstring (date-time), nullable
info.verifiedbooleanAccount approved (true for promo-code sign-ups, otherwise set by an admin).
info.email_verifiedboolean
info.use_cloudboolean
info.number_verifiedboolean
info.is_first_timebooleanTrue until the app's first-run flow has been acknowledged (POST /users/me/).
info.rolesstring[]Role names, e.g. ["General"].
info.is_staffbooleanDjango staff flag — what admin-only endpoints check.
info.dedicated_agentbooleanWhether the Dedicated Agent module is enabled for this user.
info.date_joinedstring (date-time)
info.is_ssobooleanTrue when the account has no usable password (SSO-only).
info.auth_methodsobjectWhich sign-in methods the account currently has.
info.auth_methods.passwordboolean
info.auth_methods.passkeyboolean
info.auth_methods.microsoftboolean
info.planobject, nullableActive subscription plan (hosted deployments only; null when there is none).
info.plan.namestring
info.plan.codestringStable plan key, e.g. pro.
info.plan.monthly_pricestring
info.plan.yearly_pricestring
info.plan.active_untilstring (date-time), nullable
info.plan.ownerbooleanPresent on organisation plans — true if this user owns the subscription.
info.entitlementsobjectLimits 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_codestringResolved plan key ("" on the free tier).
info.entitlements.plan_namestring
info.entitlements.owner_typestring ("organization", "user", "none")Who the quota pool belongs to.
info.entitlements.is_activeboolean
info.entitlements.seatsinteger
info.entitlements.support_tierstring
info.entitlements.featuresobjectCapability 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_runsinteger, nullable
info.entitlements.included_tokensinteger, nullable
info.entitlements.storage_bytesinteger, nullable
info.entitlements.run_history_daysinteger, nullable
info.entitlements.live_build_sessionsinteger, nullable
info.entitlements.workflow_runs_usedintegerRuns this calendar month.
info.entitlements.storage_used_bytesintegerLast crawl plus bytes reserved since.
info.entitlements.storage_breakdownobjectMeasured footprint from the last storage crawl.
info.entitlements.storage_breakdown.mediainteger
info.entitlements.storage_breakdown.vectorinteger
info.entitlements.storage_breakdown.workflowinteger
info.entitlements.storage_breakdown.totalinteger
info.entitlements.storage_breakdown.limitinteger, nullable
info.entitlements.storage_breakdown.measured_atstring (date-time), nullable
info.entitlements.enforcedbooleanWhether the feature gate is switched on for this deployment.
info.entitlements.storage_enforcedbooleanWhether exceeding storage_bytes actually blocks uploads.
info.plan_requiredbooleanTrue 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.

FieldTypeDescription
is_first_timeboolean
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)

FieldTypeRequiredDescription
first_namestringno
last_namestringno
usernamestringnoMust stay unique.
phoneinteger, nullableno
languagestring ("en", "ar")no
llm_model_idintegernoId 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.

FieldTypeDescription
infoobject
info.first_namestring
info.last_namestring
info.usernamestring
info.phoneinteger, nullable
info.languagestring
info.llm_model_idinteger, 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:

FieldTypeDescription
idinteger
namestringDisplay name.
valuestringProvider model identifier, e.g. gpt-4.1.
providerstring, nullable
capabilitystring ("chat", "vision", "image_gen", "embed")
descriptionstring, nullable
detailsstring[]Free-form notes shown in the model picker.
on_premisesboolean
created_atstring (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

FieldTypeRequiredDescription
keystringnoReturn only objects that have this top-level key.

Response 200 — The list (possibly filtered).

FieldTypeDescription
metadataobject[]
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)

FieldTypeRequiredDescription
dataobjectyesThe object to append (a JSON string is also accepted).
json
{
  "data": {
    "preferences": {
      "theme": "dark"
    }
  }
}

Response 200 — Appended.

FieldTypeDescription
messagestring
json
{
  "message": "Metadata updated successfully"
}

Response 400data 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)

FieldTypeRequiredDescription
dataobjectyesMatch criteria (exact equality on each key).
json
{
  "data": {
    "preferences": {
      "theme": "dark"
    }
  }
}

Response 200 — Removed.

FieldTypeDescription
messagestring
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)

FieldTypeRequiredDescription
dekstringyesData encryption key as a hex string (32 bytes).
json
{
  "dek": "9a3f2c…"
}

Response 200 — Number of messages converted.

FieldTypeDescription
migratedinteger
json
{
  "migrated": 12
}

Response 400dek 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.

FieldTypeDescription
providersobjectKeyed by provider (google, microsoft).
has_passwordboolean
passkey_countinteger
auth_method_countintegerWhen 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)

FieldTypeRequiredDescription
providerstring ("google", "microsoft")yes
substringyesProvider account id from link-exchange.
emailstringno
rek_wrappedstringnoClient-wrapped recovery key (opaque).
json
{
  "provider": "google",
  "sub": "112233445566778899000",
  "email": "jane@acme.com"
}

Response 200 — Linked.

FieldTypeDescription
messagestring
providerstring
emailstring
has_encryption_keyboolean
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)

FieldTypeRequiredDescription
providerstring ("google", "microsoft")yes
json
{
  "provider": "microsoft"
}

Response 200 — Unlinked.

FieldTypeDescription
messagestring
providerstring
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.

FieldTypeDescription
usersobject[]
users[].idinteger
users[].usernamestring
users[].display_namestringFull name, or the username when no name is set.
users[].emailstring
users[].rolesstring[]
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.

FieldTypeDescription
filesstring[]
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.

FieldTypeDescription
messagestring
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"

Finblade documentation