Skip to content

Organizations API

Base path: /api/v1/organizations

Manages organization profiles, subscription plans, resource limits, BYOK LLM configuration, SSO, and physical locations. Every authenticated user belongs to exactly one organization; their JWT carries an org_id claim that identifies it.

See API Reference for auth, errors, and pagination.

Core

GET /organizations/plans

Return the full plan catalog with pricing and resource limits.

Auth: None — public endpoint.

Response — 200 OK

[
  {
    "name": "trial",
    "price_monthly_usd": 0,
    "max_users": 30,
    "max_integrations": 3,
    "max_locations": 1,
    "description": "Free 30-day trial — same limits as Starter."
  },
  {
    "name": "starter",
    "price_monthly_usd": 49,
    "max_users": 30,
    "max_integrations": 3,
    "max_locations": 1,
    "description": "For small teams getting started with institutional memory."
  },
  {
    "name": "growth",
    "price_monthly_usd": 149,
    "max_users": 150,
    "max_integrations": -1,
    "max_locations": -1,
    "description": "For growing organisations across multiple locations."
  },
  {
    "name": "enterprise",
    "price_monthly_usd": 399,
    "max_users": -1,
    "max_integrations": -1,
    "max_locations": -1,
    "description": "Unlimited scale with dedicated support."
  }
]

A value of -1 for any limit means unlimited. Plan changes go through POST /api/v1/billing/change-plan.

curl

curl https://api.knora.io/api/v1/organizations/plans

GET /organizations/me

Return the authenticated caller's organization.

Auth: JWT — any role. The JWT must carry a valid org_id claim.

Response — 200 OK

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "name": "Acme Corp",
  "slug": "acme-corp",
  "plan": "growth",
  "plan_started_at": "2025-03-01T00:00:00Z",
  "trial_ends_at": null,
  "is_active": true,
  "max_users": 150,
  "max_integrations": -1,
  "max_locations": -1,
  "settings_json": {
    "timezone": "Asia/Riyadh",
    "locale": "ar"
  },
  "created_at": "2025-03-01T00:00:00Z",
  "updated_at": "2025-11-20T14:32:10Z"
}

stripe_customer_id and stripe_subscription_id are not included in this response.

Errors

Status Code Cause
401 Missing or invalid JWT
403 MISSING_ORG_CLAIM JWT contains no org_id claim
403 INVALID_ORG_CLAIM org_id claim is not a valid UUID
404 Organization deleted after token was issued

curl

curl -H "Authorization: Bearer $TOKEN" \
  https://api.knora.io/api/v1/organizations/me

GET /organizations/me/limits

Return the caller's organization's current resource usage compared to plan limits.

Auth: JWT — any role.

Response — 200 OK

{
  "org_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "plan": "growth",
  "user_count": 42,
  "max_users": 150,
  "integration_count": 5,
  "max_integrations": -1,
  "location_count": 3,
  "max_locations": -1,
  "can_add_user": true,
  "can_add_integration": true,
  "can_add_location": true
}

can_add_* is false when the current count has reached the plan ceiling (not applicable when limit is -1).

curl

curl -H "Authorization: Bearer $TOKEN" \
  https://api.knora.io/api/v1/organizations/me/limits

PATCH /organizations/me

Update mutable organization fields.

Auth: JWT — role admin or member with is_org_owner: true.

Request body (all fields optional)

Field Type Constraints Description
name string | null 2–255 chars Display name of the organization
settings_json object | null Any valid JSON object Arbitrary org-level configuration

Example request

curl -X PATCH \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme Corporation", "settings_json": {"timezone": "Asia/Riyadh", "locale": "ar"}}' \
  https://api.knora.io/api/v1/organizations/me

Response — 200 OK — full updated organization object (same shape as GET /me).

Errors

Status Code Cause
400 name fails length validation
401 Missing or invalid JWT
403 Caller is a member without is_org_owner
404 Organization not found

BYOK (Enterprise)

Bring Your Own Key lets an organization supply its own LLM API credentials. All endpoints require an Enterprise plan and admin or owner role.

GET /organizations/byok

Return the current BYOK configuration. The stored API key is never returned verbatim — only the last 4 characters are shown as a hint (e.g. ****...ab12).

Auth: JWT — admin/owner. Plan: Enterprise.

Response — 200 OK

{
  "byok_enabled": true,
  "provider": "anthropic",
  "api_key_hint": "****...ab12",
  "model_override": "anthropic/claude-sonnet-4-6"
}

Errors

Status Code Cause
403 ENTERPRISE_REQUIRED Org is not on the Enterprise plan
403 Caller is not an admin or owner

curl

curl -H "Authorization: Bearer $TOKEN" \
  https://api.knora.io/api/v1/organizations/byok

PUT /organizations/byok

Set or update the BYOK configuration. The plaintext api_key is encrypted with Fernet before storage. byok_enabled is set to true automatically once both provider and api_key are present.

Auth: JWT — admin/owner. Plan: Enterprise.

Request body (at least one field required)

Field Type Description
provider "openai" | "anthropic" | "custom" LLM provider
api_key string Plaintext key to encrypt and store. Omit to keep existing.
model_override string LiteLLM model string, e.g. "gpt-4o". Omit to keep existing.

Example request

curl -X PUT \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"provider": "anthropic", "api_key": "sk-ant-...", "model_override": "anthropic/claude-sonnet-4-6"}' \
  https://api.knora.io/api/v1/organizations/byok

Response — 200 OK — same shape as GET /organizations/byok.

Errors

Status Code Cause
400 VALIDATION_ERROR No fields provided, or field fails validation
400 BYOK_ERROR Key storage or encryption failure
403 ENTERPRISE_REQUIRED Org is not on the Enterprise plan

DELETE /organizations/byok

Disable BYOK and clear all stored credentials. The org immediately reverts to the platform's default LLM. The encrypted key is deleted from the database.

Auth: JWT — admin/owner. Plan: Enterprise.

Response — 200 OK — BYOK config with byok_enabled: false and all fields cleared.

curl

curl -X DELETE \
  -H "Authorization: Bearer $TOKEN" \
  https://api.knora.io/api/v1/organizations/byok

POST /organizations/byok/test

Validate a BYOK API key by making a minimal LiteLLM completion call. If api_key is omitted, the currently stored (encrypted) key is used.

Auth: JWT — admin/owner. Plan: Enterprise.

Request body

Field Type Required Description
provider "openai" | "anthropic" | "custom" Yes LLM provider to test against
api_key string No Key to test. Omit to test the stored key.
model_override string No Model to use for the test call. Defaults to provider's canonical model.

Example request

curl -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"provider": "openai", "api_key": "sk-..."}' \
  https://api.knora.io/api/v1/organizations/byok/test

Response — 200 OK

{
  "success": true,
  "message": "Connection successful.",
  "model_used": "gpt-4o-mini"
}

Errors

Status Code Cause
400 BYOK_TEST_FAILED LLM call failed — bad key, wrong provider, or model unavailable
400 VALIDATION_ERROR Request body fails schema validation
403 ENTERPRISE_REQUIRED Org is not on the Enterprise plan

SSO (Enterprise)

Manage Single Sign-On configuration. All endpoints require an Enterprise plan and admin or owner role. The actual SAML/OIDC handshake (SP-initiated login, ACS callback) is a separate implementation — these routes manage configuration storage only.

GET /organizations/sso

Return the current SSO configuration. Sensitive fields (certificate, client_secret, private_key) are replaced with "[REDACTED]".

Auth: JWT — admin/owner. Plan: Enterprise.

Response — 200 OK

{
  "sso_enabled": true,
  "sso_provider": "oidc",
  "sso_enforce": false,
  "config": {
    "client_id": "your-client-id",
    "client_secret": "[REDACTED]",
    "discovery_url": "https://accounts.google.com/.well-known/openid-configuration",
    "scopes": ["openid", "email", "profile"]
  },
  "updated_at": "2026-01-10T09:00:00Z"
}

When no SSO has been configured, sso_enabled is false and config is null.

curl

curl -H "Authorization: Bearer $TOKEN" \
  https://api.knora.io/api/v1/organizations/sso

PUT /organizations/sso

Create or update the SSO configuration. Config fields are merged with existing values — only supplied keys are overwritten.

Auth: JWT — admin/owner. Plan: Enterprise.

Request body

Field Type Required Description
provider "saml" | "oidc" | "google_workspace" | "microsoft_entra" Required on first setup SSO provider identifier
config object No Provider-specific config fields (see below). Partial update — unset keys are preserved.
sso_enforce boolean No When true, disables password login for all org members.

config fields by provider

Provider Key fields
saml entity_id, sso_url, certificate, metadata_url
oidc client_id, client_secret, discovery_url, scopes
google_workspace customer_id, admin_email
microsoft_entra tenant_id, client_id, client_secret

All config fields are optional at the schema level. Provider-specific validation is deferred to the SAML/OIDC handshake. attribute_mapping (object) is supported across all providers for mapping IdP claims to Knora user fields.

Example request

curl -X PUT \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "oidc",
    "config": {
      "client_id": "your-client-id",
      "client_secret": "your-client-secret",
      "discovery_url": "https://accounts.google.com/.well-known/openid-configuration"
    }
  }' \
  https://api.knora.io/api/v1/organizations/sso

Response — 200 OK — same shape as GET /organizations/sso (secrets redacted).

Errors

Status Code Cause
400 VALIDATION_ERROR Request body fails schema validation
400 SSO_CONFIG_ERROR Config storage failure
403 ENTERPRISE_REQUIRED / PLAN_UPGRADE_REQUIRED Org is not on the Enterprise plan

POST /organizations/sso/test

Probe the stored SSO metadata/discovery URL for reachability. Performs a lightweight HTTP GET to confirm the IdP endpoint is accessible — does not validate SAML XML schema or negotiate any OIDC token flow. Always returns 200; check the reachable field.

Auth: JWT — admin/owner. Plan: Enterprise.

Response — 200 OK

{
  "reachable": true,
  "url_tested": "https://accounts.google.com/.well-known/openid-configuration",
  "message": "IdP endpoint is reachable."
}

curl

curl -X POST \
  -H "Authorization: Bearer $TOKEN" \
  https://api.knora.io/api/v1/organizations/sso/test

DELETE /organizations/sso

Disable SSO and clear all stored config. sso_enforce is also reset to false, which immediately re-enables password-based login for all org members.

Auth: JWT — admin/owner. Plan: Enterprise.

Response — 200 OK — SSO config with sso_enabled: false and all fields cleared.

curl

curl -X DELETE \
  -H "Authorization: Bearer $TOKEN" \
  https://api.knora.io/api/v1/organizations/sso

Locations

Physical locations within an organization. List is available to all members; create/update/delete require manager or above.

GET /organizations/locations

List active locations for the caller's org.

Auth: JWT — any role.

Query parameters

Param Type Default Description
include_inactive boolean false Include deactivated locations. Admin only.

Response — 200 OK

[
  {
    "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
    "org_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "name": "Riyadh HQ",
    "description": "Main headquarters",
    "address": "King Fahd Road, Riyadh",
    "is_active": true,
    "created_at": "2025-06-01T10:00:00Z",
    "updated_at": "2025-06-01T10:00:00Z"
  }
]

curl

curl -H "Authorization: Bearer $TOKEN" \
  https://api.knora.io/api/v1/organizations/locations

POST /organizations/locations

Create a new location.

Auth: JWT — manager or admin. Plan: Growth+.

Request body

Field Type Required Constraints Description
name string Yes 1–255 chars Location display name
description string No max 2000 chars Optional description
address string No max 500 chars Physical address

Example request

curl -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Jeddah Office", "address": "Corniche Road, Jeddah"}' \
  https://api.knora.io/api/v1/organizations/locations

Response — 201 Created — the created location object (same shape as list items).

Errors

Status Code Cause
400 VALIDATION_ERROR Missing name or field exceeds length limit
400 LOCATION_LIMIT_EXCEEDED Org has reached its plan's location ceiling
403 Caller is a member (not manager/admin)
403 PLAN_UPGRADE_REQUIRED Plan is below Growth
409 LOCATION_ALREADY_EXISTS A location with that name already exists in this org

PUT /organizations/locations/:id

Update name, description, or address of an existing location.

Auth: JWT — manager or admin.

Path parameter: id — UUID of the location.

Request body (all fields optional)

Field Type Constraints Description
name string 1–255 chars New display name
description string max 2000 chars New description
address string max 500 chars New address

Example request

curl -X PUT \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Jeddah Branch", "address": "Prince Sultan Road, Jeddah"}' \
  https://api.knora.io/api/v1/organizations/locations/b2c3d4e5-f6a7-8901-bcde-f12345678901

Response — 200 OK — the updated location object.

Errors

Status Code Cause
403 Caller is a member (not manager/admin)
404 LOCATION_NOT_FOUND No location with that UUID in this org
409 LOCATION_ALREADY_EXISTS Another location already uses the new name

DELETE /organizations/locations/:id

Soft-deactivate a location. Sets is_active = false; the row is retained for historical referential integrity.

Auth: JWT — manager or admin.

Path parameter: id — UUID of the location.

Response — 200 OK

{
  "message": "Location deactivated successfully."
}

Errors

Status Code Cause
403 Caller is a member (not manager/admin)
404 LOCATION_NOT_FOUND No location with that UUID in this org

curl

curl -X DELETE \
  -H "Authorization: Bearer $TOKEN" \
  https://api.knora.io/api/v1/organizations/locations/b2c3d4e5-f6a7-8901-bcde-f12345678901

Plan Catalog

Plan Price/mo Max users Max integrations Max locations
trial $0 30 3 1
starter $49 30 3 1
growth $149 150 Unlimited Unlimited
enterprise $399 Unlimited Unlimited Unlimited

Plan changes go through POST /api/v1/billing/change-plan. Downgrading is only permitted when current usage is within the target plan's limits; otherwise 400 LIMIT_EXCEEDED is returned.

Error Codes

HTTP Code Description
400 LIMIT_EXCEEDED Downgrade blocked — current usage exceeds the target plan's ceiling
400 BYOK_ERROR BYOK key storage or encryption failure
400 BYOK_TEST_FAILED BYOK LLM test call failed
400 SSO_CONFIG_ERROR SSO configuration storage failure
400 LOCATION_LIMIT_EXCEEDED Org has reached its location limit
401 JWT absent, expired, or malformed
402 SUBSCRIPTION_REQUIRED Org is inactive or trial has expired
403 MISSING_ORG_CLAIM JWT does not contain an org_id claim
403 INVALID_ORG_CLAIM org_id claim in JWT is not a valid UUID
403 ENTERPRISE_REQUIRED Feature requires Enterprise plan
403 PLAN_UPGRADE_REQUIRED Route requires a higher plan; details includes required_plan and feature
403 INSUFFICIENT_ROLE Caller's role is too low for this operation
404 Requested resource does not exist
409 LOCATION_ALREADY_EXISTS A location with that name already exists in this org