Skip to main content

Organisation quotas

Per-organisation caps on resource consumption. Useful when the platform hosts multiple tenants and operators need to enforce different licensing tiers without writing a billing system from scratch.

Set at Organization → Settings → Quotas (org owners / admins can see usage; only Platform staff — is_staff or is_superuser — can edit), or at creation time in Platform → Organizations → Create.

What can be capped​

Four resource types:

ResourceFieldDefault
Max membersmax_membersunlimited
Max projectsmax_projectsunlimited
Max tasks (across all projects in the org)max_tasksunlimited
Max storage per project (uploaded bytes)max_storage_bytesunlimited

Leave a field empty (null) for unlimited — the default. Any integer, including 0, is enforced as a hard cap: 0 freezes that resource entirely (no new members / projects / tasks / uploads).

Storage is the cap that protects the disk: a thousand text tasks cost less than one video. It applies per project, so one runaway import cannot starve the organisation's other projects. Only files uploaded through the app count; files in connected cloud storage are never copied and are not counted. The Quotas tab shows it in GB per project; the API takes bytes.

Organisation tiers​

Tiers ("organisation types") are named presets that stamp all four limits at once. They live under Platform → Settings → Organization tiers, where a superuser can rename them, change any limit, add or remove tiers, reorder them and choose the default tier every new organisation starts on. The shipped set:

Tier (key)MembersProjectsTasksStorage per project
Community (community, default)3055 0002 GB
Level 1 (team)25025100 00050 GB
Level 2 (business)2 0001001 000 000250 GB
Level 3 (enterprise)unlimitedunlimitedunlimitedunlimited

This is the one ladder of the platform: the names and limits set here are what every page shows, and the four keys are fixed because they are the levels an organisation can be put on. Rename a tier's label and it is renamed everywhere (an installed extension may show other names for the same keys where it is set to). A deployment that still had the earlier Bronze-to-Enterprise set is moved onto this ladder on upgrade (Bronze became Community, Silver Level 1, Gold and Platinum Level 2, Enterprise Level 3), including organisations whose limits matched one of them.

  • New organisations (self-service sign-up or staff creation without explicit limits) get the default tier. Clear the default to start new organisations unlimited. Before this setting existed every organisation started unlimited; organisations created earlier keep whatever they have until staff move them.
  • Moving an organisation between tiers is one click on its Settings → Quotas tab (staff only): pick the tier chip, Save quotas. The chip matching the current values is highlighted; anything else reads Custom. Staff can also pick a tier when creating an organisation.
  • Editing a tier never touches organisations already on it. Their limits are copies; enforcement reads the four raw fields on the organisation, so a custom mix typed by hand is just as valid.

API: GET /api/platform/quota-tiers (staff) returns the list and the default key; PUT /api/platform/quota-tiers (superuser) replaces it with {"tiers": [{"key", "label", "description", "max_members", "max_projects", "max_tasks", "max_storage_bytes"}], "default": "<key>"} (null = unlimited). Every save is audit-logged.

When a quota is enforced​

Enforcement happens at the mutation point:

  • Add member — checked when an org admin invites someone / pre-creates them. Over-cap → invite rejected with a clear error
  • Create project — checked when + Create Project is clicked. Over-cap → button shows "Project quota reached" and the modal explains how to increase
  • Import tasks — checked at import time against the whole batch, all-or-nothing: if importing 500 tasks would take the org from 900 past its 1000-task cap, the entire import is refused (Task limit reached (1400/1000)); nothing partial lands
  • Upload files — checked twice. The import page asks GET /api/projects/<id>/import-limits before sending anything and refuses a drop that would not fit the project's remaining allowance; the server repeats the check per upload (This project has used X GB of its Y GB storage limit) for API clients

Counts:

  • max_members counts current org members (removed members don't count)
  • max_projects counts non-deleted projects — there is no archive state, so the only way to free project quota is to delete
  • max_tasks counts all tasks across all non-deleted projects in the org
  • max_storage_bytes counts the bytes of a single project's uploaded files (FileUpload.file_size); deleting a task does not free the bytes until its uploaded file is removed

Threshold notifications​

Every quota check also evaluates usage. At ≥ 80% an org.quota_warning notification goes to every org owner (in-app + email via the normal notification rules — there is no separate on/off setting for it); at ≥ 100% it's org.quota_hit ("…will be refused until a platform admin raises the cap"). Notices are de-duplicated to one per org, resource and kind per day, so a busy import doesn't spam the owner.

Where organisations see their quotas​

  • Organization → Overview → Plan and usage (owners and admins): one tile per resource with used / limit and a bar. Storage shows the heaviest project, because that is the project the per-project cap bites first, plus the total across projects. A tile turns amber past 80 % and red at the limit; a resource under review is marked. The Request an increase button sits in the strip header and is hidden when nothing is capped.
  • Organization → Settings → Quotas (owners and admins): the same four resources with usage bars, read-only for the organisation and editable (with tier chips) for platform staff, followed by a Requests card listing every request the organisation has made and how each item was answered.

Requesting an increase (organisation side)​

Owners and admins cannot raise their own limits, so they ask the platform team:

  1. Press Request an increase. The form lists every capped resource (storage per project in GB, members, projects, tasks) with the current limit and what is in use. Tick one or several, give the new limit for each, add a reason, send. One request can carry everything at once, or the organisation can ask for one thing now and another later.
  2. A resource that already has an item under review is locked in the form until the platform team answers it; a new limit must be above the current one; unlimited resources are not offered.
  3. Platform staff get an in-app notice each and one email to the community inbox (platform.quota_request) linking straight to the review modal; an org.quota_request audit event is written.
  4. Each item is answered separately. Owners and admins are notified (org.quota_request_decided) with one line per item: approved and the new limit, declined and the reason, or still under review.

API: POST /api/organizations/<id>/quota-requests with {"items": [{"resource": "storage", "requested": 50}, {"resource": "members", "requested": 120}], "reason": "…"} (owner / admin; storage in GB per project). GET on the same path lists the organisation's requests, the resources currently under review and the usage snapshot.

Reviewing requests (platform side)​

Platform → Organizations opens with a Quota requests queue above the organisation list (filter Pending / Decided / All; the pending count also shows as a pill on the organisation's row). Review opens the request in a modal:

  • the organisation, who asked, when, and their reason;
  • one row per item with the current limit, what is in use and what was asked for;
  • a decision per row: Approve with an editable amount (grant less or more than asked), Decline with a reason the organisation will see, or Decide later to leave that item pending;
  • Approve all / Decline all shortcuts and an optional note that goes out with the decision email.

Apply decisions raises the approved limits on the organisation immediately, records an org.update (area quotas) and an org.quota_request_decided audit event, and notifies the organisation's owners and admins. A request whose items were answered differently shows as Partly approved; one with an item left for later stays Pending.

Other ways to raise limits, independent of any request:

  1. Settings → Quotas on the organisation (Platform → Organizations → View switches you into it): pick a tier chip or type values, Save quotas

  2. API, for billing-system integrations:

    curl -X PATCH "{{platformURL}}/api/organizations/<id>/quota" \
    -H "Authorization: Bearer YOUR_STAFF_PAT" \
    -d '{"max_tasks": 10000, "max_storage_bytes": 107374182400}'
  3. Tier upgrade (when wired to billing) — the platform ships no billing system; operators wire this themselves via webhooks + the API

Review API: GET /api/platform/quota-requests?status=pending|decided|all and POST /api/platform/quota-requests/<id>/decide with {"decisions": {"<item id>": {"action": "approve", "granted": 25}, "<item id>": {"action": "deny", "note": "…"}}, "note": "…"} (staff).

What quotas DON'T cover​

  • Connected cloud storage (GCS / S3 / Azure buckets the org links) — nothing is copied, so nothing is counted. Use cloud-provider quotas
  • API rate limits — separate; see Platform admin → Settings → Rate limits
  • Compute (ML backend predictions) — backend operators bill their own usage; AfriAnnotate doesn't proxy that
  • Annotators-per-project — that's tasks_per_annotator_limit, configured per-project not per-org

Use case: tiered SaaS​

For a public SaaS deployment with Free / Pro / Enterprise tiers:

TierMembersProjectsTasksAnnual price
Free321000$0
Pro2020100 000$500
Enterpriseunlimitedunlimitedunlimitedper-customer

Operator workflow: when a new org signs up via the public landing page, set their quotas to Free defaults. On billing-system upgrade, flip the quotas via API.

Use case: internal cost-allocation​

Even without a billing system, quotas are useful for chargeback: allocate task budgets to internal teams, see which teams are running over.

Combine with the Metrics dashboard export to feed quota usage into your finance team's reporting.

Audit log​

Every quota change lands in the audit log as an org.update event with area: "quotas" and the list of changed_fields in its metadata (the org owner is also notified), so finance can prove "yes, you got upgraded to 100k tasks on this date." Every request lands as org.quota_request (resources asked for) and every decision as org.quota_request_decided (approved and declined resources); tier edits land as branding.update. Read the current values and usage, including the storage block (allowance, total used, heaviest projects), from the org's quota endpoint (GET .../quota).

What's next​

Freezing an organisation​

A platform administrator can freeze an organisation (Platform → Organizations → Freeze, or the organisation's Settings → Quotas → Freeze organisation, with an optional reason). While frozen:

  • every member sees a Frozen pill on the organisation header and gets read-only access — sign-in, browsing, the Data Manager and exports keep working;
  • every write in that organisation is refused with HTTP 423 and the reason (new projects, members, tasks, annotations, settings, webhooks);
  • platform staff are never blocked, so the organisation can still be administered and Unfrozen at any time;
  • owners and admins are notified on freeze and unfreeze, and the action is written to the audit log.

API: PATCH /api/organizations/<id>/quota with {"is_frozen": true, "frozen_reason": "…"} (staff only).