Floreal Logo
DocumentsUpload CVsUrl fetch

Upload CV from URL

Fetch and process a CV from a public URL

The simplest upload method for automation - just provide a URL to a CV file, and the system will download, upload, and process it automatically.

This endpoint requires a direct download URL that returns the actual file content, not an HTML sharing page.


How It Works

Automated Fetch & Process:

  1. Provide a public URL to CV file
  2. Server fetches file from URL
  3. File uploaded to temporary storage
  4. Document record created automatically
  5. CV analysis starts immediately
  6. Poll status endpoint for results

Perfect for integrations - no file handling needed on your end.

Common URL Mistakes

Sharing links don't work - convert to direct download URLs:

WRONG: https://drive.google.com/file/d/FILE_ID/view?usp=sharingCORRECT: https://drive.google.com/uc?export=download&id=FILE_ID

How to fix:

  1. Extract the FILE_ID from between /d/ and /view in your sharing URL
  2. Use this format: https://drive.google.com/uc?export=download&id=FILE_ID
  3. Make sure file is set to "Anyone with the link can view"

Example:

Sharing URL: https://drive.google.com/file/d/1abc123xyz/view?usp=sharing
Direct URL:  https://drive.google.com/uc?export=download&id=1abc123xyz

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

How to fix: Change ?dl=0 to ?dl=1 at the end of the URL

3. HTML Pages Instead of Files

WRONG: Download pages, login pages, landing pages ✅ CORRECT: Direct link to the file itself

How to test: Run curl -I "YOUR_URL" and check the Content-Type header should be application/pdf or similar, not text/html

Request Format

Body (JSON)

{
  "url": "https://drive.google.com/uc?export=download&id=1niWNOc_FeSjt03vTpwl3TwFvW2KzU0jc",
  "documentName": "John Doe - Software Engineer",
  "documentType": "cv",
  "documentDate": "11-2025",
  "fileName": "john-doe-resume.pdf"
}

Required Fields

FieldTypeRequiredDescriptionExample
urlstringYesPublic URL to CV filehttps://example.com/cv.pdf
documentNamestringYesDisplay name for CVJohn Doe - Software Engineer
documentTypestringNoDocument category (default: cv)cv
documentDatestringYesCV date in MM-YYYY format11-2025
fileNamestringNoCustom filename (optional)john-doe-cv.pdf

URL Requirements

Publicly Accessible - No authentication required ✅ Direct File Link - Must point directly to file, not HTML page ✅ HTTPS Recommended - HTTP also supported ✅ Response Time - Must respond within 30 seconds

Supported File Types

PDF - application/pdf (recommended) ✅ DOCX - application/vnd.openxmlformats-officedocument.wordprocessingml.documentDOC - application/mswordTXT - text/plain

Maximum file size: 10 MB


Security Features

SSRF Protection

The endpoint blocks requests to internal/private URLs:

Blocked:

  • localhost, 127.0.0.1, 0.0.0.0
  • AWS metadata endpoint: 169.254.169.254
  • Private IP ranges: 10.x.x.x, 192.168.x.x, 172.16 to 31.x.x
  • IPv6 localhost: ::1

Allowed:

  • Public internet URLs
  • CDN URLs
  • Cloud storage URLs (S3, GCS, Azure Blob)

Response

Success (201 Created)

{
  "documentId": "789e4567-e89b-12d3-a456-426614174000",
  "status": "uploading",
  "message": "Document fetched from URL and processing started",
  "sourceUrl": "https://example.com/resumes/john-doe.pdf",
  "documentUrl": "s3://bucket/companies/.../documents/.../resume.pdf",
  "file": {
    "name": "john_doe_resume.pdf",
    "size": 245760,
    "sizeFormatted": "240 KB",
    "contentType": "application/pdf"
  },
  "estimatedProcessingTime": "30 to 60 seconds",
  "statusEndpoint": "/v1/public/documents/789e4567-e89b-12d3-a456-426614174000",
  "nextSteps": [
    "Poll GET /v1/public/documents/789e4567-... to check processing status",
    "Document will be in 'completed' state when analysis is done"
  ]
}

Processing Pipeline

After fetching, your CV goes through this automated workflow:

Total time: 30 to 120 seconds for most CVs (up to 90s for complex documents or slow URLs)


Processing States

StateDurationDescriptionNext Action
uploading0 to 15sFetching and uploading fileWait 10s
pending15 to 50sAI analyzing CV contentPoll every 10s
completedFinalAnalysis finished, searchableRetrieve data
failedFinalProcessing error occurredCheck error details
invalidFinalNot recognized as CVUpload different file

Complete Examples

cURL

curl -X POST https://api.floreal.ai/v1/public/documents/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"
  }'

JavaScript (Node.js)

// Upload from URL
const response = await fetch(
  'https://api.floreal.ai/v1/public/documents/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 result = await response.json();
console.log('✅ Upload started! Document ID:', result.documentId);

// Poll for completion
const pollStatus = async (documentId) => {
  const status = await fetch(
    'https://api.floreal.ai/v1/public/documents/' + documentId,
    { headers: { 'Authorization': 'X-API-Key YOUR_API_KEY' } }
  ).then(r => r.json());

  console.log('Status:', status.status);

  if (status.status === 'completed') {
    console.log('✅ Processing complete!');
    console.log('Candidate:', status.contact);
    return status;
  }

  if (status.status === 'failed' || status.status === 'invalid') {
    console.error('❌ Processing failed:', status.error);
    return status;
  }

  // Still processing
  await new Promise(resolve => setTimeout(resolve, 5000));
  return pollStatus(documentId);
};

await pollStatus(result.documentId);

Python

# Upload from URL
response = requests.post(
    'https://api.floreal.ai/v1/public/documents/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'
    }
)

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

# Poll for completion
def poll_status(document_id):
    while True:
        status_response = requests.get(
            f'https://api.floreal.ai/v1/public/documents/{document_id}',
            headers={'Authorization': 'X-API-Key YOUR_API_KEY'}
        )
        status = status_response.json()

        print(f"Status: {status['status']}")

        if status['status'] == 'completed':
            print("✅ Processing complete!")
            print(f"Candidate: {status.get('contact', {})}")
            return status

        if status['status'] in ['failed', 'invalid']:
            print(f"❌ Processing failed: {status.get('error', {})}")
            return status

        time.sleep(5)

poll_status(document_id)

Error Responses

StatusErrorCauseSolution
400Invalid URLMalformed URLCheck URL format
400Cannot fetch from internal URLsPrivate/internal IP or localhostUse public URL
400Failed to fetch fileURL not accessible or timeoutEnsure URL is public and responds quickly
400Invalid file typeUnsupported content typeUse PDF, DOC, DOCX, or TXT
400File too largeFile exceeds 10MBCompress document or use smaller file
400Invalid date formatWrong date formatUse MM-YYYY (e.g., "11-2025")
401UnauthorizedInvalid API keyCheck Authorization header
500Upload failedServer errorRetry or contact support

Validation & Limits

URL Requirements

Format: Valid HTTP or HTTPS URL ✅ Access: Publicly accessible (no auth) ✅ Timeout: Must respond within 30 seconds ✅ Content: Direct file link, not HTML page

File Requirements

Size: Maximum 10 MB ✅ Types: PDF, DOC, DOCX, TXT only ✅ Content: Must be a valid CV/resume ✅ Auto-conversion: DOC/DOCX files converted to PDF automatically

Metadata Requirements

Document Name: 1 to 255 characters ✅ Document Date: MM-YYYY format (e.g., "03-2024") ✅ Document Type: "cv" or "other" (default: "cv") ✅ File Name: Optional, extracted from URL if not provided


Use Cases

Integration with Cloud Storage

// Upload from AWS S3 public URL
await uploadFromUrl({
  url: 'https://my-bucket.s3.amazonaws.com/resumes/candidate-123.pdf',
  documentName: 'Jane Smith - Data Scientist',
  documentDate: '11-2025'
});

Webhook Processing

// Process CV when webhook received
app.post('/webhook/new-application', async (req, res) => {
  const { candidateName, cvUrl, applicationDate } = req.body;

  const result = await uploadFromUrl({
    url: cvUrl,
    documentName: candidateName + ' - Application',
    documentDate: applicationDate
  });

  res.json({ documentId: result.documentId });
});

Batch Import

// Import multiple CVs from URL list
const cvUrls = [
  { url: 'https://...', name: 'John Doe', date: '11-2025' },
  { url: 'https://...', name: 'Jane Smith', date: '10-2025' },
  // ... more CVs
];

const results = await Promise.all(
  cvUrls.map(cv =>
    uploadFromUrl({
      url: cv.url,
      documentName: cv.name,
      documentDate: cv.date
    })
  )
);

console.log('Imported ' + results.length + ' CVs');

ATS Integration

// Sync CVs from external ATS
async function syncFromATS() {
  const applications = await ats.getNewApplications();

  for (const app of applications) {
    await uploadFromUrl({
      url: app.resumeUrl,
      documentName: app.firstName + ' ' + app.lastName + ' - ' + app.position,
      documentDate: app.submittedDate,
      fileName: app.applicationId + '.pdf'
    });
  }
}

Comparison: Upload Methods

FeatureDirect UploadPresigned URLURL Fetch
Requests1 request3 requests1 request
Complexity⭐ Simple⭐⭐⭐ Complex⭐ Simple
File SourceLocal fileLocal fileRemote URL
Client HandlingSend file bytesUpload to S3JSON only
Best ForQuick integrationLarge filesAutomation
Webhook Friendly❌ No❌ No✅ Yes

Recommendation: Use URL Fetch for:

  • Webhook integrations
  • Batch imports
  • ATS integrations
  • When files are already hosted online

Security

API Key Required - All requests authenticated ✅ SSRF Protection - Blocks internal/private URLs ✅ Organization Scoped - Files isolated per company ✅ Temporary Storage - Files in temp folder until validated ✅ Automatic Cleanup - Temp files deleted after processing ✅ 30s Timeout - Prevents hanging on slow URLs ✅ Content-Type Validation - Only allowed file types ✅ Size Limits - Maximum 10MB enforced ✅ HTTPS Only - API encrypted in transit ✅ Audit Trail - All uploads tracked with source URL


Architecture

Upload Flow

Client
  ↓ (sends URL + metadata)
API Server
  ↓ (fetches from URL with 30s timeout)
External Server
  ↓ (returns file)
API Server validates & uploads

S3: uploads/\{uploadId\}/file.pdf  ← Temporary
  ↓ (processes)
Step Functions Workflow:
  1. Copy to permanent location
  2. Convert (if DOC/DOCX)
  3. Extract text
  4. Analyze with AI
  5. Specialized CV parsing
  6. Chunk for search
  7. Generate embeddings
  8. Store in Pinecone
  ↓ (cleanup)
Delete temp file

✅ Ready for search

Monitoring & Debugging

Check Upload Status

curl -X GET https://api.floreal.ai/v1/public/documents/DOCUMENT_ID \
  -H "X-API-Key: YOUR_API_KEY"

List Recent Uploads

curl -X GET "https://api.floreal.ai/v1/public/documents?limit=10&sortOrder=desc" \
  -H "X-API-Key: YOUR_API_KEY"

Common Issues

Issue: "Failed to fetch file - timeout" Solution: URL took greater than 30s to respond - use faster hosting or direct upload

Issue: "Failed to fetch file - HTTP 403/404" Solution: URL not publicly accessible - check permissions and URL

Issue: "Cannot fetch from internal URLs" Solution: Provided private IP or localhost - use public URL

Issue: "Invalid file type" Solution: URL returns HTML page instead of file - ensure direct file link

Issue: "File too large" Solution: File at URL exceeds 10MB - compress or use smaller file


Troubleshooting

Testing URLs

Before uploading, test your URL:

# Check if URL is accessible
curl -I "https://example.com/resume.pdf"

# Should return:
# HTTP/1.1 200 OK
# Content-Type: application/pdf
# Content-Length: 245760

URL Validation Checklist

✅ URL returns 200 OK status ✅ Content-Type header matches file type ✅ Content-Length is under 10MB ✅ No authentication required ✅ Not behind CAPTCHA or rate limiting ✅ Responds within 30 seconds ✅ Direct file link (not download page)


Next Steps

  1. Provide URL using this endpoint
  2. Save documentId from response
  3. Poll status every 5 to 10 seconds
  4. Wait for completion (30 to 70 seconds typical)
  5. Retrieve data once status is completed
  6. Start searching using Search API

  • Direct Upload - POST /v1/public/documents/uploads/direct - Upload file directly
  • Get Document - GET /v1/public/documents/\{documentId\} - Check status and retrieve data
  • List Documents - GET /v1/public/documents - Browse all uploads
  • Delete Document - DELETE /v1/public/documents/\{documentId\} - Remove CV
  • Search CVs - POST /v1/public/search - Find candidates
POST
/v1/public/documents/upload-from-url
X-API-Key<token>

API key for public API access. Get yours at https://app.floreal.ai?tab=api

In: header

urlstring
Formaturi
documentNamestring
Length1 <= length <= 255
documentType?string
Default"cv"
Value in"cv" | "other"
documentDatestring
Match^\d{2}-\d{4}$
fileName?string

Response Body

curl -X POST "https://api.floreal.ai/v1/public/documents/upload-from-url" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "http://example.com",
    "documentName": "string",
    "documentDate": "string"
  }'
{
  "documentId": "789e4567-e89b-12d3-a456-426614174000",
  "status": "uploading",
  "message": "Document fetched from URL and processing started",
  "sourceUrl": "https://example.com/resumes/john-doe.pdf",
  "documentUrl": "string",
  "file": {
    "name": "john_doe_resume.pdf",
    "size": 245760,
    "sizeFormatted": "240 KB",
    "contentType": "application/pdf"
  },
  "estimatedProcessingTime": "30 to 60 seconds",
  "statusEndpoint": "/v1/public/documents/789e4567-e89b-12d3-a456-426614174000",
  "nextSteps": [
    "string"
  ]
}
{
  "error": "string",
  "message": "string"
}
{
  "error": "string"
}
{
  "error": "string",
  "message": "string"
}