On this pageQuickstartAuthenticationPOST /tasksGET /tasksGET /tasks/:idGET /tasks/:id/submissionsGET /submissions/:idPOST /webhook-endpointsStripe webhookErrorsRate limitsIdempotencyKey scopes
API Reference v1
Groundtruth APIFull reference for the Groundtruth REST API. Base URL: https://groundtruth.io/api/v1. All endpoints require an API key and return JSON with a consistent { data } or { error: { code, message } } envelope.QuickstartGet a task posted in under 60 seconds. You need an API key — generate one from your dashboard → API Keys.
bash
# 1. Create a task
curl -X POST https://groundtruth.io/api/v1/tasks \
-H "Authorization: Bearer gt_test_<your-key>" \
-H "Content-Type: application/json" \
-d '{
"title": "Record a busy street scene",
"instructions": "Walk any public street for 30 s. Steady hand, no filters.",
"modalities": ["video"],
"reward_cents": 250,
"target_submissions": 100
}'
# Response → 201 Created
# {
# "data": {
# "id": "...",
# "status": "funding",
# "funding_url": "https://checkout.stripe.com/..."
# }
# }
# 2. Open the funding_url in a browser to pay → task status moves to "open"
# 3. Poll for approved submissions
curl https://groundtruth.io/api/v1/tasks/<task-id>/submissions \
-H "Authorization: Bearer gt_test_<your-key>"AuthenticationAll /api/v1/* endpoints require a bearer token. Pass it in the Authorization header:Keys are prefixed gt_test_ (test mode) or gt_live_ (live mode). The plaintext key is shown exactly once at creation time. Groundtruth stores only a SHA-256 hash — if you lose the key, rotate it from Settings.
http
Authorization: Bearer gt_test_••••••••
401unauthorizedMissing or invalid key
403forbiddenKey exists but lacks the required scope
TasksCreate and manage data-collection tasks.
POST
/tasksjson
{
"title": "string (3–120 chars, required)",
"instructions": "string (10–5000 chars, required)",
"modalities": ["video"], // only "video" enabled in v1
"reward_cents": 250, // integer cents, $0.50–$500
"target_submissions": 100, // integer 1–10000
"example_media_key": "optional" // Supabase Storage object key
}json
{
"data": {
"id": "tsk_01hx4nvzgw5q9k3m",
"org_id": "org_...",
"title": "Record a busy street scene",
"status": "funding",
"budget_cents": 25000,
"funded_cents": 0,
"reward_cents": 250,
"target_submissions": 100,
"approved_count": 0,
"modalities": ["video"],
"device_requirement": "any",
"funding_url": "https://checkout.stripe.com/...",
"created_at": "2026-06-18T14:32:00Z",
"updated_at": "2026-06-18T14:32:00Z"
}
}http
POST /api/v1/tasks Authorization: Bearer gt_test_•••••••• Idempotency-Key: my-unique-key-abc123
GET
/tasksjson
{
"data": [
{
"id": "tsk_01hx4nvzgw5q9k3m",
"status": "open",
"title": "Record a busy street scene",
"budget_cents": 25000,
"approved_count": 12,
"created_at": "2026-06-18T14:32:00Z"
// ... full task row
}
]
}GET
/tasks/:idbash
curl https://groundtruth.io/api/v1/tasks/tsk_01hx4nvzgw5q9k3m \ -H "Authorization: Bearer gt_test_••••••••"
GET
/tasks/:id/submissionsjson
{
"data": [
{
"id": "sub_01hx7r4s2g0k1a5b",
"task_id": "tsk_01hx4nvzgw5q9k3m",
"contributor_id": "usr_...",
"media_object_key": "submissions/...",
"duration_seconds": 31,
"byte_size": 52428800,
"status": "approved",
"review_note": null,
"reviewed_at": "2026-06-18T16:10:00Z",
"created_at": "2026-06-18T16:05:12Z"
}
]
}Submissions
GET
/submissions/:idjson
{
"data": {
"id": "sub_01hx7r4s2g0k1a5b",
"status": "approved",
"media_url": "https://...supabase.co/storage/v1/...?token=...&expires_in=3600",
"media_url_expires_in_seconds": 3600,
"duration_seconds": 31,
"byte_size": 52428800,
"created_at": "2026-06-18T16:05:12Z"
}
}Webhooks
POST
/webhook-endpointsjson
{ "url": "https://your-server.com/webhooks/groundtruth" }json
{
"data": {
"id": "whe_...",
"url": "https://your-server.com/webhooks/groundtruth",
"enabled": true,
"signing_secret": "gt_whsec_••••••••",
"_note": "Store signing_secret securely — it will not be shown again.",
"created_at": "2026-06-18T14:32:00Z"
}
}typescript
import { createHmac } from 'crypto'
function verifyGroundtruthSignature(
rawBody: Buffer,
sig: string, // X-Groundtruth-Signature header
secret: string, // your stored signing_secret (gt_whsec_...)
): boolean {
const expected = createHmac('sha256', secret).update(rawBody).digest('hex')
return expected === sig
}submission.createdA contributor submitted a video for a task
submission.approvedA buyer approved a submission and a payout was issued
submission.rejectedA buyer rejected a submission
POST
/api/webhooks/stripetext
https://groundtruth.io/api/webhooks/stripe
ErrorsAll errors use a consistent envelope with a machine-readable code field.
json
{
"error": {
"code": "validation_error",
"message": "Human-readable description",
"details": { /* optional structured context */ }
}
}400validation_errorRequest body failed Zod validation
401unauthorizedMissing or invalid API key
403forbiddenKey lacks the required scope
404not_foundResource not found or belongs to another org
409conflictResource already exists or decision already made
429rate_limitedPer-key limit exceeded — back off and retry
500internalUnexpected server error
Rate limitsLimits are applied per API key. Every response includes these headers:When you hit a limit you receive a 429 rate_limited response. Wait until X-RateLimit-Reset before retrying.
X-RateLimit-LimitMax requests allowed in the window (120/min)
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp (seconds) when the window resets
Production note
The current rate limiter uses an in-process token bucket. For multi-replica deployments, swap it for a shared store (Upstash Redis, Vercel KV) so limits are enforced across all instances.IdempotencyAll POST endpoints support the Idempotency-Key header. Send the same key on a retry and you get back the original response without creating a duplicate resource.Keys are scoped per API key and cached for 24 hours. Use a UUID or a meaningful idempotency key that is unique per intended operation.
http
POST /api/v1/tasks Authorization: Bearer gt_test_•••••••• Content-Type: application/json Idempotency-Key: task-create-2026-06-18-campagna-01
Key scopesAPI keys carry a list of scopes that gate what operations they can perform. A missing scope returns 403 forbidden.
tasks:readList and fetch tasks, list submissions
tasks:writeCreate tasks
webhooks:writeRegister webhook endpoints