Left at /api/, because these are not ours to rename: - the vendor endpoint catalogues under products/warehouse_sources - the ingress route table in docs/internal/feature-flags, which really does send /api/* to Django - ActiveCampaign's own /api/3 path segment - the billing service's routes in the Vercel sequence diagrams - the Slack event and callback URLs, which are registered in the Slack app config and have to byte-match what the code still builds
10 KiB
Django API endpoints
Django serves the admin/management API for feature flags: CRUD operations, analytics, and organization-level operations. Runtime flag evaluation (/flags, /decide) is routed directly to the Rust service by Contour/Envoy at the Kubernetes infrastructure level -- these requests never reach Django. Django does make internal service-to-service HTTP calls to the Rust service for actions like my_flags and evaluation_reasons.
The /api/feature_flag/local_evaluation endpoint was historically served by a dedicated Django deployment (insights-local-evaluation). All local evaluation traffic is now served by the Rust definitions fleet at /flags/definitions (see Rust service overview). The Django endpoint and deployment have been removed.
Architecture overview
┌─────────────────────────────────────────────────────────────────┐
│ Django API │
│ (routed via Contour from /v1/* paths) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Project-scoped (CRUD + management) │
│ /v1/projects/{id}/feature_flags/ │
│ │
│ Organization-scoped (cross-team ops) │
│ /v1/organizations/{id}/feature_flags/ │
│ │
│ Legacy (derives team from user session) │
│ /v1/feature_flag/ │
│ │
└─────────────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌──────────┐ ┌──────────────┐
│ Postgres │ │ Rust flags │
│ (CRUD) │ │ service │
│ │ │ (internal │
│ │ │ proxy) │
└──────────┘ └──────────────┘
Viewsets
All in insights/api/feature_flag.py unless noted otherwise.
| Viewset | Route | Notes |
|---|---|---|
FeatureFlagViewSet |
api/projects/{id}/feature_flags/ |
Primary viewset. Full CRUD, soft delete via ForbidDestroyModel. |
LegacyFeatureFlagViewSet |
api/feature_flag/ |
Inherits FeatureFlagViewSet, derives team from session. |
OrganizationFeatureFlagView |
api/organizations/{id}/feature_flags/ |
Cross-team operations. File: insights/api/organization_feature_flag.py. |
FlagValueViewSet |
api/projects/{id}/flag_value/ |
Returns possible values for a flag. File: insights/api/flag_value.py. |
Endpoint reference
CRUD operations
Standard REST on /v1/projects/{id}/feature_flags/. Hard DELETE is blocked — use PATCH with deleted: true for soft delete.
Custom actions
| Method | URL | Description |
|---|---|---|
GET |
.../feature_flags/my_flags/ |
All flags with values for the current user (proxied to Rust service) |
GET |
.../feature_flags/evaluation_reasons/ |
Evaluate flags for a distinct_id with match reasons (proxied to Rust service) |
POST |
.../feature_flags/user_blast_radius/ |
Estimate how many users a condition affects |
POST |
.../feature_flags/{pk}/create_static_cohort_for_flag/ |
Create a static cohort from matched users |
GET |
.../feature_flags/{pk}/status/ |
Flag status (ACTIVE, STALE, DELETED, UNKNOWN) |
GET |
.../feature_flags/{pk}/dependent_flags/ |
Flags that depend on this flag |
POST |
.../feature_flags/{pk}/dashboard/ |
Create a usage dashboard for the flag |
Organization endpoints
| Method | URL | Description |
|---|---|---|
GET |
/v1/organizations/{id}/feature_flags/{key}/ |
Get a flag by key across all accessible teams |
POST |
/v1/organizations/{id}/feature_flags/copy_flags/ |
Copy a flag from one project to target projects |
copy_flags requires editor access to feature_flag in each target project, not just visibility.
A caller who can see a project but can't edit flags there gets a failed entry for that target instead of a copy.
target_project_ids is capped at MAX_COPY_FLAGS_TARGET_PROJECTS (50) per call, and the endpoint has its own burst/sustained throttles since each target project can create cohorts and a flag.
Key actions in detail
my_flags and evaluation_reasons
Both actions proxy to the Rust flags service via get_flags_from_service() in insights/api/services/flags_service.py. The Rust service URL defaults to http://localhost:3001 (configured via FEATURE_FLAGS_SERVICE_URL in insights/settings/data_stores.py).
create_static_cohort_for_flag
Creates a static cohort containing all users that match a flag's conditions. This is the only remaining use of the legacy Python flag matching code in insights/models/feature_flag/flag_matching.py.
Django model
FeatureFlag (insights/models/feature_flag/feature_flag.py)
Key things to know:
keyis unique per team (UniqueConstraint(fields=["team", "key"]))- Hard deletes are blocked —
deletedis a soft delete flag versionfield provides optimistic concurrency control. Updates useselect_for_update()and return HTTP 409 on version mismatch.filters(JSONField) holds conditions, variants, payloads, and aggregation configensure_experience_continuityenables hash key overrides for consistent bucketing across identity changesevaluation_runtimecontrols whether a flag is evaluated client-side, server-side, or both- The
@approval_gatedecorator on updates can require approval before changes take effect
Cache invalidation: The refresh_flag_cache_on_updates signal handler fires on save/delete, calling set_feature_flags_for_team_in_cache() via transaction.on_commit().
Related models (same file)
| Model | Purpose |
|---|---|
FeatureFlagHashKeyOverride |
Experience continuity hash key storage (managed = False, handled by Rust migrations) |
FeatureFlagDashboards |
Through table for flag <-> dashboard M2M |
FeatureFlagEvaluationTag |
Links flags to evaluation context tags |
Supporting modules
| File | Purpose |
|---|---|
insights/models/feature_flag/flag_matching.py |
Legacy Python evaluation engine (only used for static cohort creation) |
insights/models/feature_flag/flags_cache.py |
HyperCache for the Rust flags service with signal-based invalidation |
insights/models/feature_flag/local_evaluation.py |
Prepares flag data for SDK local evaluation with HyperCache |
insights/models/feature_flag/user_blast_radius.py |
Estimates user/group match counts for conditions |
insights/api/services/flags_service.py |
HTTP proxy to the Rust flags service |
Remote config endpoints
Remote config (/array/{token}/config, /array/{token}/config.js, /array/{token}/array.js) and the surveys config endpoint (/api/surveys) are no longer served by Django. They are served by the Rust hypercache service, which reads from the same RemoteConfig model populated by Django via post-save signals. See HyperCache system.
See also
- Rust service overview - Runtime flag evaluation service
- Flag evaluation engine - How flags are matched and evaluated
- HyperCache system - Multi-tier caching for flag definitions
- Experience continuity - Hash key overrides design
- Billing - Quota enforcement for feature flag requests