PRIV ProtocolPRIV Docs
API Reference

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

POST/api/v1/contributions/upload

Upload a new contribution (photo, video, voice, or text file) with consent settings.

Content Type

This endpoint accepts multipart/form-data.

Form Fields

FieldTypeRequiredDescription
fileFileYesThe file to upload
contribution_typestringYesOne of: photo, video, voice, text
consent_ai_trainingbooleanYesConsent for AI/ML training
consent_researchbooleanYesConsent for research use
consent_commercialbooleanYesConsent for commercial use
metadatastringNoJSON string with additional metadata

Allowed File Types

Contribution TypeAllowed MIME TypesMax Size
photoimage/jpeg, image/png, image/webp, image/heic, image/heif50MB
videovideo/mp4, video/webm, video/quicktime, video/x-msvideo500MB
voiceaudio/mpeg, audio/wav, audio/webm, audio/ogg, audio/mp4100MB
texttext/plain, application/json10MB

Response

{
  "success": true,
  "data": {
    "contribution_id": "contrib_abc123",
    "file_hash": "a1b2c3d4e5f6...",
    "storage_path": "ipfs/QmXyz...",
    "estimated_reward": 2.50,
    "status": "pending"
  }
}

Response Fields

FieldTypeDescription
contribution_idstringUnique identifier for the contribution
file_hashstringSHA-256 hash of the file content
storage_pathstringIPFS CID or storage path
estimated_rewardnumberEstimated PRIV reward (after approval)
statusstringInitial status is always pending

Reward Calculation

Base rewards vary by contribution type:

TypeBase RewardWith Full Consent
photo1.0 PRIVUp to 1.6 PRIV
video5.0 PRIVUp to 8.0 PRIV
voice2.0 PRIVUp to 3.2 PRIV
text0.5 PRIVUp 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

GET/api/v1/contributions

List the authenticated user's contributions with optional filtering.

Query Parameters

ParameterTypeRequiredDescription
statusstringNoFilter by status: pending, approved, rejected
typestringNoFilter by type: photo, video, voice, text
limitnumberNoResults per page (default: 20, max: 100)
offsetnumberNoPagination 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

StatusDescription
pendingAwaiting review and quality assessment
approvedApproved and eligible for rewards/marketplace
rejectedRejected 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

GET/api/v1/contributions/:id

Get detailed information about a specific contribution.

Path Parameters

ParameterTypeRequiredDescription
idstringYesContribution 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

DELETE/api/v1/contributions/:id

Delete a contribution. Supports both hard delete (for pending) and soft delete (GDPR request).

Path Parameters

ParameterTypeRequiredDescription
idstringYesContribution UUID

Request Body (Optional)

{
  "reason": "User requested deletion",
  "force_gdpr": false
}

Request Fields

FieldTypeRequiredDescription
reasonstringNoReason for deletion
force_gdprbooleanNoForce GDPR deletion request (soft delete with 30-day deadline)

Deletion Types

StatusDefault BehaviorDescription
pendingHard deleteImmediate permanent deletion
approved/rejectedSoft deleteMarked for deletion, removed in 30 days
Any (with force_gdpr)GDPR requestSoft 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');
}