Flashpoint.AIFlashpoint.AIdocs

Quotas

Quotas control the composition of your sample by capping how many responses are collected for each demographic segment. They prevent over-collection in easy-to-reach groups and ensure your final dataset matches your target proportions.


How quotas work

Each quota has three parts:

PartExampleDescription
Name"Males 25-34"Human-readable label for reporting
Target250Number of completes needed
Definition"Q2 == `1` AND Q5 IN [`3`, `4`]"DSL condition that determines which responses count. null for a total quota

When a respondent submits, the system evaluates every active quota's definition against their answers. If the response matches a quota and that quota is full, the response is rejected as over-quota.

Enforcement architecture

Quotas are enforced atomically using a two-tier system:

  • Redis holds live counters for sub-millisecond check-and-increment during response ingestion. A Lua script checks all applicable quotas atomically — if any quota is full, none increment, preventing race conditions.
  • PostgreSQL is the source of truth for quota definitions and historical counts.
  • Reconciliation syncs PostgreSQL counts to Redis every 5 minutes, correcting any drift from failures or manual exclusions.

The system fails open: if Redis is unavailable, responses are accepted and reconciliation corrects the counts later.


Total vs conditional quotas

Total quota

A quota with definition: null counts every complete response regardless of answers. Use this to cap your overall sample size.

{
  "name": "Total",
  "target": 1000,
  "definition": null
}

Conditional quota

A quota with a DSL condition string counts only responses that match. The DSL syntax is the same as skip logic conditions.

{
  "name": "Males 25-34",
  "target": 250,
  "definition": "Q2 == `1` AND Q5 IN [`3`, `4`]"
}

Common quota structures

Gender quotas:

[
  {"name": "Total", "target": 1000, "definition": null},
  {"name": "Male", "target": 500, "definition": "Q2 == `1`"},
  {"name": "Female", "target": 500, "definition": "Q2 == `2`"}
]

Nested quotas (gender x age):

[
  {"name": "Total", "target": 1000, "definition": null},
  {"name": "Males 18-34", "target": 250, "definition": "(Q2 == `1`) AND (Q3 IN [`1`, `2`])"},
  {"name": "Males 35-54", "target": 250, "definition": "(Q2 == `1`) AND (Q3 IN [`3`, `4`])"},
  {"name": "Females 18-34", "target": 250, "definition": "(Q2 == `2`) AND (Q3 IN [`1`, `2`])"},
  {"name": "Females 35-54", "target": 250, "definition": "(Q2 == `2`) AND (Q3 IN [`3`, `4`])"}
]

Regional quotas:

[
  {"name": "Total", "target": 600, "definition": null},
  {"name": "Northeast", "target": 150, "definition": "Q4 IN [`1`, `2`, `3`]"},
  {"name": "Southeast", "target": 150, "definition": "Q4 IN [`4`, `5`, `6`]"},
  {"name": "Midwest", "target": 150, "definition": "Q4 IN [`7`, `8`, `9`]"},
  {"name": "West", "target": 150, "definition": "Q4 IN [`10`, `11`, `12`]"}
]

Setting quotas

Bulk replace (recommended for initial setup)

Replace all quotas at once. This deactivates any existing quotas and creates new ones.

curl -X PUT https://surveys.flashpoint.ai/api/v1/surveys/{survey_id}/quotas \
  -H "X-Service-Token: $TOKEN" \
  -H "X-Team-ID: $TEAM_ID" \
  -H "X-User-ID: $USER_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "quotas": [
      {"name": "Total", "target": 1000, "definition": null},
      {"name": "Male", "target": 500, "definition": "Q2 == `1`"},
      {"name": "Female", "target": 500, "definition": "Q2 == `2`"}
    ]
  }'

Response (200 OK):

{
  "total_target": 1000,
  "total_collected": 0,
  "overall_fill_percentage": 0.0,
  "all_quotas_met": false,
  "quotas": [
    {
      "id": "a1b2c3d4-...",
      "name": "Total",
      "target": 1000,
      "current_count": 0,
      "fill_percentage": 0.0,
      "remaining": 1000,
      "is_full": false,
      "is_active": true,
      "definition": null,
      "created_at": "2026-05-26T10:00:00Z",
      "updated_at": "2026-05-26T10:00:00Z"
    }
  ]
}

Agent prompt: "Set quotas: 1000 total, 500 males (Q2 option 1), 500 females (Q2 option 2)"

Add a single quota

Add one quota without touching existing ones.

curl -X POST https://surveys.flashpoint.ai/api/v1/surveys/{survey_id}/quotas \
  -H "X-Service-Token: $TOKEN" \
  -H "X-Team-ID: $TEAM_ID" \
  -H "X-User-ID: $USER_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "California",
    "target": 100,
    "definition": "Q4 == `5`"
  }'

Response (201 Created):

{
  "id": "e5f6g7h8-...",
  "name": "California",
  "target": 100,
  "definition": "Q4 == `5`"
}

Agent prompt: "Add a regional quota for California respondents, target 100, based on Q4 option 5"

Update a quota

Change a quota's name or target. Only the fields you provide are modified.

The agent tool update_quota accepts quota_id, and optionally name and/or target.

Agent prompt: "Increase the female quota to 600"

Delete a quota

Soft-deletes a quota (sets is_active = false). Redis counters are removed immediately.

Agent prompt: "Delete the California quota"


Monitoring progress

Get quota summary

Returns live fill counts from Redis for every active quota.

curl https://surveys.flashpoint.ai/api/v1/surveys/{survey_id}/quotas \
  -H "X-Service-Token: $TOKEN" \
  -H "X-Team-ID: $TEAM_ID" \
  -H "X-User-ID: $USER_ID"

Response:

{
  "total_target": 1000,
  "total_collected": 347,
  "overall_fill_percentage": 34.7,
  "all_quotas_met": false,
  "quotas": [
    {
      "id": "a1b2c3d4-...",
      "name": "Total",
      "target": 1000,
      "current_count": 347,
      "fill_percentage": 34.7,
      "remaining": 653,
      "is_full": false,
      "is_active": true,
      "definition": null,
      "created_at": "2026-05-26T10:00:00Z",
      "updated_at": "2026-05-26T10:00:00Z"
    },
    {
      "id": "b2c3d4e5-...",
      "name": "Male",
      "target": 500,
      "current_count": 189,
      "fill_percentage": 37.8,
      "remaining": 311,
      "is_full": false,
      "is_active": true,
      "definition": "Q2 == `1`",
      "created_at": "2026-05-26T10:00:00Z",
      "updated_at": "2026-05-26T10:00:00Z"
    },
    {
      "id": "c3d4e5f6-...",
      "name": "Female",
      "target": 500,
      "current_count": 158,
      "fill_percentage": 31.6,
      "remaining": 342,
      "is_full": false,
      "is_active": true,
      "definition": "Q2 == `2`",
      "created_at": "2026-05-26T10:00:00Z",
      "updated_at": "2026-05-26T10:00:00Z"
    }
  ]
}

Agent prompt: "Show me the quota progress for this survey"

Export quota report

The agent's export_quota_report tool returns quota progress combined with response analytics (total responses, completes, completion rate, median time). Use it for fieldwork status updates.

Agent prompt: "Give me a fieldwork status report with quota progress"

Response shape:

{
  "survey_id": "...",
  "analytics": {
    "total_responses": 412,
    "completes": 347,
    "completion_rate": 84.2,
    "median_time_ms": 245000
  },
  "quota_summary": {
    "total_target": 1000,
    "total_collected": 347,
    "overall_fill_percentage": 34.7,
    "all_quotas_met": false
  },
  "quotas": [
    {
      "name": "Total",
      "target": 1000,
      "current_count": 347,
      "fill_percentage": 34.7,
      "remaining": 653,
      "is_full": false
    }
  ]
}

Reconciliation

Reconciliation recounts all quotas from actual response data in PostgreSQL and syncs the counts to Redis. This corrects drift caused by:

  • Response exclusions (manually removing a response after it was counted)
  • Redis failures during ingestion (the system fails open, so responses are accepted)
  • Manual data corrections

Automatic reconciliation

Runs every 5 minutes as a background job. No action required.

Manual reconciliation

Trigger a recount on demand after excluding responses or if counts look off.

curl -X POST https://surveys.flashpoint.ai/api/v1/surveys/{survey_id}/quotas/reconcile \
  -H "X-Service-Token: $TOKEN" \
  -H "X-Team-ID: $TEAM_ID" \
  -H "X-User-ID: $USER_ID"

Response:

{
  "status": "reconciled"
}

Agent prompt: "Reconcile the quotas — I just excluded some test responses"

The agent's reconcile_quotas tool returns the updated counts after reconciliation:

{
  "reconciled": true,
  "total_collected": 340,
  "quotas": [
    {"name": "Total", "target": 1000, "current_count": 340},
    {"name": "Male", "target": 500, "current_count": 185},
    {"name": "Female", "target": 500, "current_count": 155}
  ]
}

Events

The quota system emits events that can drive notifications and automations:

EventTriggerUse case
QUOTA_UPDATEDQuotas are bulk-replacedAudit trail
QUOTA_WARNINGA quota reaches 80% fillAlert the team to adjust panel sources
QUOTA_REACHEDA quota hits its targetClose a panel source, notify stakeholders

Best practices

  1. Always include a total quota. Without one, there is no cap on overall sample size.

  2. Set conditional quotas after building questions. Quota definitions reference question labels, so the questions must exist first.

  3. Use the same DSL syntax as skip logic. The condition language is identical — see Skip logic & conditions for the full reference.

  4. Over-recruit slightly. Set targets 5-10% above your analysis plan to account for exclusions and quality checks.

  5. Monitor fill rates during fielding. Use get_quota_progress or the agent's fieldwork report to check if any cells are filling too slowly.

  6. Reconcile after bulk exclusions. If you exclude a batch of responses (speed checks, quality flags), run reconciliation so the counts reflect the remaining valid data.

  7. Keep quotas simple. Each additional conditional quota adds a Redis key check during ingestion. For typical surveys (under 50 quotas), this has no measurable impact. For very large quota matrices, consider collapsing into fewer conditions.

API reference

MethodEndpointDescription
GET/api/v1/surveys/{survey_id}/quotasGet quota summary with live counts
POST/api/v1/surveys/{survey_id}/quotasCreate a single quota
PUT/api/v1/surveys/{survey_id}/quotasBulk replace all quotas
POST/api/v1/surveys/{survey_id}/quotas/reconcileManual reconciliation

All endpoints require X-Service-Token, X-Team-ID, and X-User-ID headers.

Next steps