Lumaktaw sa pangunahing nilalaman

Flags: isang desisyon

Ang feature flag ay isang desisyong sinusuri laban sa user state. Ang targeting ay isang hanay ng kondisyon sa state na iyon, sinusuri sa mismong sandaling hinihingi ang flag.

At a glance

Host
https://api.prodantix.com
Read
GET /v1/flags, GET /v1/flags/snapshot, GET /v1/flags/memberships
Write
upsertFlag, archiveFlag over GraphQL, or a change request under review
Live updates
flagChange on the project room
Streaming
flagChange pushed to server SDKs over Socket.IO
Auth
Project key to read, access token to write
  • Real-time na targeting sa live user state
  • Napagpapasyahan sa mismong sandali ng pagtatanong, hindi binabasa mula sa nakaimbak na listahan
  • Rollouts at experiments na sinusukat sa parehong source of truth

Paano ito gumagana

A flag is evaluated against the user state currently on file for a distinct id, in the same order everywhere: a disabled flag is off, every prerequisite must hold, every targeting condition must match, and then the rollout percentage decides by a stable bucket of the distinct id, so a user keeps their answer across reads and SDKs. Evaluation is server-side by default, so a rule change takes effect without a client release.

Saan ito maaabot

SurfaceOperation
RESTGET /v1/flags · GET /v1/flags/snapshot · GET /v1/flags/memberships
GraphQLflags, flagHistory, flagChangeRequests, flagRollouts, promotionDiff queries · upsertFlag, archiveFlag, proposeFlagChange, approveFlagChange, rejectFlagChange, withdrawFlagChange, startFlagRollout, cancelFlagRollout, rollbackFlagRollout, promoteFlag, setFlagReviewRequired mutations
RealtimeflagChange on the project room and on the flags room server SDKs join · flagRollout, flagChangeRequest on the project room
MCPprodantix.flags.list · .upsert · .archive · .history · .change_requests · .rollouts · .propose · .archived · .change_request · .review_required · .promotion_diff · .withdraw · .promote · .restore · .start_rollout · .cancel_rollout · .rollback_rollout · .approve · .reject

Halimbawa

curl "https://api.prodantix.com/v1/flags?distinct_id=u_8f3a" \
  -H "Authorization: Bearer $PRODANTIX_KEY"

Evaluating locally

For latency-sensitive paths, fetch the whole ruleset once and evaluate in-process. Snapshot responses carry Cache-Control: public, max-age=30, stale-while-revalidate=300. A snapshot also names the realtime gateway in socketUrl; a server SDK subscribes there and refetches the snapshot when a flag changes, so a change reaches it in under a second instead of at the next poll.

JSON
{
  "flags": [
    {
      "key": "new-checkout",
      "enabled": true,
      "rolloutPercentage": 50,
      "targeting": [
        { "attribute": "plan", "op": "in", "value": ["pro", "scale"] }
      ]
    }
  ],
  "generatedAt": "2026-09-04T09:00:00Z",
  "socketUrl": "https://ws.prodantix.com"
}

Variants and exposure

A typed flag assigns each user a variation. GET /v1/flags answers with a variants map beside flags, the assigned variation key and value per flag, and the SDKs expose it as getVariant. Every per-flag read records one $feature_flag_called event carrying $feature_flag and $feature_flag_response, once per user, flag and response, which is what an experiment attributes on.

JSON
{
  "flags": { "new-checkout": true },
  "variants": { "new-checkout": { "key": "treatment", "value": "b" } }
}

Mga kondisyon ng cohort

A targeting condition can test cohort membership. It names the reserved attribute $cohort, the operator in_cohort or not_in_cohort, and a cohort id. Membership is read from the cohort store at decision time, so the flag follows the cohort as it is recomputed, and a flag cannot name an archived cohort. A local evaluator reads GET /v1/flags/memberships?distinct_id=… once per distinct id, and only when a flag in the snapshot names a cohort.

JSON
{ "attribute": "$cohort", "op": "in_cohort", "value": "<cohort id>" }

Mga paunang kinakailangan

A flag can require other flags to serve a named variation first. Prerequisites are resolved against the same snapshot, before targeting and rollout; an unsatisfied one answers false with the reason prerequisite, and the off variation for a typed flag. A flag holds at most ten prerequisites, a cycle is refused at save, and a flag cannot be archived while another depends on it.

JSON
{
  "key": "new-checkout",
  "enabled": true,
  "rolloutPercentage": 100,
  "targeting": [],
  "prerequisites": [{ "flagKey": "checkout-v2", "variationKey": "true" }]
}

Two recipes fall out of this. A mutual-exclusion layer is a string flag whose variations partition the audience; each experiment flag requires one variation, so no user is in two experiments of the layer at once.

JSON
{ "key": "layer", "type": "string", "variations": [
  { "key": "a", "value": "a", "weight": 34 },
  { "key": "b", "value": "b", "weight": 33 },
  { "key": "c", "value": "c", "weight": 33 }
] }

{ "key": "exp-pricing",  "prerequisites": [{ "flagKey": "layer", "variationKey": "a" }] }
{ "key": "exp-onboard",  "prerequisites": [{ "flagKey": "layer", "variationKey": "b" }] }
{ "key": "exp-search",   "prerequisites": [{ "flagKey": "layer", "variationKey": "c" }] }

A holdout is a boolean flag rolled out to a slice of the audience; every experiment flag requires it to be false, so the slice never sees any experiment and stays a clean baseline.

JSON
{ "key": "holdout", "enabled": true, "rolloutPercentage": 5, "targeting": [] }

{ "key": "exp-pricing", "prerequisites": [{ "flagKey": "holdout", "variationKey": "false" }] }
{ "key": "exp-onboard", "prerequisites": [{ "flagKey": "holdout", "variationKey": "false" }] }

Mga rollout at guardrail

A progressive rollout moves a flag from one variation to another on a schedule. Each step names a time and the share the target variation holds from then on; the scheduler applies the latest due step once a minute and checks the guardrail every five. A guardrail names a metric and the direction that counts as worse. When the target variation is worse with a probability of at least 95 percent, over at least minExposures exposures, the rollout restores its start weights and notifies the project. While a rollout runs the flag is locked against edits that would move the weights; cancelFlagRollout keeps the weights where they are, rollbackFlagRollout restores the start.

JSON
{
  "variation": "true",
  "fromVariation": "false",
  "steps": [
    { "at": "2026-09-05T09:00:00Z", "percentage": 10 },
    { "at": "2026-09-06T09:00:00Z", "percentage": 50 },
    { "at": "2026-09-08T09:00:00Z", "percentage": 100 }
  ],
  "guardrail": {
    "metric": { "kind": "conversion", "event": "checkout_failed" },
    "worseWhen": "higher",
    "minExposures": 100
  }
}
GraphQL
mutation StartRollout($projectId: String!, $rollout: String!) {
  startFlagRollout(projectId: $projectId, key: "new-checkout", rollout: $rollout) {
    id
    status
    currentStep
    nextStepAt
  }
}

Mga kahilingan sa pagbabago

A project can require review. Once setFlagReviewRequired is on, a change that reaches users becomes a request instead of a write; disabling a flag, and editing its description or intent, still apply directly. proposeFlagChange carries the change as JSON, one of upsert, archive, restore or start_rollout. approveFlagChange applies it and rejectFlagChange closes it, and both must come from someone other than the requester, who can take the request back with withdrawFlagChange. A request records the flag version it was written against, and approving it after the flag moved on fails with a conflict rather than overwriting the newer write. A flag holds one pending request at a time.

JSON
{
  "kind": "upsert",
  "flag": {
    "key": "new-checkout",
    "enabled": true,
    "rolloutPercentage": 50,
    "targeting": [{ "attribute": "plan", "op": "eq", "value": "pro" }]
  }
}
GraphQL
mutation RequireReview($projectId: String!) {
  setFlagReviewRequired(projectId: $projectId, required: true)
}

mutation Propose($projectId: String!, $change: String!) {
  proposeFlagChange(projectId: $projectId, key: "new-checkout", change: $change, comment: "Half of pro") {
    id
    status
    baseUpdatedAt
  }
}

mutation Approve($projectId: String!, $id: String!) {
  approveFlagChange(projectId: $projectId, id: $id, comment: "Looks right") {
    id
    status
    appliedAt
  }
}

Pag-promote sa live

A flag defined in a test project is promoted to the live project of the same slug. promotionDiff lists the fields that would change, with the live value beside the one the promotion writes; promoteFlag writes them. Cohorts are matched by name across the two projects and every prerequisite must already exist in the target. enabled is never copied, so a promoted flag lands off until someone switches it on. Under review, a promotion becomes a change request like any other write.

GraphQL
query Diff($from: String!, $to: String!) {
  promotionDiff(fromProjectId: $from, toProjectId: $to, key: "new-checkout") {
    exists
    diff {
      field
      from
      to
    }
  }
}

mutation Promote($from: String!, $to: String!) {
  promoteFlag(fromProjectId: $from, toProjectId: $to, key: "new-checkout") {
    applied
    changeRequestId
  }
}

Kasaysayan

Every write keeps the previous version. flagHistory lists versions newest first with who wrote each one, how (changedVia is one of user, experiment, rollout, guardrail, promotion, change_request, copy) and which fields changed. Pass before with the last writtenAt to page further back.

GraphQL
query History($projectId: String!) {
  flagHistory(projectId: $projectId, key: "new-checkout", limit: 20) {
    writtenAt
    changedByEmail
    changedVia
    changedFields
  }
}