# FirstHandAPI > Human-in-the-loop crowdsourced data collection API. Post jobs via REST API or MCP, real workers capture photos, audio, video, and screen recordings. AI ensemble (Claude Vision + Whisper) scores quality 1-5 stars. Approved files auto-delivered to S3 with pre-signed download URLs. ## Quick Start - Base URL: https://api.firsthandapi.com - Signup: POST /v1/auth/signup { name, email } → returns api_key + $2.50 free credits (after phone verification) - Auth: Bearer token header — Authorization: Bearer fh_live_... - SDK: npm install @firsthandapi/sdk | pip install firsthandapi - MCP: npx @firsthandapi/mcp-server ## Response Format - Single resources: flat JSON with `object` field (e.g., { "object": "job", "id": "job_..." }) - Lists: { "object": "list", "data": [...], "has_more": bool, "next_cursor": string|null } - Errors: { "error": { "type": "...", "message": "...", "request_id": "req_..." } } ## Create a Job POST /v1/jobs Headers: Authorization: Bearer fh_live_..., Idempotency-Key: , Content-Type: application/json Body: { "type": "data_collection", // or "feedback" "title": "Street signs Tokyo", // optional, max 100 chars — short label for dashboards "description": "Photos of street signs in daylight", "files_needed": 10, "accepted_formats": ["image/jpeg"], // image/png, image/webp, audio/mpeg, audio/wav, audio/mp4, video/mp4, video/quicktime "price_per_file_cents": 50 // $0.50 per file, min $0.10 for images } Response (201): { "object": "job", "id": "job_01JP...", "status": "open", "files_needed": 10, "files_approved": 0, "total_cost_cents": 500 } ## Job Types - `data_collection` — Workers capture original photos, audio, video, screen recordings. Use for UGC, training data, ground truth. - `feedback` — Workers review provided content and give feedback. Use for quality assessment, content moderation. ## Custom Approval Threshold Add `min_star_rating` (1-5, default 3) to raise or lower the quality bar per job: POST /v1/jobs { ..., "min_star_rating": 4 } → only 4+ star submissions approved ## AI Scoring Rubric - 5 stars: Excellent — fully addresses job description, no improvements needed - 4 stars: Good — minor issues only, well-executed - 3 stars: Acceptable — meets basic requirements, some gaps - 2 stars: Below threshold — major deficiencies (retry allowed on first attempt) - 1 star: Rejected — off-topic or corrupted (strike issued) Dimensions: relevance, quality, completeness (each 1-5) ## Refresh File Download URL POST /v1/files/:id/refresh_url → { download_url, download_url_expires_at } Use when a 7-day URL has expired. Generates a fresh pre-signed S3 URL. ## Audit Events GET /v1/audit-events → { object: "list", data: [...] } Requires audit:read scope. Filters: job_id, event_type, limit, cursor. Event types: job.created, submission.approved, submission.rejected, api_key.created, etc. Both types support the same file formats and scoring pipeline. ## Accepted Formats (MIME types required, not extensions) image/jpeg, image/png, image/webp, image/gif audio/mpeg, audio/wav, audio/mp4 video/mp4, video/quicktime Note: Use MIME types like "image/jpeg", NOT extensions like "jpg". ## Check Job Status GET /v1/jobs/:id Long-poll: add header Prefer: wait=60 (max 60 seconds, holds connection until status changes) ## Get Approved Files (with Auto-Generated Annotations) GET /v1/jobs/:id/files Response: { "object": "list", "data": [{ "id": "file_...", "object": "file", "content_type": "image/jpeg", "size_bytes": 102400, "ai_star_rating": 4, "original_filename": "photo.jpg", "download_url": "https://s3.amazonaws.com/...", "download_url_expires_at": "2026-03-27T...", "annotations": { "type": "image", "objects": ["mailbox", "house", "lawn"], "scene": { "setting": "outdoor residential", "indoor": false }, "text_extraction": "1234", "color_palette": ["#8B4513", "#228B22", "#87CEEB"], "dominant_objects": [{ "label": "mailbox", "position": "center", "approximate_coverage": "25%" }], "composition": "centered subject, natural daylight" } }] } NOTE: download_url is a pre-signed S3 URL valid for 7 days. Re-fetch this endpoint for fresh URLs. ## Auto-Labeling & Annotations Every scored file automatically gets structured annotation metadata — no separate labeling pipeline needed. - Images: object labels, scene classification (indoor/outdoor), OCR text extraction, hex color palette, dominant objects with positions, composition analysis - Audio: speaker count, language detection (ISO 639-1), topic classification, keyword extraction, noise level, transcript segments with timestamps - Video: scene segmentation with timestamps, action recognition labels, object tracking across scenes, keyframe descriptions, transcript with timestamps Annotations are null for policy violations and stock photo auto-rejects. See https://docs.firsthandapi.com/guides/auto-labeling ## List Submissions (with AI scores) GET /v1/jobs/:id/submissions Response data items: { id, status, ai_star_rating (1-5), ai_reasoning, scored_at } Status values: pending_score, approved, rejected, retry_pending ## Cancel Job POST /v1/jobs/:id/cancel → refunds remaining escrowed credits ## Rate Limits - 100 requests/minute per API key - 500 requests/second per organization - Response headers: x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-reset, retry-after - On 429: wait for retry-after seconds ## Credits & Billing - GET /v1/billing/credits → { balance_cents, currency } - POST /v1/billing/credits/purchase { amount_cents: 1000|2500|5000|10000|25000 } → { checkout_url } (opens Stripe) - 402 error on job creation means insufficient credits. Response includes: current_balance_cents, required_cents, shortfall_cents, purchase_url - Enable auto-top-up: PATCH /v1/organization/settings { "auto_top_up_enabled": true, "auto_top_up_threshold_cents": 500, "auto_top_up_amount_cents": 2500 } - Header X-FirstHandAPI-Credits-Remaining sent on every authenticated response - Purchase amounts are fixed tiers: $10, $25, $50, $100, $250 (no arbitrary amounts) - Free-tier and bonus credits have `expires_at` — they expire 12 months after issuance - Purchase credits are permanent (no expiry) - Check `GET /v1/billing/transactions` for expiry dates on each credit transaction ## Sandbox / Test Mode - Use fh_test_* API keys (same base URL: https://api.firsthandapi.com) - AI scoring returns 4 stars (dev placeholder) — no real AI charges - Stripe uses test mode — no real charges - Same endpoints, same response format ## Webhooks - POST /v1/webhook_endpoints { url, description } - Events: submission.approved, submission.rejected, job.completed, job.cancelled, credits.low_balance - Payload: { "id": "evt_...", "type": "submission.approved", "data": { "job_id": "...", "submission_id": "...", "ai_star_rating": 4 } } - evt_* ID is the idempotency key — use it to deduplicate retries - Signature: X-FirstHandAPI-Signature header, HMAC-SHA256 of raw body using your webhook secret - Verify: hmac.new(secret, body, hashlib.sha256).hexdigest() ## Submission States - `pending_score` — File uploaded, AI is scoring (5-15 seconds) - `approved` — Scored 3+ stars, file delivered to buyer's folder - `retry_pending` — Scored 2 stars on first attempt, worker can retry once with a better file - `rejected` — Scored 1 star, or failed retry. No payment. ## Identity GET /v1/me — Returns your organization ID, environment, API key scopes. Use to verify authentication. ## Long-Polling (recommended for job completion) GET /v1/jobs/:id with header `Prefer: wait=60` — holds connection up to 60 seconds, returns immediately when job status changes. Eliminates polling loops. ## Job Filtering GET /v1/jobs?status=open&created_after=2026-01-01T00:00:00Z — filter by status and creation date. ## Webhook Event Replay POST /v1/webhook_events/:id/replay — replay a specific webhook event for debugging. ## API Key Rotation POST /v1/api_keys/:id/rotate — creates a new key with 24-hour overlap. Old key works for 24h. ## Download URL Expiry File download URLs expire after 7 days. Each file includes `download_url_expires_at` timestamp. Re-call GET /v1/jobs/:id/files for fresh URLs. ## Submissions vs Files Endpoints - GET /v1/jobs/:id/submissions — ALL submissions (all statuses), includes file data + AI scores + reasoning - GET /v1/jobs/:id/files — ONLY approved files, flat objects with download URLs Use /files for downloading content. Use /submissions for monitoring the scoring pipeline. ## Buyer Rating POST /v1/jobs/:id/rate { "rating": 4, "feedback": "Great quality" } — rate completed jobs 1-5 stars. ## API Keys - POST /v1/api_keys { name, scopes: ["jobs:read","jobs:write"], environment: "live" } → { secret: "fh_live_..." } - Scopes: jobs:read, jobs:write, webhooks:read, webhooks:write, keys:manage, audit:read, usage:read, billing:write ## Use Cases - UGC: Authentic photos, videos, audio from real people (product shots, testimonials, unboxings) - Ground Truth: Verified reference data for AI model evaluation (labeled images, audio benchmarks) - LLM Training Data: Diverse media at scale for multimodal fine-tuning (cross-demographic photos, multilingual audio) ## Full Documentation - Docs: https://docs.firsthandapi.com - Full reference: https://docs.firsthandapi.com/llms-full.txt - OpenAPI spec: https://api.firsthandapi.com/openapi.json - TypeScript SDK: https://www.npmjs.com/package/@firsthandapi/sdk - Python SDK: https://pypi.org/project/firsthandapi/ - MCP Server: https://www.npmjs.com/package/@firsthandapi/mcp-server