API reference
Language-agnostic REST API for discovering cohorts and creating submissions. Use curl, any HTTP client, or try endpoints in the Explorer tab.
Loading…
Language-agnostic REST API for discovering cohorts and creating submissions. Use curl, any HTTP client, or try endpoints in the Explorer tab.
Loading…
Prefer these runnable Next.js samples when onboarding — they use the same env vars as Account → API keys (PROJEX_API_URL, PROJEX_API_KEY).
Example apps
Clone these Next.js samples to try the same flow as Account → API keys: live cohorts → milestones/tasks → submit (with optional file attachments).
The Submission API is a versioned JSON REST surface under /api/v1. Every request runs as the user who owns the API key. Server-side rules (enrollment, deadlines, team membership) match the web app — the API is not a softer path.
Typical flow: create a key → list live cohorts → list milestones or tasks → submit. Prefer the TypeScript SDK for Node/CI; use this HTTP API from any other language.
Paths in this guide are relative to your app origin plus /api/v1. Trailing slashes are not required.
# Local http://localhost:3000/api/v1 # Production (example) https://projex.xceleratordemo.in/api/v1
OpenAPI machine-readable spec: GET /api/v1/openapi.json.
Create a personal key in Account → API keys. The secret is shown once and looks like pjx_live_…. Send it on every request using either header:
| Header | Example |
|---|---|
Authorization | Bearer pjx_live_… |
x-api-key | pjx_live_… |
JSON bodies use Content-Type: application/json. Successful responses include x-request-id for support correlation.
Scopes are chosen when you generate the key. They only narrow what the owning user can already do.
| Scope | Allows |
|---|---|
cohorts:read | List and get cohorts the user belongs to |
milestones:read | List milestones and tasks in a cohort |
submissions:read | Read the user’s own submissions |
submissions:write | Create submissions and presign uploads |
cohorts:write | Create, update and delete cohorts |
teams:read | List the teams in a cohort |
teams:write | Create, update and delete teams |
cohort-members:write | Enrol, re-role and remove cohort members |
team-members:write | Add, re-role and remove a team’s learners and staff |
The last five are admin scopes. They are checked twice: the scope says what the key may do, and the key owner must separately hold the role — in that specific cohort or team — to do it. A manager of one cohort can do nothing in another.
High-level map of v1. Switch to the Explorer tab for full schemas and Try it out.
| Method | Path | Scope | Purpose |
|---|---|---|---|
GET | /cohorts | cohorts:read | List enrollments; optional ?status=live|planned|closed |
GET | /cohorts/{cohortId} | cohorts:read | Cohort detail + caller’s teams |
GET | /cohorts/{cohortId}/milestones | milestones:read | Milestones — global to the cohort |
GET | /cohorts/{cohortId}/tasks | milestones:read | Your team(s)’ tasks; ?milestoneId=, ?assignee=me|others |
GET | /milestones/{ref}/submissions | submissions:read | Caller’s milestone submissions |
POST | /milestones/{ref}/submissions | submissions:write | Submit or resubmit a milestone |
GET | /tasks/{ref}/submissions | submissions:read | Caller’s task submissions |
POST | /tasks/{ref}/submissions | submissions:write | Submit or resubmit a task |
POST | /uploads | submissions:write | Presign a file upload (max 50 MB); returns publicUrl |
POST | /cohorts | cohorts:write | Create a cohort under a project template; you become its manager |
PATCH | /cohorts/{cohortId} | cohorts:write | Update name, visibility, dates or cluster |
DELETE | /cohorts/{cohortId} | cohorts:write | Soft-delete the cohort and everything under it; ?dryRun=true previews |
GET | /cohorts/{cohortId}/teams | teams:read | List the cohort’s teams; ?teamKind=multi|solo and ?memberEmail= narrow it |
POST | /cohorts/{cohortId}/teams | teams:write | Create a team (individual cohorts make solo teams themselves) |
PATCH / DELETE | /teams/{teamId} | teams:write | Update or soft-delete a team |
POST / PATCH / DELETE | /cohorts/{cohortId}/members | cohort-members:write | Add by email (creating the user if needed), change role, or remove |
POST / PATCH / DELETE | /teams/{teamId}/members | team-members:write | Same three verbs for a team’s learners and staff |
Admin routes. Members are identified by email, never by an internal membership id, and adding one walks the whole chain — app user, org membership, cohort enrolment, optional team placement — skipping every layer that already exists, so retries are safe. Every mutating route accepts ?dryRun=true to validate and preview without writing, and honours Idempotency-Key. Deletes are soft and cascade.
Creating a cohort. POST /cohorts takes the project template it runs from, a name, and a date range:
curl -sS -X POST \
-H "Authorization: Bearer $PROJEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"projectId": "prj_123",
"name": "Cohort 12",
"type": "team",
"visibility": "private",
"clusterId": "cl_9",
"startDate": "2026-08-01",
"endDate": "2026-10-01"
}' \
"$PROJEX_API_URL/api/v1/cohorts"| Field | Required | Notes |
|---|---|---|
projectId | Yes | The project template the cohort runs from. You need manage-project on it. |
name | Yes | Non-empty string. |
startDate | Yes | Any parseable date. endDate must be on or after it. |
endDate | Yes | See above. |
type | No — defaults to team | team or individual. An individual cohort gives every learner an automatic solo team, and rejects POST …/teams with 409 conflict (details.reason: individual_cohort). |
visibility | No — defaults to private | public or private. |
clusterId | Only when visibility is private | The cluster whose roster seeds the cohort. Optional (and simply stored) for a public cohort. |
Watch the visibility default. Omitting it means private, so a body with no visibility and no clusterId fails with 422 validation_error — “Cluster is required for private cohorts”. Send "visibility": "public" if you don’t want a cluster. The same rule applies on PATCH: switching a cohort to private needs a clusterId either in the body or already on the cohort.
Scoping. Milestones are global to the cohort — the team is only resolved at submit time (auto-picked, or pass teamId if you’re on several). Tasks are team-scoped: you get the tasks of the team(s) you belong to in that cohort (multi-team users get all their teams’), narrowable with ?assignee=me or ?assignee=others.
Submitting. A submission is a single value (a URL). Paste a link, or upload a file via POST /uploads and use its returned publicUrl as the submission (optionally also attach it). Refs: tasks use TS42; milestones use T12M3 (team + milestone public id) — or the ref from the list response, which is already directly submittable.
Every list endpoint pages results and supports search. Pass limit (default 50, max 100), cursor (opaque — from the previous response), and q (free-text over name/title). The response adds nextCursor, which is null on the last page.
# First page, searching for "sprint"
GET https://projex.xceleratordemo.in/api/v1/cohorts/{cohortId}/tasks?q=sprint&limit=25
{
"tasks": [ /* … up to 25 … */ ],
"nextCursor": "b2ZmOjI1"
}
# Next page — pass the cursor back verbatim
GET https://projex.xceleratordemo.in/api/v1/cohorts/{cohortId}/tasks?q=sprint&limit=25&cursor=b2ZmOjI1Treat cursor as opaque — don't parse or construct it. In the SDK, list methods return { data, nextCursor }, and client.cohorts.iterate() walks every page for you.
All failures return a stable JSON envelope. Use code for branching and requestId when contacting support.
{
"error": {
"code": "validation_error",
"message": "Cannot submit after the deadline has passed",
"requestId": "req_…",
"details": {}
}
}| code | HTTP | Meaning |
|---|---|---|
invalid_api_key | 401 | Missing, bad, expired, or disabled key |
insufficient_scope | 403 | Key missing required scope |
not_found | 404 | Unknown resource or no access |
ambiguous_team | 409 | Pass teamId — user is on multiple teams |
conflict | 409 | Admin routes: the request clashes with current state — branch on details.reason (individual_cohort, role_locked_to_cohort, not_cohort_member, user_banned) |
validation_error | 422 | Invalid body or closed submission window |
rate_limited | 429 | Slow down; honor Retry-After when present |
internal_error | 500 | Unexpected server failure |
Full apps live in the example repos above. Snippets below are the same HTTP calls those apps make.
List live cohorts:
curl -sS \ -H "Authorization: Bearer $PROJEX_API_KEY" \ "$PROJEX_API_URL/api/v1/cohorts?status=live"
Submit a milestone (single-team user — omit teamId):
curl -sS -X POST \
-H "Authorization: Bearer $PROJEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Sprint deliverable",
"submission": "https://github.com/org/repo/pull/42"
}' \
"$PROJEX_API_URL/api/v1/milestones/T12M3/submissions"Submit a task:
curl -sS -X POST \
-H "Authorization: Bearer $PROJEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Bugfix notes",
"submission": "## Steps\n1. …"
}' \
"$PROJEX_API_URL/api/v1/tasks/TS42/submissions"