# Organizations API Reference

All endpoints require a valid Supabase JWT in the Authorization: Bearer <token> header. Organization-scoped endpoints also require X-Organization-ID.


# Register Organization

POST /organizations/register

Creates a new organization and adds the requesting user as admin.

Request

{
  "name": "Acme Corp"
}

Response

{
  "success": true,
  "data": {
    "organization_id": "uuid"
  }
}

# List Organizations

GET /organizations/list

Returns all organizations the authenticated user belongs to.

Response

{
  "success": true,
  "data": [
    {
      "id": "uuid",
      "name": "Acme Corp",
      "role": "admin"
    }
  ]
}

# Get Organization Settings

GET /organizations/settings

Returns settings for the current organization.

Response

{
  "success": true,
  "data": {
    "id": "uuid",
    "name": "Acme Corp",
    "logo_url": "https://...",
    "currency": "USD"
  }
}

POST /organizations/logo

Uploads a logo image for the organization. Admin only.

Request: multipart/form-data with a file field.

Response

{
  "success": true,
  "data": {
    "logo_url": "https://..."
  }
}

# Get User Language

GET /organizations/user/language

Returns the authenticated user's preferred UI language.

Response

{
  "success": true,
  "data": {
    "language": "en"
  }
}

# Update User Language

PUT /organizations/user/language

Updates the authenticated user's preferred UI language.

Request

{
  "language": "ru"
}

Response

{
  "success": true,
  "data": {
    "language": "ru"
  }
}

# Send Team Invitations

POST /organizations/team/invite

Sends email invitations to join the organization. Admin only. Max 3 per request.

Invitations expire after 7 days. Re-inviting an expired address is allowed.

Request

{
  "invites": [
    { "email": "user@example.com", "role": "editor" }
  ]
}

Allowed roles: viewer, editor, admin.

Response

{
  "success": true,
  "data": {
    "sent": [{ "email": "user@example.com", "role": "editor" }],
    "failed": []
  }
}

# List Team Members

GET /organizations/team/members

Returns all members of the current organization sorted by email.

Response

{
  "success": true,
  "data": [
    { "id": "uuid", "email": "user@example.com" }
  ]
}

Data source: organization_members_view


# Pending Invites Count

GET /organizations/team/pending-invites/count

Returns the count of pending (sent, not accepted, not expired) invitations for the current organization. Used to show the badge in the Settings sidebar.

Response

{
  "success": true,
  "data": {
    "count": 3
  }
}

Data source: organization_invites where status = 'pending', accepted_at IS NULL, expires_at > now()


# Accept Team Invitation

POST /organizations/team/invite/accept

Accepts a pending team invitation and adds the user to the organization. No X-Organization-ID header required — the user may not belong to any org yet.

Request

{
  "invite_id": "uuid"
}

Validation:

  1. Invite must exist and have status pending
  2. Invite must not be expired (expired invites are auto-marked as expired)
  3. Authenticated user's email must match the invite email (case-insensitive)

Response (success)

{
  "success": true,
  "data": {
    "organization_id": "uuid",
    "organization_name": "Acme Corp"
  }
}

Response (already a member) — idempotent, returns success:

{
  "success": true,
  "data": {
    "organization_id": "uuid",
    "organization_name": "Acme Corp",
    "message": "You are already a member of this organization."
  }
}

Race conditions are handled gracefully — concurrent accept requests return success.


# Decline Team Invitation

POST /organizations/team/invite/decline

Declines a pending team invitation. No X-Organization-ID header required.

Request

{
  "invite_id": "uuid"
}

Validation:

  1. Invite must exist
  2. Authenticated user's email must match the invite email (case-insensitive)
  3. Invite must have status pending

Response

{
  "success": true,
  "data": {
    "message": "Invitation declined successfully."
  }
}

# Remove Team Member

POST /organizations/team/members/remove

Removes a member from the organization. Owner or Admin only. Cannot remove yourself. Cannot remove the last admin.

Request

{
  "user_id": "uuid"
}

Response

{
  "success": true,
  "data": {
    "message": "Member removed successfully."
  }
}