PUT /v1/projects/{projectId}/quotas. You can also send the same { version: "v2", groups[] } body as quotas on POST /v1/projects. /v1/projects/{projectId}/quota is an alias of /quotas.
A project with no quota returns 200 and { version: "v2", groups: [] }. 404 is only for a project that does not exist.
A quota holds at most 20 groups, and a group at most 50 segments; a configuration over either cap is rejected with a 400.
Conditions
Each segment has a rule tree. Nodes arekind: "group" (AND / OR children) or kind: "condition". Age is a numeric attribute, not age-band enums.
A participant who skipped a question matches no condition on it,
none and count included; a CHECKBOX answered with nothing selected counts as 0.
Targets are { op: "min" }, { op: "max" }, { op: "exact" } (each with value) or { op: "track" } for counted-but-uncapped buckets. Address a group’s implicit leftover bucket with segmentId: "no-match".
Sample workflow
- Create a project via POST /v1/projects, optionally with
quotas. - Create or replace the quota with PUT /v1/projects/{projectId}/quotas (allowed on draft and published projects).
- Publish. Matching and invites consume the quota. A public invite is always admitted: the invite succeeds, consumption is recorded, and the bucket may go over target. There is no 409 on this path.
- Read fill with GET /v1/projects/{projectId}/quotas. Each screener response that occupies a quota carries
quotaAssignment(segments[],tier,revision,assignedAt,tierChangedAt). - Subscribe to
QUOTAS.SEGMENT_FILLEDandQUOTAS.FILLEDon your webhook.
Create or replace
PUT /v1/projects/{projectId}/quotas A group or segment whoseid matches an existing one keeps its progress. An omitted or new id is a new bucket at zero. The response includes changes (created, preserved, removed, rebucketed, externalFillsDropped, overTarget) and warnings.
Update targets
PATCH /v1/projects/{projectId}/quotas/targets Sparse array. One entry per bucket; a duplicate(groupId, segmentId) is 400. Omitted buckets are untouched. Tracked-only is { "op": "track" }. Rules and progress are not rewritten.
Read
GET /v1/projects/{projectId}/quotas Config and progress together, plus derivedfilled, closed, and per-group minProtected. version discriminates the body: "v2" is the document above; "v1" is a legacy { criteria, progress, totalTarget, status } shape still returned for projects that have not been moved.
External fills
PUT /v1/projects/{projectId}/quotas/external-fills Bare array of{ groupId, segmentId, count }. Absolute counts, applied atomically.
Delete
DELETE /v1/projects/{projectId}/quotas Returns204. The next GET is the empty V2 configuration. Assignments on existing responses are not released by delete.
References
Example of how quotas are created via the Respondent Researcher Platform
