Contributions API
Upload and manage data contributions (photos, videos, voice, text) for AI training with the PRIV Protocol Contributions API.
Upload and manage data contributions for AI training. Contributions are stored on IPFS and can be monetized through the marketplace.
All contribution endpoints require user authentication. Users can only access their own contributions.
Upload Contribution
/api/v1/contributions/uploadUpload a new contribution (photo, video, voice, or text file) with consent settings.
Content Type
This endpoint accepts multipart/form-data.
Form Fields
| Field | Type | Required | Description |
|---|---|---|---|
file | File | Yes | The file to upload |
contribution_type | string | Yes | One of: photo, video, voice, text |
consent_ai_training | boolean | Yes | Consent for AI/ML training |
consent_research | boolean | Yes | Consent for research use |
consent_commercial | boolean | Yes | Consent for commercial use |
metadata | string | No | JSON string with additional metadata |
Allowed File Types
| Contribution Type | Allowed MIME Types | Max Size |
|---|---|---|
photo | image/jpeg, image/png, image/webp, image/heic, image/heif | 50MB |
video | video/mp4, video/webm, video/quicktime, video/x-msvideo | 500MB |
voice | audio/mpeg, audio/wav, audio/webm, audio/ogg, audio/mp4 | 100MB |
text | text/plain, application/json | 10MB |
Response
{
"success": true,
"data": {
"contribution_id": "contrib_abc123",
"file_hash": "a1b2c3d4e5f6...",
"storage_path": "ipfs/QmXyz...",
"estimated_reward": 2.50,
"status": "pending"
}
}Response Fields
| Field | Type | Description |
|---|---|---|
contribution_id | string | Unique identifier for the contribution |
file_hash | string | SHA-256 hash of the file content |
storage_path | string | IPFS CID or storage path |
estimated_reward | number | Estimated PRIV reward (after approval) |
status | string | Initial status is always pending |
Reward Calculation
Base rewards vary by contribution type:
| Type | Base Reward | With Full Consent |
|---|---|---|
photo | 1.0 PRIV | Up to 1.6 PRIV |
video | 5.0 PRIV | Up to 8.0 PRIV |
voice | 2.0 PRIV | Up to 3.2 PRIV |
text | 0.5 PRIV | Up to 0.8 PRIV |
Consent multipliers:
- AI Training: +20%
- Research: +10%
- Commercial: +30%
Example: cURL
curl -X POST "https://api.priv.io/v1/contributions/upload" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-F "file=@photo.jpg" \
-F "contribution_type=photo" \
-F "consent_ai_training=true" \
-F "consent_research=true" \
-F "consent_commercial=true" \
-F 'metadata={"location":"outdoor","subject":"nature"}'Example: JavaScript
async function uploadContribution(file: File, type: string) {
const formData = new FormData();
formData.append('file', file);
formData.append('contribution_type', type);
formData.append('consent_ai_training', 'true');
formData.append('consent_research', 'true');
formData.append('consent_commercial', 'true');
formData.append('metadata', JSON.stringify({
originalName: file.name,
uploadedAt: new Date().toISOString(),
}));
const response = await fetch('https://api.priv.io/v1/contributions/upload', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
},
body: formData,
});
return response.json();
}Duplicate files (same content hash) from the same user will be rejected.
List Contributions
/api/v1/contributionsList the authenticated user's contributions with optional filtering.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
status | string | No | Filter by status: pending, approved, rejected |
type | string | No | Filter by type: photo, video, voice, text |
limit | number | No | Results per page (default: 20, max: 100) |
offset | number | No | Pagination offset (default: 0) |
Response
{
"success": true,
"data": {
"contributions": [
{
"id": "contrib_abc123",
"user_id": "usr_xyz789",
"contribution_type": "photo",
"file_hash": "a1b2c3d4...",
"storage_path": "ipfs/QmXyz...",
"file_size_bytes": 2048576,
"mime_type": "image/jpeg",
"original_filename": "beach_photo.jpg",
"metadata": {"subject": "nature"},
"consent_ai_training": true,
"consent_research": true,
"consent_commercial": true,
"quality_score": 0.85,
"priv_earned": 2.50,
"status": "approved",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T12:00:00Z"
}
],
"total": 25,
"limit": 20,
"offset": 0
}
}Contribution Status
| Status | Description |
|---|---|
pending | Awaiting review and quality assessment |
approved | Approved and eligible for rewards/marketplace |
rejected | Rejected due to quality or policy issues |
Example
curl "https://api.priv.io/v1/contributions?status=approved&type=photo&limit=10" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Get Contribution Details
/api/v1/contributions/:idGet detailed information about a specific contribution.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Contribution UUID |
Response
{
"success": true,
"data": {
"contribution": {
"id": "contrib_abc123",
"user_id": "usr_xyz789",
"contribution_type": "photo",
"file_hash": "a1b2c3d4e5f6...",
"storage_bucket": "contributions",
"storage_path": "ipfs/QmXyz...",
"file_size_bytes": 2048576,
"mime_type": "image/jpeg",
"original_filename": "beach_photo.jpg",
"metadata": {"subject": "nature", "location": "outdoor"},
"consent_ai_training": true,
"consent_research": true,
"consent_commercial": true,
"consent_timestamp": "2024-01-15T10:30:00Z",
"consent_version": "1.0",
"quality_score": 0.85,
"quality_factors": {
"resolution": 0.9,
"clarity": 0.8,
"uniqueness": 0.85
},
"priv_earned": 2.50,
"reward_multiplier": 1.6,
"status": "approved",
"reviewed_at": "2024-01-15T12:00:00Z",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T12:00:00Z"
},
"can_delete": true,
"can_update_consent": true
}
}Example
curl "https://api.priv.io/v1/contributions/contrib_abc123" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Delete Contribution
/api/v1/contributions/:idDelete a contribution. Supports both hard delete (for pending) and soft delete (GDPR request).
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Contribution UUID |
Request Body (Optional)
{
"reason": "User requested deletion",
"force_gdpr": false
}Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | No | Reason for deletion |
force_gdpr | boolean | No | Force GDPR deletion request (soft delete with 30-day deadline) |
Deletion Types
| Status | Default Behavior | Description |
|---|---|---|
pending | Hard delete | Immediate permanent deletion |
approved/rejected | Soft delete | Marked for deletion, removed in 30 days |
Any (with force_gdpr) | GDPR request | Soft delete with GDPR compliance tracking |
Response
{
"success": true,
"data": {
"contribution_id": "contrib_abc123",
"deleted": true,
"deletion_type": "hard",
"message": "Contribution permanently deleted"
}
}For soft deletions:
{
"success": true,
"data": {
"contribution_id": "contrib_abc123",
"deleted": true,
"deletion_type": "gdpr_request",
"message": "Contribution marked for deletion. Data will be permanently removed within 30 days per GDPR requirements.",
"gdpr_deadline": "2024-02-15T10:30:00Z"
}
}Example: Simple Delete
curl -X DELETE "https://api.priv.io/v1/contributions/contrib_abc123" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Example: GDPR Deletion Request
curl -X DELETE "https://api.priv.io/v1/contributions/contrib_abc123" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"reason": "Exercising right to erasure",
"force_gdpr": true
}'GDPR deletion requests create a deletion record for compliance tracking. Data is permanently removed within 30 days.
JavaScript Examples
Batch Upload
async function uploadMultipleContributions(files: File[]) {
const results = await Promise.all(
files.map(async (file) => {
const type = getContributionType(file.type);
if (!type) {
return { file: file.name, error: 'Unsupported file type' };
}
try {
const result = await uploadContribution(file, type);
return {
file: file.name,
success: true,
contributionId: result.data.contribution_id,
estimatedReward: result.data.estimated_reward,
};
} catch (error) {
return { file: file.name, error: error.message };
}
})
);
const successful = results.filter(r => r.success);
const failed = results.filter(r => r.error);
console.log(`Uploaded ${successful.length}/${files.length} files`);
return { successful, failed };
}
function getContributionType(mimeType: string): string | null {
if (mimeType.startsWith('image/')) return 'photo';
if (mimeType.startsWith('video/')) return 'video';
if (mimeType.startsWith('audio/')) return 'voice';
if (mimeType.startsWith('text/')) return 'text';
return null;
}Track Contribution Status
async function trackContributionStatus(contributionId: string) {
const response = await fetch(
`https://api.priv.io/v1/contributions/${contributionId}`,
{
headers: { 'Authorization': `Bearer ${token}` },
}
);
const data = await response.json();
const contribution = data.data.contribution;
return {
id: contribution.id,
status: contribution.status,
qualityScore: contribution.quality_score,
privEarned: contribution.priv_earned,
canDelete: data.data.can_delete,
};
}
// Poll for status updates
async function waitForApproval(contributionId: string, maxWait = 300000) {
const startTime = Date.now();
while (Date.now() - startTime < maxWait) {
const status = await trackContributionStatus(contributionId);
if (status.status === 'approved') {
return { approved: true, ...status };
}
if (status.status === 'rejected') {
return { approved: false, ...status };
}
// Wait 10 seconds before checking again
await new Promise(resolve => setTimeout(resolve, 10000));
}
throw new Error('Timeout waiting for contribution approval');
}