Proofs API
Proofs are the evidence workers submit to demonstrate task completion. Each proof includes a file (photo, video, or signature) along with GPS coordinates that are automatically validated against the task location.
Base path: /v1/tasks/:id/proofs
List Proofs
GET /v1/tasks/:id/proofs
Retrieve all proofs submitted for a specific task.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | The task ID |
Example Request
curl https://api.workfunder.com/v1/tasks/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4/proofs \
-H "Authorization: Bearer wf_live_your_key"
Example Response
{
"data": [
{
"id": "p1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4",
"task_id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"worker_id": "w1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4",
"attempt_number": 1,
"type": "photo",
"file_url": "https://proofs.workfunder.com/signed/abc123?expires=1708794000",
"content_type": "image/jpeg",
"file_size_bytes": 2458624,
"gps_latitude": 40.6784,
"gps_longitude": -73.9440,
"geo_valid": true,
"distance_from_task_meters": 142,
"device_info": {
"user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)",
"platform": "iOS"
},
"status": "approved",
"rejection_reason": null,
"reviewed_at": "2026-02-24T14:30:00.000Z",
"created_at": "2026-02-24T13:45:00.000Z"
}
],
"meta": {
"total": 1,
"limit": 20,
"offset": 0
}
}
Proof Object Fields
| Field | Type | Description |
|---|---|---|
id | string | Proof ID |
task_id | string | Associated task ID |
worker_id | string | Worker who submitted the proof |
attempt_number | integer | Submission attempt (1-3) |
type | string | Proof type: photo, video, or signature |
file_url | string|null | Signed URL to access the proof file (expires in 1 hour) |
content_type | string | MIME type of the uploaded file |
file_size_bytes | integer | File size in bytes |
gps_latitude | number | GPS latitude at time of submission |
gps_longitude | number | GPS longitude at time of submission |
geo_valid | boolean | Whether GPS coordinates are within 500m of task location |
distance_from_task_meters | number | Distance from task location in meters |
device_info | object | Device metadata at time of submission |
status | string | Proof status: pending, approved, or rejected |
rejection_reason | string|null | Reason for rejection (if rejected) |
reviewed_at | string|null | When the proof was reviewed |
created_at | string | When the proof was submitted |
Signed URLs
Proof file URLs are signed and expire after 1 hour. To get a fresh URL, re-fetch the proofs for the task. This ensures proof files are not accessible without authorization.
Do not store or redistribute proof file URLs. They contain personal information (worker photos, location data) and are intended solely for task verification purposes. See the Terms of Service for usage restrictions.
Proof Types
WorkFunder supports three types of proof:
Photo
The most common proof type. Workers take a photo at the task location using their device camera.
- Format: JPEG or PNG
- Max size: 10 MB
- GPS required: Yes
- Use cases: Storefront photography, inspections, retail audits, delivery confirmation
Video
For tasks requiring video evidence of completion.
- Format: MP4
- Max size: 100 MB
- GPS required: Yes
- Use cases: Installation verification, event coverage, process documentation
Signature
A digital signature captured on the worker's screen.
- Format: PNG (rendered from canvas)
- Max size: 1 MB
- GPS required: No
- Use cases: Notary tasks, delivery confirmation, document acknowledgment
GPS Validation
Every photo and video proof includes GPS coordinates captured from the worker's device at the time of submission. WorkFunder automatically validates proximity:
- Validation radius: 500 meters from the task location
- Calculation: Haversine formula for great-circle distance
- Result fields:
geo_valid(boolean) anddistance_from_task_meters(exact distance)
How It Works
- Worker's device captures
navigator.geolocation.getCurrentPosition()coordinates - API calculates the distance between proof GPS and task location coordinates
- If distance is 500m or less,
geo_validis set totrue - If distance exceeds 500m,
geo_validis set tofalseand the proof is flagged for review
A geo_valid: false proof is not automatically rejected. GPS accuracy can vary in dense urban areas. Flagged proofs enter the admin review queue where they are evaluated with additional context.
Approve Proof
POST /v1/tasks/:id/proofs/:proofId/approve
Approve a submitted proof. This triggers the worker payout via Stripe and moves the task to completed status.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | The task ID |
proofId | string | The proof ID |
Example Request
curl -X POST https://api.workfunder.com/v1/tasks/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4/proofs/p1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4/approve \
-H "Authorization: Bearer wf_live_your_key"
Example Response
{
"id": "p1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4",
"status": "approved",
"reviewed_at": "2026-02-24T14:30:00.000Z",
"task": {
"id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"status": "completed",
"completed_at": "2026-02-24T14:30:00.000Z"
}
}
What Happens on Approval
- Proof status changes to
approved - Task status changes to
completed - Stripe Transfer is created to send
worker_payout_centsto the worker's Stripe Express account - Platform fee is captured from the original PaymentIntent
task.completedwebhook is delivered to your endpoint
Errors
| Code | Status | Description |
|---|---|---|
TASK_NOT_FOUND | 404 | Task not found |
PROOF_NOT_FOUND | 404 | Proof not found |
INVALID_TASK_STATE | 409 | Task is not in proof_submitted status |
Reject Proof
POST /v1/tasks/:id/proofs/:proofId/reject
Reject a submitted proof. The worker is notified and can resubmit if they have remaining attempts.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | Yes | Reason for rejection (shown to the worker) |
Example Request
curl -X POST https://api.workfunder.com/v1/tasks/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4/proofs/p1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4/reject \
-H "Authorization: Bearer wf_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"reason": "Photo is blurry and the storefront signage is not readable. Please retake with better focus."
}'
Example Response
{
"id": "p1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4",
"status": "rejected",
"rejection_reason": "Photo is blurry and the storefront signage is not readable. Please retake with better focus.",
"reviewed_at": "2026-02-24T14:30:00.000Z",
"task": {
"id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"status": "proof_rejected",
"attempts_remaining": 2
}
}
What Happens on Rejection
- Proof status changes to
rejectedwith the provided reason - Task status changes to
proof_rejected - Worker is notified via email and push notification with the rejection reason
- If the worker has remaining attempts (max 3), they can resubmit
- If all 3 attempts are exhausted, the task moves to
refundedand the developer receives a full refund task.proof_rejectedwebhook is delivered to your endpoint
Errors
| Code | Status | Description |
|---|---|---|
TASK_NOT_FOUND | 404 | Task not found |
PROOF_NOT_FOUND | 404 | Proof not found |
INVALID_TASK_STATE | 409 | Task is not in proof_submitted status |
Proof Submission Attempts
Workers have a maximum of 3 attempts to submit acceptable proof for each task. The attempt_number field on each proof tracks this.
| Attempt | Outcome | Result |
|---|---|---|
| 1 | Approved | Task completed, worker paid |
| 1 | Rejected | Worker can resubmit (2 remaining) |
| 2 | Approved | Task completed, worker paid |
| 2 | Rejected | Worker can resubmit (1 remaining) |
| 3 | Approved | Task completed, worker paid |
| 3 | Rejected | Task refunded, no more attempts |
After all 3 attempts are rejected, the task is automatically moved to refunded status and the developer receives a full refund of the escrowed budget.