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:
http
Authorization: Bearer gt_test_••••••••
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.
401unauthorizedMissing or invalid key
403forbiddenKey exists but lacks the required scope
TasksCreate and manage data-collection tasks.
POST
/tasks
Create a new task. The task is created in funding status. If Stripe is configured, funding_url is a Stripe Checkout URL the buyer visits to fund the task; on payment the task transitions to open.Required scopetasks:writeRequest body
json
{
  "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
}
Response — 201 Created
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"
  }
}
IdempotencyInclude an Idempotency-Key header on POSTs to safely retry without creating duplicate tasks. The same response is returned for up to 24 hours.
http
POST /api/v1/tasks
Authorization: Bearer gt_test_••••••••
Idempotency-Key: my-unique-key-abc123
GET
/tasks
List all tasks for the authenticated organization, newest first.Required scopetasks:readResponse — 200 OK
json
{
  "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/:id
Fetch a single task by ID. Returns 404 if the task does not exist or belongs to a different organization.Required scopetasks:read
bash
curl https://groundtruth.io/api/v1/tasks/tsk_01hx4nvzgw5q9k3m \
  -H "Authorization: Bearer gt_test_••••••••"
GET
/tasks/:id/submissions
List all submissions for a task. Newest first. Includes all statuses (pending, approved, rejected). Use GET /submissions/:id to get a signed media download URL for a specific submission.Required scopetasks:readResponse — 200 OK
json
{
  "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/:id
Fetch a single submission and a short-lived signed download URL (1 hour) for the raw media file. The URL is generated on every call — do not cache it beyond its expiry.Required scopetasks:readResponse — 200 OK
json
{
  "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-endpoints
Register an HTTPS URL to receive event deliveries. Groundtruth signs every payload with HMAC-SHA256 and includes the hex digest in the X-Groundtruth-Signature header. Verify it on your server before processing.Required scopewebhooks:writeRequest body
json
{ "url": "https://your-server.com/webhooks/groundtruth" }
Response — 201 Created
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"
  }
}
Verifying signatures
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
}
Event types
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/stripe
Internal Stripe webhook receiver. This endpoint is called by Stripe — you do not call it directly. Point your Stripe webhook configuration at:
text
https://groundtruth.io/api/webhooks/stripe
Stripe events processed: checkout.session.completed (funds the task), account.updated (updates contributor payout eligibility), transfer.created (logged). All other events are acknowledged and ignored.
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:
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
When you hit a limit you receive a 429 rate_limited response. Wait until X-RateLimit-Reset before retrying.
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.
http
POST /api/v1/tasks
Authorization: Bearer gt_test_••••••••
Content-Type: application/json
Idempotency-Key: task-create-2026-06-18-campagna-01
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.
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