Skip to main content

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

ParameterTypeDescription
idstringThe 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

FieldTypeDescription
idstringProof ID
task_idstringAssociated task ID
worker_idstringWorker who submitted the proof
attempt_numberintegerSubmission attempt (1-3)
typestringProof type: photo, video, or signature
file_urlstring|nullSigned URL to access the proof file (expires in 1 hour)
content_typestringMIME type of the uploaded file
file_size_bytesintegerFile size in bytes
gps_latitudenumberGPS latitude at time of submission
gps_longitudenumberGPS longitude at time of submission
geo_validbooleanWhether GPS coordinates are within 500m of task location
distance_from_task_metersnumberDistance from task location in meters
device_infoobjectDevice metadata at time of submission
statusstringProof status: pending, approved, or rejected
rejection_reasonstring|nullReason for rejection (if rejected)
reviewed_atstring|nullWhen the proof was reviewed
created_atstringWhen 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.

warning

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) and distance_from_task_meters (exact distance)

How It Works

  1. Worker's device captures navigator.geolocation.getCurrentPosition() coordinates
  2. API calculates the distance between proof GPS and task location coordinates
  3. If distance is 500m or less, geo_valid is set to true
  4. If distance exceeds 500m, geo_valid is set to false and the proof is flagged for review
info

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

ParameterTypeDescription
idstringThe task ID
proofIdstringThe 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

  1. Proof status changes to approved
  2. Task status changes to completed
  3. Stripe Transfer is created to send worker_payout_cents to the worker's Stripe Express account
  4. Platform fee is captured from the original PaymentIntent
  5. task.completed webhook is delivered to your endpoint

Errors

CodeStatusDescription
TASK_NOT_FOUND404Task not found
PROOF_NOT_FOUND404Proof not found
INVALID_TASK_STATE409Task 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

FieldTypeRequiredDescription
reasonstringYesReason 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

  1. Proof status changes to rejected with the provided reason
  2. Task status changes to proof_rejected
  3. Worker is notified via email and push notification with the rejection reason
  4. If the worker has remaining attempts (max 3), they can resubmit
  5. If all 3 attempts are exhausted, the task moves to refunded and the developer receives a full refund
  6. task.proof_rejected webhook is delivered to your endpoint

Errors

CodeStatusDescription
TASK_NOT_FOUND404Task not found
PROOF_NOT_FOUND404Proof not found
INVALID_TASK_STATE409Task 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.

AttemptOutcomeResult
1ApprovedTask completed, worker paid
1RejectedWorker can resubmit (2 remaining)
2ApprovedTask completed, worker paid
2RejectedWorker can resubmit (1 remaining)
3ApprovedTask completed, worker paid
3RejectedTask 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.