Floreal Logo
Documents

Upload CVs

Upload candidate CVs to automatically extract structured information like work experience, skills, and contact details. Our AI-powered parsing eliminates manual data entry and builds your searchable talent database.

Quick Start

Choose your upload method and get started in minutes:

Perfect for automation - just provide a URL to your CV file:

# Upload from public URL (ONE request!)
curl -X POST "https://api.floreal.aiv1/public/documents/upload-from-url" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://drive.google.com/uc?export=download&id=1niWNOc_FeSjt03vTpwl3TwFvW2KzU0jc",
    "documentName": "John Doe - Software Engineer",
    "documentDate": "11-2025"
  }'

# Check status (poll every 5 seconds)
curl "https://api.floreal.aiv1/public/documents/DOCUMENT_ID" \
  -H "X-API-Key: YOUR_API_KEY"

Simple single-step - upload file bytes directly:

curl -X POST "https://api.floreal.aiv1/public/documents/upload-direct?documentName=John%20Doe&documentDate=11-2025" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/pdf" \
  --data-binary @resume.pdf

For large files - upload directly to S3 (3 steps):

# Step 1: Get presigned URL
curl -X POST "https://api.floreal.aiv1/public/documents/upload-presigned" \
  -H "X-API-Key: YOUR_API_KEY" \
  -d '{"fileName":"resume.pdf","contentType":"application/pdf","fileSize":245760}'

# Step 2: Upload to S3
curl -X PUT "PRESIGNED_URL" \
  -H "Content-Type: application/pdf" \
  --data-binary @resume.pdf

# Step 3: Finalize
curl -X POST "https://api.floreal.aiv1/public/documents/upload-presigned-finalize" \
  -H "X-API-Key: YOUR_API_KEY" \
  -d '{"uploadId":"...","documentName":"John Doe","documentDate":"11-2025"}'

Processing takes 30 to 90 seconds. Once status: "completed", you get the full parsed candidate data.


Why Upload CVs?

rocket

🚀 Speed up screening

Extract key qualifications instantly from hundreds of CVs

database

🎯 Build talent database

Automatically parse and store candidate information

magnifying-glass

🔍 Search candidates

Find qualified candidates based on parsed skills and experience

link

🔗 Integrate with ATS

Connect CV parsing to your recruitment workflow


What You Need

Before you start, make sure you have:

API Key - Get yours from your dashboard
CV Files - PDF, DOC, DOCX, or TXT (max 10 MB each)
HTTP Client - cURL, Postman, or any programming language


Choose Your Upload Method

We offer three ways to upload CVs - pick the one that fits your use case:

Best for: Webhooks, ATS integrations, batch imports, automation

Pros:

  • ✅ Simplest method (1 request)
  • ✅ No file handling on your end
  • ✅ Perfect for automation
  • ✅ Works with S3, Google Drive, Dropbox URLs

How it works: Provide a public URL to your CV file, and our server fetches, processes, and parses it automatically.

Perfect for integrations! If CVs are already hosted (S3, Drive, Dropbox), this is the easiest method.

Learn more →


📤 Method 2: Direct Upload

Best for: Quick integration, small to medium files, simple workflows

Pros:

  • ✅ Single request
  • ✅ Simple implementation
  • ✅ Works with local files
  • ✅ Good for small to medium files (up to 10MB)

How it works: Send CV file bytes directly in the request body with metadata in query parameters.

Learn more →


🚀 Method 3: Presigned URL (Advanced)

Best for: Large files, browser uploads, bandwidth optimization

Pros:

  • ✅ Fastest for large files
  • ✅ Direct-to-S3 upload
  • ✅ Better for concurrent uploads
  • ✅ Scales better

Cons:

  • ❌ More complex (3 steps)
  • ❌ More implementation code

How it works: Get a presigned S3 URL, upload directly to S3, then finalize the document record.

Learn more →


File Requirements

RequirementDetails
Max Size10 MB per file
FormatsPDF (recommended), DOCX, DOC, TXT
URL RequirementsMust be publicly accessible, direct file link (not HTML page)
LanguagesEnglish, French, German, Spanish, and more
LayoutSimple single-column works best

Timeline:

  • 0 to 15s: File fetched/uploaded and validated
  • 15 to 50s: AI analyzes CV content
  • 50 to 90s: Data extracted, embedded, and indexed
  • Result: Fully searchable candidate profile

Step-by-Step: Upload from URL (Easiest)

Step 1: Prepare Your URL

Make sure your URL is a direct file link, not a sharing page:

Common Mistakes:

❌ Google Drive sharing link: https://drive.google.com/file/d/FILE_ID/view?usp=sharing
Direct download link: https://drive.google.com/uc?export=download&id=FILE_ID

❌ Dropbox preview link: https://dropbox.com/s/abc/file.pdf?dl=0
Direct download link: https://dropbox.com/s/abc/file.pdf?dl=1

Test your URL:

# Should return Content-Type: application/pdf (not text/html)
curl -I "https://your-url.com/file.pdf"

Step 2: Upload from URL

Send your CV URL to the API:

curl -X POST "https://api.floreal.aiv1/public/documents/upload-from-url" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://drive.google.com/uc?export=download&id=1niWNOc_FeSjt03vTpwl3TwFvW2KzU0jc",
    "documentName": "John Doe - Software Engineer",
    "documentType": "cv",
    "documentDate": "11-2025"
  }'
const response = await fetch(
  'https://api.floreal.aiv1/public/documents/upload-from-url',
  {
    method: 'POST',
    headers: {
      'X-API-Key': 'YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      url: 'https://drive.google.com/uc?export=download&id=1niWNOc_FeSjt03vTpwl3TwFvW2KzU0jc',
      documentName: 'John Doe - Software Engineer',
      documentType: 'cv',
      documentDate: '11-2025'
    })
  }
);

const { documentId } = await response.json();
console.log('Upload started! Document ID:', documentId);
import requests

response = requests.post(
    'https://api.floreal.aiv1/public/documents/upload-from-url',
    headers={
        'X-API-Key': 'YOUR_API_KEY',
        'Content-Type': 'application/json'
    },
    json={
        'url': 'https://drive.google.com/uc?export=download&id=1niWNOc_FeSjt03vTpwl3TwFvW2KzU0jc',
        'documentName': 'John Doe - Software Engineer',
        'documentType': 'cv',
        'documentDate': '11-2025'
    }
)

document_id = response.json()['documentId']
print(f'Upload started! Document ID: {document_id}')

Response:

{
  "documentId": "789e4567-e89b-12d3-a456-426614174000",
  "status": "uploading",
  "message": "Document fetched from URL and processing started",
  "sourceUrl": "https://drive.google.com/uc?export=download&id=...",
  "file": {
    "name": "john_doe_resume.pdf",
    "size": 245760,
    "sizeFormatted": "240 KB",
    "contentType": "application/pdf"
  },
  "estimatedProcessingTime": "30 to 60 seconds",
  "statusEndpoint": "/v1/public/documents/789e4567-..."
}

Step 3: Poll for Completion

Check the processing status every 5 seconds:

async function waitForCompletion(documentId) {
  const maxAttempts = 18; // 90 seconds max

for (let i = 0; i < maxAttempts; i++) {
const response = await fetch(
`https://api.floreal.aiv1/public/documents/${documentId}`,
{ headers: { 'X-API-Key': 'YOUR_API_KEY' } }
);

    const data = await response.json();

    if (data.status === 'completed') {
      console.log('✅ Processing complete!');
      return data;
    }

    if (data.status === 'failed' || data.status === 'invalid') {
      throw new Error(`Processing failed: ${data.error?.message}`);
    }

    console.log(`⏳ Status: ${data.status} - waiting...`);
    await new Promise(resolve => setTimeout(resolve, 5000));

}

throw new Error('Timeout waiting for processing');
}

const result = await waitForCompletion(documentId);
console.log('Candidate:', result.contact);
console.log('Profile:', result.profile);
import time

def wait_for_completion(document_id):
    max_attempts = 18  # 90 seconds max

    for i in range(max_attempts):
        response = requests.get(
            f'https://api.floreal.aiv1/public/documents/{document_id}',
            headers={'X-API-Key': 'YOUR_API_KEY'}
        )

        data = response.json()

        if data['status'] == 'completed':
            print('✅ Processing complete!')
            return data

        if data['status'] in ['failed', 'invalid']:
            raise Exception(f"Processing failed: {data.get('error', {}).get('message')}")

        print(f"⏳ Status: {data['status']} - waiting...")
        time.sleep(5)

    raise Exception('Timeout waiting for processing')

result = wait_for_completion(document_id)
print('Candidate:', result['contact'])
print('Profile:', result['profile'])

Completed Response:

{
  "documentId": "789e4567-e89b-12d3-a456-426614174000",
  "status": "completed",
  "summary": "John Doe is a Senior Software Engineer with 8 years of experience...",
  "extractedText": "JOHN DOE\\nSoftware Engineer\\n\\nEXPERIENCE\\n...",
  "contact": {
    "firstName": "John",
    "lastName": "Doe",
    "email": "john.doe@example.com",
    "linkedin": "linkedin.com/in/johndoe",
    "location": {
      "city": "San Francisco",
      "country": "United States"
    }
  },
  "profile": {
    "domain": "Technology",
    "specialization": "Backend Development",
    "seniorityLevel": "Senior",
    "experienceYears": 8,
    "technicalStack": ["Python", "JavaScript", "PostgreSQL"],
    "industries": ["Fintech", "SaaS"],
    "hasManagement": true
  }
}

Professional Profile

{
  "profile": {
    "domain": "Technology",
    "specialization": "Backend Development",
    "seniorityLevel": "Senior",
    "experienceYears": 8,
    "technicalStack": ["Python", "JavaScript", "PostgreSQL", "AWS"],
    "industries": ["Fintech", "SaaS", "E-commerce"],
    "recentExpertise": ["API Design", "Microservices", "Cloud Architecture"],
    "hasManagement": true
  }
}

Structured Attributes

Over 100+ structured attributes for advanced filtering:

{
  "attributes": {
    "languages": {
      "english": 1,
      "english_fluent": 1,
      "french": 1,
      "languages_count": 2
    },
    "technical_skills": {
      "python_experience": 1,
      "javascript_experience": 1,
      "aws_experience": 1,
      "docker_experience": 1,
      "programming_languages_count": 4
    },
    "professional_experience": {
      "total_experience_years": 8,
      "number_of_employers": 3,
      "management_experience": 1,
      "remote_work_experience": 1
    },
    "education_indicators": {
      "masters_degree": 1,
      "computer_science_degree": 1,
      "university_name": "Stanford University"
    }
  }
}

AI-Generated Summary

{
  "summary": "John Doe is a Senior Software Engineer with 8 years of experience specializing in backend development and cloud architecture. He has deep expertise in Python, JavaScript, and AWS, with a proven track record of designing and implementing scalable microservices. John has led teams of 5+ engineers and has experience across fintech and SaaS industries. He holds a Master's degree in Computer Science from Stanford University."
}

Full CV Text

{
  "extractedText": "JOHN DOE\nSenior Software Engineer\n\nCONTACT\nEmail: john.doe@example.com\nLinkedIn: linkedin.com/in/johndoe\nLocation: San Francisco, CA\n\nEXPERIENCE\n\nSenior Software Engineer | TechCorp Inc. | 2020 - Present\n• Led development of microservices architecture serving 1M+ users\n• Designed and implemented RESTful APIs using Python and FastAPI\n• Managed team of 5 engineers\n..."
}

URL Requirements & Tips

Valid URL Formats

AWS S3 Public URLs

https://my-bucket.s3.amazonaws.com/resumes/cv.pdf
https://my-bucket.s3.us-east-1.amazonaws.com/public/cv.pdf

Google Drive Direct Download

https://drive.google.com/uc?export=download&id=FILE_ID

Dropbox Direct Download

https://www.dropbox.com/s/abc123/file.pdf?dl=1

Azure Blob Storage

https://mystorageaccount.blob.core.windows.net/public/cv.pdf

Your Own Server

https://api.yourcompany.com/files/cv.pdf
https://yourserver.com/static/uploads/resume.pdf

Common URL Mistakes

These URLs won't work:

❌ Google Drive sharing links with /view
❌ Dropbox links with ?dl=0
❌ URLs requiring login/authentication
❌ URLs that return HTML pages
❌ Private IP addresses (localhost, 10.x.x.x, 192.168.x.x)
❌ CDN URLs with hotlink protection (Webflow, some CloudFlare configs)

How to Convert Google Drive URLs

# From sharing link
https://drive.google.com/file/d/1niWNOc_FeSjt03vTpwl3TwFvW2KzU0jc/view?usp=sharing

# Extract FILE_ID (between /d/ and /view)
1niWNOc_FeSjt03vTpwl3TwFvW2KzU0jc

# Create direct download URL
https://drive.google.com/uc?export=download&id=1niWNOc_FeSjt03vTpwl3TwFvW2KzU0jc

Comparison Table

FeatureURL UploadDirect UploadPresigned URL
Requests113
Complexity⭐ Simple⭐ Simple⭐⭐⭐ Complex
File SourceRemote URLLocal fileLocal file
Best ForAutomation, webhooksQuick integrationLarge files, browsers
Webhook Friendly✅ Yes❌ No❌ No
Max File Size10 MB10 MB10 MB
NetworkServer-sideThrough APIDirect to S3

API Reference


Processing Issues

Processing stuck in "uploading"

Wait time: Up to 20 seconds
Solution: If longer than 30s, file may be corrupted

Processing stuck in "pending"

Wait time: Up to 60 seconds for complex CVs
Solution: If longer than 90s, contact support

Status returns "invalid"

Cause: AI didn't recognize document as a CV
Solution: Ensure file is actually a resume/CV, not job posting or other document

Extracted data is incomplete

Causes:

  • Complex multi-column layouts
  • Text embedded in images
  • Unusual formatting

Solutions:

  • Use simple single-column CV format
  • Convert to PDF from Word
  • Avoid heavy graphics

Need higher limits? Contact sales


Security & Privacy

  • SSRF Protection - Blocks internal/private URLs
  • Encrypted in transit - All uploads use HTTPS
  • Encrypted at rest - Files stored in secure S3
  • Scoped to your org - No cross-company access
  • GDPR compliant - Delete CVs anytime
  • Audit trail - Track who uploaded what
  • 30s timeout - Prevents hanging on slow URLs

Next Steps

Ready to start uploading CVs? Here's your roadmap:

  1. Get your API key from the dashboard
  2. Choose upload method:
    • Files on S3/Drive/Dropbox? → Use URL upload
    • Local files on server? → Use Direct upload
    • Large files from browser? → Use Presigned URL
  3. Upload
  4. Search candidates using the parsed data