# Update opportunity (https://terac.com/docs/developers/reference/updateOpportunity)

`PATCH https://terac.com/api/external/v2/opportunities/{opportunityId}`

Edits a draft opportunity; send only the fields you are changing. A list you send (`tasks`, `filters`, `screening_questions`, `quotas`, `cross_quotas`) replaces the stored one rather than merging into it, so read the opportunity first and send the whole list back. `quotas` and `cross_quotas` are only accepted alongside `screening_questions`. A new `expected_days_to_complete` moves the deadline without re-pricing. A request that changes nothing returns 400, and a launched opportunity returns 409.

## Authorization

Requires a Bearer token via the `Authorization` header.

```
Authorization: Bearer <token>
```

## Path Parameters

- `opportunityId` (string) **(required)**: ID of the opportunity to update

## Request Body (required)

`Content-Type: application/json`

- `title` (string): New participant-facing name
- `internal_title` (string): New internal-only name
- `description` (string): New description
- `num_participants` (integer): New number of completed submissions you need (1-1000 for self-serve).
- `business_type` (string): one of: `b2c`, `b2b`: New audience type: "b2c" = consumer, "b2b" = professional/business
- `customer_screening_review` (string): one of: `auto_invite`, `manual_review`: Whether qualified applicants wait for your decision ("manual_review") or are invited automatically ("auto_invite"). Applies to applicants who qualify after this call; anyone already invited stays invited.
- `device_types` (array): REPLACES the allowed device list wholesale. Omitting this leaves the current restriction untouched (unlike create, where omitting means desktop, mobile_ios, mobile_android and other).
  - values: `desktop`, `mobile_ios`, `mobile_android`, `tablet_ios`, `tablet_android`, `other`
- `tasks` (array): REPLACES the entire task list; it is not merged. Send every task you want to keep, so read the current tasks with terac_get_opportunity first.
  - `sequence` (integer) **(required)**: 1-based order in which the participant performs this task
  - `task_type` (string) **(required)**: one of: `interview`, `file_upload`, `activity`: Pick by WHERE the answer lands, not by what the work is called. Work through these in order and stop at the first that fits: 1. You have a page where they do the work and submit it -> "interview". REQUIRES task_url, that page. 2. No such page, and the deliverable is a file (document, recording, screenshot) -> "file_upload": the expert uploads files through Terac and you read them off the submission, so it needs no task_url. REQUIRES instructions and accepted_mime_types. 3. No such page and the deliverable is not a file -> ask the customer for a URL. The task is still an "interview"; you simply cannot create it until they give you that page. Do not fall through to another type while you wait. 4. You want no output at all -> "activity". It lands nowhere: Terac records the button press and whatever the expert read, decided or wrote is gone. Research, auditing a site, judging output, writing something: all real work, so all end at step 1, 2 or 3. Never reach step 4 to escape step 3, because "activity" does not collect the work, it discards it. None of these book a human-moderated call; that is not available through this API.
  - `review_type` (string) **(required)**: one of: `auto_approve`, `manual_review`, `self_report`: How completion is reviewed and when the expert is paid: "auto_approve" = accepted and paid automatically, but ONLY when the task has a task_url whose provider redirects to the completion callback below; a task with no URL has no way to fire it and falls back to manual review; "self_report" = the participant attests completion, which auto-approves and pays them (you cannot withhold payment based on your own quality signal); "manual_review" = the submission goes to AWAITING_REVIEW and is paid only when you call approve (reject withholds payment). Choose manual_review when you need to gate payout on your own verification.
  - `task_url` (string): Fully-qualified URL (https://...) of the page where the expert BOTH does the work AND submits their answer. REQUIRED on "interview". Not used by "file_upload", which collects its files in Terac. The customer hosts that page, not Terac, and it owes two things: SHOW whatever the expert has to read, and give them an INPUT to answer with. The answer arrives there. If they have no such page, that is what to ask them for. Not a reference to read: a repo, doc or homepage is not a task, because there is nothing to submit from it. A Terac task screen hosts no form and no text box, so a task without this returns only a button press and the expert's work is lost. Files are the exception: use "file_upload". Pasting the material into description does not collect it, and neither does screening_questions. Terac appends tracking params per participant (submissionId, teracSubmissionId, taskId) so you can attribute an answer; the tool description carries the callback contract.
  - `title` (string): Short label for this task
  - `description` (string): What the participant should do in this task
  - `duration_minutes` (integer): Estimated minutes to complete this task; drives pricing, so a duration you guess is a price you invent. Take it from whoever is commissioning the work. Required except for "file_upload", which carries no time estimate.
  - `instructions` (string): REQUIRED on "file_upload": what the expert should upload, in words shown above the drop zone. Ignored by other task types.
  - `accepted_mime_types` (array): REQUIRED on "file_upload": MIME types the expert may upload. Exact types ("application/pdf"), family wildcards ("image/*") and "*/*" for any file all work. Ignored by other task types.
  - `min_files` (integer): For "file_upload": fewest files the expert must upload before they can submit. Defaults to 1.
  - `max_files` (integer): For "file_upload": most files the expert may upload; must be >= min_files and at most 20. Defaults to 1.
  - `max_file_size_bytes` (integer): For "file_upload": per-file size ceiling in bytes. Defaults to 25 MB.
- `filters` (array): REPLACES every hard filter; it is not merged. Send the full targeting list you want to keep. An empty array drops all of them and leaves the study recruiting everyone, so it is accepted only alongside unrestricted_audience: true.
- `unrestricted_audience` (boolean): Set true in the same call that sends an empty filters array, to record that the customer wants every restriction gone. It decides what THIS call does to the audience, so it is rejected on a call that leaves filters alone, and rejected alongside a non-empty filters list.
- `screening_questions` (array): REPLACES the entire screener; it is not merged. Send every question you want to keep, so read the current screener with terac_get_opportunity first.
  - `key` (string): Stable identifier for this question, referenced by quotas[].screening_question and display conditions. Defaults to q0, q1, ... by position if omitted.
  - `text` (string) **(required)**: The screening question text
  - `question_rich_text` (string): Optional Markdown (GFM) version of the prompt, used as the display copy when set (bold, bullet lists, ...). `text` stays required and remains the plaintext source of truth for the voice agent and search, so keep the two in sync.
  - `pick` (string) **(required)**: one of: `one`, `any`, `boolean`, `text`, `grid`: "one" = single-select, "any" = multi-select, "boolean" = yes/no, "text" = open-ended freeform response (no answer options; responses route to manual review), "grid" = matrix (put rows/columns on `grid`, omit answers).
  - `answers` (array): Answer options, each with qualify_logic. Required for choice questions (pick "one" | "any" | "boolean"): at least two. Omit for pick: "text" and pick: "grid".
    - `text` (string) **(required)**: The answer option shown to the participant
    - `qualify_logic` (string) **(required)**: one of: `may`, `must`, `must_one_of`, `reject`, `review`: How this answer affects qualification: "may" = acceptable; "must" = this exact answer is required (valid with pick "one" or "any"); "must_one_of" = counts toward a required any-of group (multi-select, pick: "any"); "reject" = disqualifies; "review" = flag for manual review (routes an otherwise-qualified applicant to customer review instead of auto-inviting; only acts on auto-invite studies). IMPORTANT, single-select collapse: on pick "one" and pick "boolean" only one answer can be selected, so an answer can be neither individually required nor ignored - "may", "must" and "must_one_of" all persist as the same qualifying disposition and all read back as "must". A single-select whose answers are all "may"/"must"/"must_one_of" therefore screens nobody out. Put "reject" on the answers that must screen an applicant out (or "review" to route them to a human), or use pick "any" when you need a different disposition per answer.
    - `allow_free_text` (boolean): Selecting this answer reveals a write-in box ("Other, please specify") whose typed value is captured with the response. Eligibility is unchanged - the answer keeps its qualify_logic. Applies to pick "one" and "any"; ignored for pick "boolean".
  - `grid` (object): Required when pick is "grid"; omit otherwise.
    - `pick` (string): one of: `one`, `any`: How many columns per row: "one" (default) = a single column per row; "any" = any number of columns per row (select all that apply per row).
    - `rows` (array) **(required)**
      - `key` (string): Stable row key; defaults to r0, r1, ... by position.
      - `text` (string) **(required)**: Row label, e.g. a platform name
    - `columns` (array) **(required)**
      - `value` (string): Stable column value; defaults to c0, c1, ... by position.
      - `text` (string) **(required)**: Column label shared across rows
    - `cell_actions` (array): Per-cell qualify logic. Omit a cell for neutral (may).
      - `row` (string) **(required)**: A row key from rows[]
      - `column` (string) **(required)**: A column value from columns[]
      - `qualify_logic` (string) **(required)**: one of: `may`, `must`, `must_one_of`, `reject`
  - `min_qualifying` (integer): Multi-select (pick: "any") only. Require at least this many "must_one_of" answers to be selected to qualify, e.g. min_qualifying: 2 with three must_one_of answers means "select 2+ of these three". Omitted or 1 = the default "at least one of" behavior. Ignored for other pick types.
  - `display_condition` (object): Show this question only if earlier answers match ("ask only if"). Conditions reference EARLIER questions by key.
    - `conditions` (array) **(required)**
      - `screening_question` (string) **(required)**: The key of an EARLIER question this one depends on
      - `answer` (string) **(required)**: An answer of that earlier question. For a grid source use "Row: Column".
      - `operator` (string): one of: `eq`, `ne`: "eq" = answered that value, "ne" = did not.
    - `join` (string): one of: `and`, `or`
  - `conditional_rules` (array): Cross-question consistency / anti-falsification checks that reject or flag when answers match a pattern (e.g. "if role is Store Manager but none of the management tasks are selected here, flag for review"). Write one all-"and" rule per role. Reference an earlier question with screening_question; omit it to test this question's own selections.
    - `conditions` (array) **(required)**
      - `screening_question` (string): Key of the question this condition reads. Omit to test an answer on THIS question (a self-check).
      - `answer` (string) **(required)**: An answer by its label. For a grid source use "Row: Column".
      - `operator` (string): one of: `eq`, `ne`: "eq" = selected that answer, "ne" = did not.
    - `join` (string): one of: `and`, `or`
    - `outcome` (string) **(required)**: one of: `reject`, `review`: "reject" screens the participant out, "review" flags for a human, when the rule matches.
  - `skip_rules` (array): Branching: when a rule's conditions hold, jump straight to target_question (or end the screener when it is null), skipping everything in between. Conditions test THIS question's answers, and the target must be a later question. Not available for pick "text" or "grid".
    - `conditions` (array) **(required)**
      - `screening_question` (string) **(required)**: The key of the question this rule lives on. A rule tests its OWN answers; use display_condition to gate a question on an earlier one.
      - `answer` (string) **(required)**: One of that question's answers (its text; "Yes"/"No" for pick: "boolean").
      - `operator` (string) **(required)**: one of: `eq`, `ne`: "eq" = answered that value, "ne" = did not.
    - `join` (string) **(required)**: one of: `and`, `or`
    - `target_question` (string, nullable) **(required)**: Key of the question to jump to when the conditions hold; null ends the screener. Must be a LATER question - the screener only walks forward.
  - `allow_paste` (boolean): Open-ended (pick: "text") only. Whether participants may paste into the freeform field. Pasting is blocked by default to discourage low-effort copied responses; set true for questions that ask for a pasted value (e.g. a link). Ignored for choice questions.
- `quotas` (array): REPLACES every per-answer target count. Only accepted alongside screening_questions in the SAME call, because a quota attaches to one of those answers.
  - `screening_question` (string) **(required)**: The key of the screening question this quota applies to (must match a screening_questions[].key)
  - `targets` (array) **(required)**: Per-answer target counts for this question
    - `value` (string) **(required)**: The answer option text (matches a screening answer's text) this quota targets
    - `count` (integer) **(required)**: How many approved submissions to allow/require for this answer
    - `type` (string): one of: `exact`, `minimum`, `maximum`: How the count is enforced: "minimum" = at least this many (a floor; recruiting prioritizes the answer while it is short); "maximum" = at most this many (a cap; further applicants who pick this answer are screened out once it is full); "exact" = both. Omit for "minimum".
- `cross_quotas` (array): REPLACES every cross-question (cross-tab) quota. Only accepted alongside screening_questions in the SAME call, because each cell references those answers. Send an empty array to drop all cross-quotas.
  - `label` (string) **(required)**: Human label for this cell, e.g. "Brazil x Agency".
  - `conditions` (array) **(required)**: The conditions, one per dimension: one for a simple per-answer quota, two+ to interlock a cross-tab across distinct questions.
    - `screening_question` (string) **(required)**: Key of a screening_questions[].key this condition tests (one dimension of the cross-tab).
    - `answer` (string) **(required)**: An answer of that question, by its label. For a grid source use "Row: Column".
    - `operator` (string): one of: `eq`, `ne`: "eq" = selected that answer, "ne" = did not.
  - `join` (string): one of: `and`, `or`
  - `target` (integer) **(required)**: Target participant count for this cell.
  - `quota_type` (string): one of: `exact`, `minimum`, `maximum`: "exact" = hit exactly, "minimum" = at least, "maximum" = no more than.
  - `dimension` (string): Cells sharing a dimension are summed as one interlocked cross-tab quota; use one token per cross-tab.
- `expected_days_to_complete` (integer): New recruitment window in calendar days counted from today (min 5). Moves the deadline only; it does not re-price the opportunity.

## Responses

### 200: The draft as stored after the edit.

- `id` (string) **(required)**
- `title` (string) **(required)**
- `internal_title` (string, nullable) **(required)**
- `description` (string, nullable) **(required)**
- `status` (string) **(required)**: one of: `draft`, `active`, `fulfilled`, `paused`, `stopped`, `completed`
- `num_participants` (integer) **(required)**
- `estimated_duration_minutes` (integer) **(required)**
- `filters` (array) **(required)**
- `screening_questions` (array) **(required)**
  - `key` (string) **(required)**: Identifier for this question, yours or assigned. Comes back as `screening_answers[].key`, so answers join to their question. Opaque.
  - `text` (string) **(required)**
  - `question_rich_text` (string): Markdown version of the prompt, when the author wrote one. `text` stays the plaintext source of truth.
  - `pick` (string) **(required)**: one of: `one`, `any`, `boolean`, `text`, `grid`
  - `answers` (array)
    - `text` (string) **(required)**
    - `qualify_logic` (string) **(required)**: one of: `may`, `must`, `must_one_of`, `reject`, `review`: The disposition that is actually stored, which is not always the one that was sent. On a single-select (pick "one", which is also how a yes/no question reads back) every qualifying answer reads back as "must", because "may", "must" and "must_one_of" collapse to one disposition where only one answer can be picked. So a "may" answer reading back as "must" is the stored truth, not a mistranslation - rewriting it to "may" will not restore a filter. Only "reject" and "review" screen or flag on a single-select; switch the question to pick "any" if you need per-answer dispositions.
    - `allow_free_text` (boolean): Selecting this answer reveals a write-in box ("Other, please specify").
  - `grid` (object)
    - `pick` (string): one of: `one`, `any`
    - `rows` (array) **(required)**
      - `key` (string)
      - `text` (string) **(required)**
    - `columns` (array) **(required)**
      - `value` (string)
      - `text` (string) **(required)**
    - `cell_actions` (array)
      - `row` (string) **(required)**
      - `column` (string) **(required)**
      - `qualify_logic` (string) **(required)**: one of: `may`, `must`, `must_one_of`, `reject`, `review`
  - `min_qualifying` (integer)
  - `display_condition` (object)
    - `conditions` (array) **(required)**
      - `screening_question` (string) **(required)**: The key of an EARLIER question this one depends on
      - `answer` (string) **(required)**: An answer of that earlier question. For a grid source use "Row: Column".
      - `operator` (string) **(required)**: one of: `eq`, `ne`: "eq" = answered that value, "ne" = did not.
    - `join` (string) **(required)**: one of: `and`, `or`
  - `conditional_rules` (array)
    - `conditions` (array) **(required)**
      - `screening_question` (string)
      - `answer` (string) **(required)**
      - `operator` (string) **(required)**: one of: `eq`, `ne`
    - `join` (string) **(required)**: one of: `and`, `or`
    - `outcome` (string) **(required)**: one of: `reject`, `review`
  - `skip_rules` (array): Branching authored here or in the dashboard, in the same shape create accepts, so it can be sent straight back on an update. Omitted when the question has none - an absent field means no branching, never that branching was dropped on read.
    - `conditions` (array) **(required)**
      - `screening_question` (string) **(required)**: The key of the question this rule lives on. A rule tests its OWN answers; use display_condition to gate a question on an earlier one.
      - `answer` (string) **(required)**: One of that question's answers (its text; "Yes"/"No" for pick: "boolean").
      - `operator` (string) **(required)**: one of: `eq`, `ne`: "eq" = answered that value, "ne" = did not.
    - `join` (string) **(required)**: one of: `and`, `or`
    - `target_question` (string, nullable) **(required)**: Key of the question to jump to when the conditions hold; null ends the screener. Must be a LATER question - the screener only walks forward.
  - `allow_paste` (boolean)
- `quotas` (array) **(required)**
  - `screening_question` (string) **(required)**: The key of the screening question this quota applies to (must match a screening_questions[].key)
  - `targets` (array) **(required)**: Per-answer target counts for this question
    - `value` (string) **(required)**: The answer option text (matches a screening answer's text) this quota targets
    - `count` (integer) **(required)**: How many approved submissions to allow/require for this answer
    - `type` (string): one of: `exact`, `minimum`, `maximum`: How the count is enforced: "minimum" = at least this many (a floor; recruiting prioritizes the answer while it is short); "maximum" = at most this many (a cap; further applicants who pick this answer are screened out once it is full); "exact" = both. Omit for "minimum".
- `cross_quotas` (array) **(required)**
  - `label` (string) **(required)**: Human label for this cell, e.g. "Brazil x Agency".
  - `conditions` (array) **(required)**: The conditions, one per dimension: one for a simple per-answer quota, two+ to interlock a cross-tab across distinct questions.
    - `screening_question` (string) **(required)**: Key of a screening_questions[].key this condition tests (one dimension of the cross-tab).
    - `answer` (string) **(required)**: An answer of that question, by its label. For a grid source use "Row: Column".
    - `operator` (string) **(required)**: one of: `eq`, `ne`: "eq" = selected that answer, "ne" = did not.
  - `join` (string) **(required)**: one of: `and`, `or`
  - `target` (integer) **(required)**: Target participant count for this cell.
  - `quota_type` (string) **(required)**: one of: `exact`, `minimum`, `maximum`: "exact" = hit exactly, "minimum" = at least, "maximum" = no more than.
  - `dimension` (string): Cells sharing a dimension are summed as one interlocked cross-tab quota; use one token per cross-tab.
- `tasks` (array) **(required)**
  - `sequence` (integer) **(required)**
  - `task_type` (string) **(required)**: Any stored task type, so wider than the set you can create: interview, survey, file_upload, activity, scheduled_interview, agreement, join_slack, slack_handoff, e_signature. Only interview, file_upload and activity are accepted on create.
  - `review_type` (string) **(required)**
  - `task_url` (string, nullable) **(required)**: null when the task type carries no URL.
  - `participant_url_template` (string): task_url with the tracking params Terac appends per participant (submissionId, teracSubmissionId, taskId), shown as {submissionId}/{taskId} macros. Omitted when there is no task_url.
  - `title` (string)
  - `description` (string)
  - `duration_minutes` (integer) **(required)**
  - `available_after_sequence` (integer): This task unlocks only after that task is approved.
  - `available_after_delay_minutes` (integer): Wait this long after the gating task before unlocking.
  - `provider` (string): Scheduled interview only: the booking provider.
  - `calendar_owner` (string): Scheduled interview only: whose calendar is booked (researcher or expert).
  - `event_type_url` (string): Scheduled interview only: the booking link.
  - `instructions` (string): File upload only: what to submit.
  - `accepted_mime_types` (array): "file_upload" only, and absent on every other task type. RESOLVED rather than echoed: a task created without this reads back with the default actually enforced, not an empty field.
  - `min_files` (integer): "file_upload" only, and absent on every other task type. Resolved to the enforced default when the task did not set one.
  - `max_files` (integer): "file_upload" only, and absent on every other task type. Resolved to the enforced default when the task did not set one.
  - `max_file_size_bytes` (integer): "file_upload" only, and absent on every other task type. Resolved to the enforced default when the task did not set one.
- `pricing` (object, nullable) **(required)**
  - `cost_per_participant_cents` (integer) **(required)**
  - `total_cost_cents` (integer) **(required)**
  - `currency` (string) **(required)**: one of: `usd`
- `funding` (object)
  - `affordable` (boolean) **(required)**: Covers the cost, overdraft included. False blocks nothing.
  - `draws_on_credit` (boolean) **(required)**: Affordable but goes negative, invoiced later. Say so.
  - `balance_cents` (integer) **(required)**: Balance now.
  - `shortfall_cents` (integer) **(required)**: Gap after overdraft, 0 when affordable.
  - `finance_url` (string, nullable) **(required)**: Where billing adds credit.
- `device_types` (array) **(required)**
- `business_type` (string, nullable) **(required)**
- `candidate_profile` (string, nullable) **(required)**: Free-text profile of the ideal candidate, when set.
- `required_accounts` (array) **(required)**: Accounts an expert must have linked to apply (e.g. linkedin).
- `video_required` (string) **(required)**: Whether the expert's camera is needed: none, requested, required.
- `screen_share_required` (string) **(required)**: Whether a screen share is needed: none, requested, required.
- `customer_screening_review` (string) **(required)**: auto_invite = qualified applicants are invited automatically; manual_review = you pick who to invite.
- `pay_frequency` (string) **(required)**: How the incentive is framed: one_time, hourly, or per_task.
- `screening_option_randomization` (string) **(required)**: Whether screening answer options are shown in random order: the string "enabled" or "disabled", not a boolean.
- `forwarding_email_enabled` (string) **(required)**: Whether each submission gets a Terac forwarding email address: the string "enabled" or "disabled", not a boolean.
- `max_responses_per_expert` (integer, nullable) **(required)**
- `feasibility_request_id` (string, nullable) **(required)**
- `project_id` (string) **(required)**
- `created_at` (string) **(required)**
- `updated_at` (string) **(required)**
- `ending_at` (string, nullable) **(required)**: Target completion date for recruitment.
- `launched_at` (string, nullable) **(required)**
- `links` (object) **(required)**
  - `self` (string) **(required)**
  - `launch` (string, nullable) **(required)**
  - `submissions` (string) **(required)**
  - `dashboard` (object) **(required)**: Pages a person opens in a browser. `self`, `launch` and `submissions` above are API paths for you, not for them - never give a customer one of those.
    - `study` (string, nullable) **(required)**: The study's own page. It routes itself by status: an editable draft opens the builder, anything else opens submissions. Never null. Give this when you do not know which tab the customer needs; when `draft_editor` is set, prefer that instead, since it opens the same builder the customer would review in. Null if Terac cannot resolve the organization or project this link belongs to.
    - `draft_editor` (string, nullable) **(required)**: Opens this draft in the study builder so a human can review the whole setup (targeting, screener, tasks, price) before anything launches. Set exactly when `submissions` here is null, never both. Give this to the customer after you build a draft, instead of launching. Null if Terac cannot resolve the organization or project this link belongs to.
    - `submissions` (string, nullable) **(required)**: Who applied, who is in progress, who is awaiting review. Set exactly when `draft_editor` here is null. Do not read that as the opportunity being live: the `status` field reports several settled states (cancelled, expired, failed) as `draft`, so read status from `status` and treat this only as a link. Give this when the customer asks about progress or has submissions to review. Null if Terac cannot resolve the organization or project this link belongs to.
    - `recruitment` (string, nullable) **(required)**: The screener questions and quotas every applicant is filtered against. Set exactly when `draft_editor` here is null. Give this when the customer wants to check who is being screened in or out. Null if Terac cannot resolve the organization or project this link belongs to.
    - `task` (string, nullable) **(required)**: What a participant is actually asked to do - task type, duration, and task link. Set exactly when `draft_editor` here is null. Give this when the customer asks what the participants see. Null if Terac cannot resolve the organization or project this link belongs to.
    - `settings` (string, nullable) **(required)**: The study's own settings: recruitment window, participant count, and the controls to stop or delete it. Set exactly when `draft_editor` here is null. Give this when the customer wants to change or end a running study rather than look at its results. Pause and resume are not here - they are on the opportunities list. Null if Terac cannot resolve the organization or project this link belongs to.
- `submission_stats` (object)
  - `total` (integer) **(required)**
  - `in_progress` (integer) **(required)**
  - `awaiting_review` (integer) **(required)**
  - `approved` (integer) **(required)**
  - `rejected` (integer) **(required)**
- `quota_progress` (array)
  - `screening_question` (string) **(required)**
  - `targets` (array) **(required)**
    - `value` (string) **(required)**
    - `count` (integer) **(required)**
    - `current` (integer) **(required)**
    - `type` (string) **(required)**: How the count is enforced: exact, minimum or maximum. Tells you whether "current" nearing "count" means a cap is about to close or a floor is about to be met.
- `cross_quota_progress` (array): Fill progress for each cross-question quota cell (live studies).
  - `label` (string) **(required)**: Human label for this cell, e.g. "Brazil x Agency".
  - `conditions` (array) **(required)**: The conditions, one per dimension: one for a simple per-answer quota, two+ to interlock a cross-tab across distinct questions.
    - `screening_question` (string) **(required)**: Key of a screening_questions[].key this condition tests (one dimension of the cross-tab).
    - `answer` (string) **(required)**: An answer of that question, by its label. For a grid source use "Row: Column".
    - `operator` (string) **(required)**: one of: `eq`, `ne`: "eq" = selected that answer, "ne" = did not.
  - `join` (string) **(required)**: one of: `and`, `or`
  - `target` (integer) **(required)**: Target participant count for this cell.
  - `quota_type` (string) **(required)**: one of: `exact`, `minimum`, `maximum`: "exact" = hit exactly, "minimum" = at least, "maximum" = no more than.
  - `dimension` (string): Cells sharing a dimension are summed as one interlocked cross-tab quota; use one token per cross-tab.
  - `current` (integer) **(required)**: Approved participants matching this cell so far.
- `screening_stats` (array): Per-question screen-out rates and answer breakdown (live studies).
  - `screening_question` (string) **(required)**: The `screening_questions[].key`.
  - `graded` (integer) **(required)**: Screeners GRADED here, not answers given: a branching screener stops at the first failure.
  - `rejected` (integer) **(required)**: Rejected by this question.
  - `rejection_rate` (number) **(required)**: `rejected` / `graded`, 0 to 1.
  - `answers` (array) **(required)**: How often each answer was chosen, for spotting a screener that is too narrow.
    - `text` (string) **(required)**: The answer as written on the question, not its stored value.
    - `selected` (integer) **(required)**
    - `share` (number) **(required)**: `selected` / `graded`, between 0 and 1.
### 400: Invalid input data

- `message` (string) **(required)**: The error message
- `code` (string) **(required)**: The error code
- `issues` (array): An array of issues that were responsible for the error
  - `message` (string) **(required)**
### 401: The API key is missing, invalid, disabled, expired or revoked, its owner no longer exists, or it is not linked to an organization. The body is nested under `error`.

- `error` (object) **(required)**
  - `code` (string) **(required)**: one of: `UNAUTHORIZED`: The error code.
  - `message` (string) **(required)**: What went wrong.
### 403: Either the account that owns the API key is banned or deleted (from the key check, nested under `error`), or the key's owner lacks the organization permission this operation needs (from the operation, with `code` and `message` at the top level).

- One of: API key error (403)
  - `error` (object) **(required)**
    - `code` (string) **(required)**: one of: `FORBIDDEN`: The error code.
    - `message` (string) **(required)**: What went wrong.
- One of: Insufficient access error (403)
  - `message` (string) **(required)**: The error message
  - `code` (string) **(required)**: The error code
  - `issues` (array): An array of issues that were responsible for the error
    - `message` (string) **(required)**
### 404: Not found

- `message` (string) **(required)**: The error message
- `code` (string) **(required)**: The error code
- `issues` (array): An array of issues that were responsible for the error
  - `message` (string) **(required)**
### 429: The API key is rate limited. A key accepts 100 requests, then refuses every request until more than 60 seconds pass with no accepted request; refused requests do not extend the wait. Wait the number of seconds in `Retry-After`, then retry. The body is nested under `error`.

- `error` (object) **(required)**
  - `code` (string) **(required)**: one of: `RATE_LIMITED`: The error code.
  - `message` (string) **(required)**: What went wrong.
### 500: Internal server error

- `message` (string) **(required)**: The error message
- `code` (string) **(required)**: The error code
- `issues` (array): An array of issues that were responsible for the error
  - `message` (string) **(required)**