Calco

Organizations

An organization (org) is the tenant that owns your data. Templates, template versions, renders, API keys, signing certificates, categories, and the audit/usage logs all belong to an organization — never to a bare user account. This is the boundary a subscription will attach to once billing ships.

This page describes how organizations behave today, in both the web UI and the HTTP API. For the machine-facing API reference, see the API reference.

The ownership model

  • Every account owns exactly one organization. It is created automatically the moment you register (local signup, the initial /auth/setup admin, or the first OIDC login). You never create it by hand, and you can never own a second one.
  • Membership is many-to-many. Beyond your own org, you can be invited into other organizations to collaborate — for example, a colleague adds you to their company's org so you can share its templates.
  • Each account has exactly one active organization. Every request you make resolves to (you, your active org), and all data you see and create is scoped to that active org. For API-key requests, the org is the one the key was pinned to when it was created.

Roles

Within an organization, each member has one of three roles:

RoleWhoCan do
ownerThe account that created the org. Exactly one, permanent, cannot be transferred or removed.Everything an admin can, and is the future billing owner. Manages categories.
adminInvited collaborators with management rights.Rename the org, invite/remove members, change member roles, manage resources.
memberInvited collaborators.Use the org's templates and resources.

The owner role cannot be changed or removed, and an owner cannot leave their own organization.

What the account page exposes

Open Account → Organization. You will see:

  • Current organization — its name. Owners and admins get a Rename button.
  • Members — everyone in the org with their role. Owners and admins can:
    • Add member — by the email of an existing account, as admin or member. (Email invites to people without an account are not supported yet.)
    • Change role between admin and member.
    • Remove a non-owner member.
  • Leave organization — shown only for orgs you were invited into (not your own owned org). Leaving an org you are currently active in repoints your active org back to the organization you own.

There is no organization switcher

You cannot switch your active organization from the app. Your active org is set once, at signup, to the organization you own, and it stays there. The only way it changes is when you leave an org you were invited into — at which point it falls back to your owned org.

This is deliberate for the current phase. The earlier in-app switcher (and its POST /api/orgs/active endpoint) has been removed.

Working with more than one organization

If you genuinely need separate sets of templates, categories, and (future) billing — for two different companies, say — you have two options today:

  1. Be invited into the other org. If a colleague owns it, they add you as a member from their account page. You then collaborate inside that org. Note that because you cannot switch active orgs, this works best when the invited org is the one you primarily work in.
  2. Create a separate account with a different email. Each account owns its own organization, with its own templates, categories, API keys, and (once billing lands) its own subscription. This is the supported path for keeping two companies' data fully separate under one person.

How a request resolves to an organization

Every request that touches org-scoped data is filtered by exactly one org id, resolved from how you authenticated:

  • Session (cookie login) → your active organization. The web UI always works inside your active org.
  • API key → the organization the key was pinned to when it was created. A key is bound to one org for life; it never follows your active org around. (Because keys are created from the dashboard inside your active org, a key is pinned to whatever your active org was at creation time — today that is always your owned org.)

Endpoints that require an org return 403 no active organization if the caller has none.

What belongs to an organization

Once a request resolves to an org, these resources are scoped to it. Data in one org is never visible from another.

Templates and versions

Templates, their versions, renders, and scaffolds all belong to the org that created them. Reading, rendering, renaming, or deleting a template you don't own returns 404 (the API does not reveal that the resource exists in another org).

Template deduplication is per-org: uploading a file byte-identical to one your org already has returns the existing template; another org uploading the same bytes gets its own independent copy. The hash-first endpoints (/api/render/{hash}, /api/templates/by-hash/{hash}/versions) look up the hash within the API key's org — the same hash can map to different templates in different orgs.

Signing certificates

PKCS#12 signing certificates are stored per-org (encrypted at rest). Each org has its own set and its own default certificate. A cert uploaded in one org is invisible to and unusable by another. The same certificate bytes can be registered independently in two different orgs.

API keys

Each organization has exactly one key pair — a live and a test key — shared by the whole org and pinned to it for life. An owner or admin generates the pair once and can later regenerate either key independently; there is no per-user or multi-pair creation. The key list (Account → API Keys) shows only the active org's keys, with no cross-org view. See the API reference for the lifecycle endpoints.

Categories

Categories are per-org. Any member can list them, but only the org owner can create or delete a category (this replaced the old admin-only rule).

Audit and usage logs

Every audit-log and usage-log row carries the org_id it happened in. GET /api/audit, GET /api/usage/summary, and GET /api/usage/timeseries only ever return rows for your active org, so each org sees its own activity and aggregate stats in isolation. The usage endpoints also accept an optional ?env=live|test to scope the stats to one environment.

API surface

All organization-management endpoints require a session (cookie login) and an active organization; they are not part of the API-key machine surface. :id is the organization id.

MethodPathMin rolePurpose
GET/api/orgsmemberList the orgs you belong to, plus your active_org_id.
PATCH/api/orgs/:idadminRename the org.
POST/api/orgs/:id/leavememberLeave an org (owners cannot leave).
GET/api/orgs/:id/membersmemberList members.
POST/api/orgs/:id/membersadminAdd an existing user by email as admin/member.
PATCH/api/orgs/:id/members/:userIdadminChange a member's role.
DELETE/api/orgs/:id/members/:userIdadminRemove a non-owner member.

GET /auth/me includes your active_org_id so the UI can scope itself without an extra round-trip.

There is no POST /api/orgs/active endpoint — active-org switching was removed (see above).

Backward compatibility

A single-user install keeps working with zero manual steps: the existing "default" tenant is migrated into a real organization, the first/oldest (or first admin) user becomes its owner, and any other users join as members. Users without an active org are backfilled to default.