The opportunities UI can filter and page review work without downloading a
large client-side bucket. All routes below use the production /v6 prefix.
GET /review-opportunities/search returns the standard response envelope:
{
"result": {
"success": true,
"status": 200,
"content": [],
"metadata": {
"total": 0,
"offset": 0,
"limit": 10,
"page": 1,
"totalPages": 0
}
}
}Supported query parameters are:
search, repeatedchallengeIds;paymentFrom,paymentTo,durationFrom,durationTo,startDateFrom,startDateTo,numSubmissionsFrom, andnumSubmissionsTo;- repeated challenge
tracks/trackandtypes/type, by UUID or catalog name; - repeated
opportunityTypesvalues; - repeated
status/statuses; omission keeps the legacyOPENdefault; appliedByMeand repeatedapplicationStatuses, which require a caller;sortBy=basePayment|duration|startDate|openPositions,sortOrder,limit(maximum 100), and zero-basedoffset.
Challenge-backed filters run in the challenge database; pagination and totals
run in the review database after active-challenge visibility filtering. That
visibility boundary enforces both ChallengeUserWhitelist and challenge group
membership. Anonymous users receive only ungrouped challenges, ordinary
members receive public challenges plus their complete groups-api ancestor tree,
and a groups-api failure hides restricted records rather than exposing them.
Task challenges are hidden from anonymous callers and require the member to
have a MemberChallengeAccess resource; Admin and M2M callers retain their
operational access. A resource holder also retains access to an assigned
group-restricted challenge, matching challenge-api-v6 self-resource searches.
An OPEN review opportunity is returned only while its linked challenge is
ACTIVE. CLOSED and CANCELLED filters preserve historical opportunities
after the linked challenge completes.
GET /review-opportunities accepts the same query but preserves its historical
bare-array response. Pagination is returned in CORS-exposed X-Total-Count,
X-Page, X-Per-Page, and X-Total-Pages headers.
Every opportunity item adds:
canApply;canApplyReason:CAN_APPLY,NOT_AUTHENTICATED,NOT_REVIEWER,OPPORTUNITY_CLOSED,CHALLENGE_NOT_ACTIVE,ALREADY_APPLIED, orNO_OPEN_POSITIONS;myApplications, containing only the caller's applications;applicationCount, the public-safe total across all application statuses;approvedApplicationCountandremainingPositions;applicationRolesanddefaultApplicationRole, which let a one-click UI submit the correct specialized role for regular, scenarios, iterative, specification, or component-development review work.
Search/list responses include only the caller's application rows (or none for
anonymous callers). applicationCount and approvedApplicationCount are
separate database aggregate counts: the former is every application status and
the latter is only approved applications occupying capacity. This gives public
cards an accurate total without exposing applicant identity or downloading
every applicant. The single-opportunity and challenge-detail routes retain the
public applicant panel for the explicit detail click, but only after the linked
challenge passes the same whitelist, group, and task visibility boundary used
by search. Each panel resolves openReviews and 60-day
latestCompletedReviews totals in one batched query rather than returning
placeholder values or issuing one query per applicant.
GET /review-applications/opportunity/{opportunityId} remains compatible with
anonymous callers for visible public opportunities. It first resolves the
linked challenge and applies that same visibility boundary before querying any
application rows. A missing opportunity returns 404; a caller denied by a
challenge whitelist, group restriction, or task assignment receives the same
404 response, preventing opportunity-ID probing and never returning applicant
identities or metrics.
Challenge card data for a result page is hydrated with one batch projection; detail-only phases, workflows, metadata, and winners are loaded only when a specific opportunity is opened.
The embedded challengeData object includes name, the legacy title alias,
and the challenge's Markdown description. Its overview alias contains the
same Markdown so the opportunities detail page can render the full brief with
one review-api request.
Only the exact Reviewer role produces CAN_APPLY. This supports the UI rule
that non-reviewers receive a disabled action and the “How to become a reviewer”
content.
GET /review-opportunities/me is authenticated, forces appliedByMe=true, and
returns the metadata envelope. GET /review-applications/me supports repeated
statuses, repeated roles, opportunityId, page, perPage (maximum 100),
and sortOrder; its metadata is total, page, perPage, and totalPages.
POST /review-applications remains compatible with
{ "opportunityId": "...", "role": "REVIEWER" }, while now failing closed
for a closed opportunity, inactive or inaccessible challenge, duplicate
application, or filled approved capacity. The database composite uniqueness
constraint on opportunity, member, and role is authoritative for concurrent
duplicate requests; the losing request receives the same HTTP 409 conflict as
a duplicate found by the pre-check. Applications are created as PENDING, so
they do not consume or overfill the approved-position capacity.