Skip to content

Playtests Endpoints

These endpoints are for game owners managing playtests for their games. All require the game_owner scope.

GET /api/v1/games/:gameId/playtests

ParameterTypeDefaultDescription
limitinteger20Max 100
cursorstringPagination cursor
{
"data": {
"playtests": [
{
"id": "pt-uuid",
"gameId": "game-uuid",
"visibility": "private",
"quantity": 3,
"durationMinutes": 60,
"playerCount": 1,
"costPerSlotCents": 2000,
"payoutCents": 1000,
"notesForTesters": "Focus on the tutorial flow",
"status": "active",
"isFreePublicTrial": false,
"targetingType": null,
"createdAt": "2026-02-01T10:00:00.000Z",
"slots": {
"open": 1,
"reserved": 1,
"submitted": 1,
"accepted": 0,
"rejected": 0,
"expired": 0
}
}
],
"eligibleForFreeTrial": true
},
"meta": {
"requestId": "req_abc123def456",
"timestamp": "2026-03-02T12:00:00.000Z",
"cursor": "eyJpZCI6ImFiYzEyMyJ9",
"hasMore": false
}
}

POST /api/v1/games/:gameId/playtests

Order playtests for a game.

FieldTypeRequiredDescription
visibilitystringNopublic or private (default: private)
quantityintegerNoNumber of slots to create, 1-100 (default: 1)
durationMinutesintegerNo30, 60, 120, or 180 (default: 60)
playerCountintegerNoPlayers per session, 1-8 (default: 1)
notesForTestersstringNoInstructions for playtesters (max 5000 chars). Only visible after claiming a slot.
keysForTestersstring[]NoGame keys distributed one per slot. Each playtester sees only their assigned key after claiming. Keys are cleared from the request after distribution.
isFreePublicTrialbooleanNoUse free public trial (one per game, singleplayer 30min only)
targetingTypestringNonew (new playtesters only) or past (returning playtesters)
pastPlaytesterScopestringNoRequired when targetingType is past: any or specific
targetedPlaytesterIdstringNoRequired when pastPlaytesterScope is specific
Terminal window
curl -X POST https://app.weplaytestgames.com/api/v1/games/game-uuid/playtests \
-H "Authorization: Bearer wpg_sk_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"visibility": "private",
"quantity": 3,
"notesForTesters": "Focus on the tutorial and first boss fight"
}'

Paid playtests return with requiresPayment: true and pricing info:

{
"data": {
"playtest": {
"id": "pt-uuid",
"gameId": "game-uuid",
"visibility": "private",
"quantity": 3,
"durationMinutes": 60,
"playerCount": 1,
"costPerSlotCents": 2000,
"payoutCents": 1000,
"notesForTesters": "Focus on the tutorial and first boss fight",
"status": "pending_payment",
"isFreePublicTrial": false,
"createdAt": "2026-03-02T12:00:00.000Z"
},
"requiresPayment": true,
"costPerSlotCents": 2000,
"totalCents": 6000
},
"meta": {
"requestId": "req_abc123def456",
"timestamp": "2026-03-02T12:00:00.000Z"
}
}

Free trials are immediately active and require admin approval:

{
"data": {
"playtest": { "..." },
"slots": [{ "id": "slot-uuid", "status": "open", "..." }],
"needsAdminApproval": true
},
"meta": { "..." }
}

GET /api/v1/playtests/:id

Returns details for a specific playtest request with slot statistics.

{
"data": {
"playtest": {
"id": "pt-uuid",
"gameId": "game-uuid",
"gameName": "Dungeon Crawlers",
"visibility": "private",
"quantity": 3,
"durationMinutes": 60,
"playerCount": 1,
"costPerSlotCents": 2000,
"payoutCents": 1000,
"notesForTesters": "Focus on the tutorial",
"status": "active",
"isFreePublicTrial": false,
"targetingType": null,
"createdAt": "2026-02-01T10:00:00.000Z",
"slots": {
"open": 1,
"reserved": 1,
"submitted": 1,
"accepted": 0,
"rejected": 0,
"expired": 0
}
}
},
"meta": {
"requestId": "req_abc123def456",
"timestamp": "2026-03-02T12:00:00.000Z"
}
}

PATCH /api/v1/playtests/:id

Update an active playtest request.

FieldTypeDescription
notesForTestersstringUpdated instructions for playtesters (max 5000 chars). Only visible after claiming a slot.

Returns the updated playtest request object.


GET /api/v1/playtests/:id/slots

Returns all slots for a playtest request with their current status (cursor-paginated).

ParameterTypeDefaultDescription
limitinteger20Max 100
cursorstringPagination cursor
{
"data": {
"slots": [
{
"id": "slot-uuid",
"requestId": "pt-uuid",
"status": "submitted",
"deadlineAt": "2026-02-16T14:00:00.000Z",
"createdAt": "2026-02-15T14:00:00.000Z",
"playtester": {
"id": "playtester-uuid",
"displayName": "TestGamer42"
},
"submission": {
"id": "sub-uuid",
"videoFileSize": 524288000,
"notes": "Found a bug in level 3",
"createdAt": "2026-02-15T18:30:00.000Z"
},
"rating": null,
"rejection": null,
"transcription": {
"id": "trans-uuid",
"status": "approved",
"selectedVersion": "claude"
}
}
]
},
"meta": {
"requestId": "req_abc123def456",
"timestamp": "2026-03-02T12:00:00.000Z",
"hasMore": false
}
}

When present, rating and rejection have these structures:

  • rating: { "score": 5, "comment": "Great feedback!" }score is an integer, comment is a string or null
  • rejection: { "reason": "Video was too short", "createdAt": "2026-02-16T10:00:00.000Z" }

GET /api/v1/playtests/slots/:id

Returns full details for a specific slot including game info, submission, rating, rejection, and transcription.

{
"data": {
"slot": {
"id": "slot-uuid",
"requestId": "pt-uuid",
"status": "submitted",
"deadlineAt": "2026-02-16T14:00:00.000Z",
"createdAt": "2026-02-15T14:00:00.000Z",
"game": {
"id": "game-uuid",
"name": "Dungeon Crawlers"
},
"playtest": {
"id": "pt-uuid",
"visibility": "private",
"durationMinutes": 60,
"playerCount": 1
},
"playtester": {
"id": "playtester-uuid",
"displayName": "TestGamer42"
},
"submission": {
"id": "sub-uuid",
"videoFileSize": 524288000,
"notes": "Found a bug in level 3",
"createdAt": "2026-02-15T18:30:00.000Z"
},
"rating": null,
"rejection": null,
"transcription": null
}
},
"meta": {
"requestId": "req_abc123def456",
"timestamp": "2026-03-02T12:00:00.000Z"
}
}

The transcription object in slot detail responses includes an additional keyTakeaways field compared to the list view:

"transcription": {
"id": "trans-uuid",
"status": "approved",
"selectedVersion": "claude",
"keyTakeaways": ["Bug found in level 3", "Tutorial was confusing"]
}

POST /api/v1/playtests/slots/:id/accept

Accept a submitted playtest. The playtester earns their payout.

No request body required.

{
"data": {
"message": "Submission accepted"
},
"meta": {
"requestId": "req_abc123def456",
"timestamp": "2026-03-02T12:00:00.000Z"
}
}

POST /api/v1/playtests/slots/:id/reject

Reject a submitted playtest with an optional reason.

FieldTypeRequiredDescription
reasonstringNoExplanation for the rejection (max 5000 chars)
allowRetrybooleanNoAllow the playtester to resubmit (default: false)
{
"data": {
"message": "Submission rejected"
},
"meta": {
"requestId": "req_abc123def456",
"timestamp": "2026-03-02T12:00:00.000Z"
}
}

When allowRetry is true, a new slot is created in reserved status for the same playtester with a fresh 24-hour deadline. The response message will be "Submission rejected - playtester can try again".


GET /api/v1/playtests/slots/:id/download-url

Returns a pre-signed URL to download the playtest video. URL expires in 1 hour.

{
"data": {
"downloadUrl": "https://storage.example.com/videos/...",
"expiresAt": "2026-03-02T13:00:00.000Z"
},
"meta": {
"requestId": "req_abc123def456",
"timestamp": "2026-03-02T12:00:00.000Z"
}
}

GET /api/v1/playtests/slots/:id/transcript

Returns the AI-generated transcript for a submission. Returns a pre-signed download URL for the transcript file.

{
"data": {
"downloadUrl": "https://storage.example.com/transcripts/...",
"filename": "transcript-claude.txt",
"version": "claude",
"keyTakeaways": ["Bug found in level 3", "Tutorial was confusing"]
},
"meta": {
"requestId": "req_abc123def456",
"timestamp": "2026-03-02T12:00:00.000Z"
}
}

Returns 404 if the transcription is not yet available or still processing.


GET /api/v1/playtests/submissions

List submissions across all your games (cursor-paginated). Defaults to showing submitted status.

ParameterTypeDefaultDescription
limitinteger20Max 100
cursorstringPagination cursor
statusstringsubmittedFilter by slot status: open, reserved, submitted, accepted, rejected, blocked, expired, cancelled, publishing
{
"data": {
"submissions": [
{
"slot": {
"id": "slot-uuid",
"requestId": "pt-uuid",
"status": "submitted",
"reservedBy": "playtester-uuid",
"deadlineAt": "2026-02-16T14:00:00.000Z",
"createdAt": "2026-02-15T14:00:00.000Z"
},
"submission": {
"id": "sub-uuid",
"videoStorageKey": "submissions/...",
"videoFileSize": 524288000,
"notes": "Found a bug in level 3",
"createdAt": "2026-02-15T18:30:00.000Z"
},
"game": {
"id": "game-uuid",
"name": "Dungeon Crawlers"
},
"playtest": {
"id": "pt-uuid",
"visibility": "private",
"durationMinutes": 60,
"playerCount": 1,
"isFreePublicTrial": false
},
"playtester": {
"id": "playtester-uuid",
"displayName": "TestGamer42"
},
"awaitingAdminApproval": false,
"blockedReport": null,
"transcription": null
}
]
},
"meta": {
"requestId": "req_abc123def456",
"timestamp": "2026-03-02T12:00:00.000Z",
"hasMore": false
}
}

GET /api/v1/playtests/dashboard/stats

Get summary statistics for the game owner dashboard.

{
"data": {
"totalGames": 3,
"activePlaytests": 5,
"pendingReviews": 2
},
"meta": {
"requestId": "req_abc123def456",
"timestamp": "2026-03-02T12:00:00.000Z"
}
}