Floreal Logo
Documents

Manage Documents

Manage Documents

Monitor CV processing, retrieve parsed data, and manage your document library. Track upload status, access extracted candidate information, and maintain your talent database.

Quick Start

Check processing status and retrieve parsed data:

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

Browse all uploaded CVs:

curl "https://api.floreal.aiv1/public/documents?limit=20" \
  -H "X-API-Key: YOUR_API_KEY"

Permanently remove a CV:

curl -X DELETE "https://api.floreal.aiv1/public/documents/DOCUMENT_ID" \
  -H "X-API-Key: YOUR_API_KEY"

What You Can Do

chart-line

📊 Monitor Processing

Track CV processing status in real-time

file-text

📋 Retrieve Data

Access complete parsed candidate information

books

📚 Browse Library

List and filter your CV database

trash

🗑️ Clean Up

Delete outdated or failed documents


Processing States:

  • uploading (0-15s) - File being copied to storage
  • pending (15-90s) - AI analyzing CV content
  • completed ✅ - Ready to use
  • failed ❌ - Processing error
  • invalid ⚠️ - Not recognized as a CV

1. Get Document Status

Poll this endpoint after uploading to check if processing is complete.

When to Use

After uploading - Monitor processing progress
Retrieve data - Access parsed CV information
Check errors - Diagnose failed uploads
Automation - Integrate status checks into workflows

Quick Example

// Poll until complete
async function getDocument(documentId) {
  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('✅ Ready!');
    console.log('Contact:', data.contact);
    console.log('Profile:', data.profile);
    return data;
  }
  
  if (data.status === 'failed') {
    console.error('❌ Failed:', data.error?.message);
    return data;
  }
  
  // Still processing
  console.log(`⏳ Status: ${data.status}`);
  await new Promise(r => setTimeout(r, 5000));
  return getDocument(documentId); // Poll again
}

const result = await getDocument('789e4567-...');
import time
import requests

def get_document(document_id):
    while True:
        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('✅ Ready!')
            print('Contact:', data['contact'])
            print('Profile:', data['profile'])
            return data

        if data['status'] == 'failed':
            print('❌ Failed:', data.get('error', {}).get('message'))
            return data

        # Still processing
        print(f"⏳ Status: {data['status']}")
        time.sleep(5)

result = get_document('789e4567-...')
curl "https://api.floreal.aiv1/public/documents/789e4567-..." \
  -H "X-API-Key: YOUR_API_KEY"

Response (when completed)

{
  "documentId": "789e4567-e89b-12d3-a456-426614174000",
  "status": "completed",
  "summary": "Benjamin Gabay - Sales-marketing Professional with 4 years of experience...",
  "extractedText": "Contact +33766771226\nLinkedIn: www.linkedin.com/in/benjamin-gabay...",
  "contact": {
    "firstName": "Benjamin",
    "lastName": "Gabay",
    "email": "benjamin.gabay@example.com",
    "linkedin": "www.linkedin.com/in/benjamin-gabay",
    "location": { "city": "Paris", "country": "FRA" }
  },
  "profile": {
    "domain": "Sales Marketing",
    "specialization": "Content Marketing",
    "seniorityLevel": "Junior",
    "experienceYears": 4,
    "technicalStack": ["Copywriting", "Content Creation"],
    "industries": ["Real-estate", "Social Media"],
    "hasManagement": false
  },
  "attributes": {
    "languages": { "french": 1, "languages_count": 1 },
    "technical_skills": { "copywriting": 1, "content_creation": 1 },
    "professional_experience": { "total_experience_years": 4 }
  },
  "processingTime": { "seconds": 62, "formatted": "62s" }
}

What you get:

  • summary - AI-generated professional overview
  • extractedText - Full CV text (5-15KB)
  • contact - Name, email, LinkedIn, location
  • profile - Domain, experience, skills, industries
  • attributes - 100+ structured fields for filtering

View full documentation →


2. List Documents

Browse your organization's CV library with filtering and pagination.

When to Use

Dashboard - Show recent uploads
Monitoring - Track processing status
Batch operations - Find documents to process
Reports - Analyze upload patterns

Quick Example

// List recent completed CVs
const response = await fetch(
  'https://api.floreal.aiv1/public/documents?status=completed&limit=20',
  { headers: { 'X-API-Key': 'YOUR_API_KEY' } }
);

const { data, pagination, summary } = await response.json();

console.log(`Found ${data.length} completed CVs`);
console.log(`Total in database: ${summary.total}`);

data.forEach(doc => {
console.log(`${doc.documentName} - ${doc.candidate?.fullName}`);
});
# List recent completed CVs
response = requests.get(
    'https://api.floreal.aiv1/public/documents',
    params={'status': 'completed', 'limit': 20},
    headers={'X-API-Key': 'YOUR_API_KEY'}
)

data = response.json()

print(f"Found {len(data['data'])} completed CVs")
print(f"Total in database: {data['summary']['total']}")

for doc in data['data']:
    candidate = doc.get('candidate', {})
    print(f"{doc['documentName']} - {candidate.get('fullName')}")
# List recent uploads
curl "https://api.floreal.aiv1/public/documents?limit=20&sortOrder=desc" \
  -H "X-API-Key: YOUR_API_KEY"

# Filter by status

curl "https://api.floreal.aiv1/public/documents?status=completed&limit=100" \
 -H "X-API-Key: YOUR_API_KEY"

Response

{
  "data": [
    {
      "documentId": "789e4567-...",
      "documentName": "Benjamin Gabay - CV",
      "documentType": "cv",
      "status": "completed",
      "documentDate": "11-2025",
      "candidate": {
        "firstName": "Benjamin",
        "lastName": "Gabay",
        "fullName": "Benjamin Gabay",
        "domain": "Sales Marketing"
      },
      "timestamps": {
        "createdAt": "2025-11-05T14:30:00.000Z",
        "updatedAt": "2025-11-05T14:30:45.000Z"
      }
    }
  ],
  "pagination": {
    "limit": 50,
    "offset": 0,
    "total": 127,
    "hasMore": true,
    "currentPage": 1,
    "totalPages": 3
  },
  "summary": {
    "uploading": 2,
    "pending": 5,
    "completed": 115,
    "failed": 3,
    "invalid": 2,
    "total": 127
  }
}

Filtering Options

ParameterTypeDescriptionExample
limitintegerResults per page (1-100)50
offsetintegerSkip results0
statusstringFilter by statecompleted
documentTypestringFilter by typecv
sortBystringSort fieldcreatedAt
sortOrderstringSort directiondesc
createdAfterdatetimeAfter date2025-11-01T00:00:00Z
createdBeforedatetimeBefore date2025-11-30T23:59:59Z

Common Use Cases

Find failed uploads

const failed = await fetch("/v1/public/documents?status=failed", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
}).then((r) => r.json());

console.log(`${failed.data.length} failed uploads`);

Paginate through all documents

let allDocs = [];
let offset = 0;

while (true) {
  const { data, pagination } = await fetch(
    `/v1/public/documents?limit=100&offset=${offset}`,
    { headers: { "X-API-Key": "YOUR_API_KEY" } }
  ).then((r) => r.json());

  allDocs = allDocs.concat(data);
  if (!pagination.hasMore) break;
  offset += 100;
}

Get documents from last week

const lastWeek = new Date();
lastWeek.setDate(lastWeek.getDate() - 7);

const recent = await fetch(
  `/v1/public/documents?createdAfter=${lastWeek.toISOString()}`,
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
).then((r) => r.json());

Note: This endpoint returns metadata only. For complete candidate data (contact, profile, attributes), use GET /v1/public/documents/{documentId}.

View full documentation →


3. Delete Document

Permanently remove a CV and all associated data.

When to Use

Cleanup - Remove failed or invalid uploads
GDPR compliance - Delete candidate data on request
Storage management - Remove outdated CVs
Error recovery - Clear corrupted documents

⚠️ Important

This action is irreversible!

Deletion removes data from 3 locations:

  1. Database - Document record and extracted data
  2. Cloud Storage (S3) - Original PDF/DOC/DOCX file
  3. Vector Database - Search embeddings (Pinecone)

Make sure you have backups if needed.

Quick Example

// Delete a document
const response = await fetch(
  'https://api.floreal.aiv1/public/documents/789e4567-...',
  {
    method: 'DELETE',
    headers: { 'X-API-Key': 'YOUR_API_KEY' }
  }
);

if (response.ok) {
const result = await response.json();
console.log('✅ Deleted:', result.documentName);
console.log('Database:', result.deleted.database);
console.log('Storage:', result.deleted.storage);
console.log('Search index:', result.deleted.searchIndex);
} else {
console.error('❌ Failed:', await response.text());
}
# Delete a document
response = requests.delete(
    'https://api.floreal.aiv1/public/documents/789e4567-...',
    headers={'X-API-Key': 'YOUR_API_KEY'}
)

if response.status_code == 200:
    result = response.json()
    print(f"✅ Deleted: {result['documentName']}")
    print(f"Database: {result['deleted']['database']}")
    print(f"Storage: {result['deleted']['storage']}")
    print(f"Search index: {result['deleted']['searchIndex']}")
else:
    print(f"❌ Failed: {response.text}")
curl -X DELETE "https://api.floreal.aiv1/public/documents/789e4567-..." \
  -H "X-API-Key: YOUR_API_KEY"

Response

{
  "message": "Document deleted successfully",
  "documentId": "789e4567-e89b-12d3-a456-426614174000",
  "documentName": "Benjamin Gabay - CV",
  "deleted": {
    "database": true,
    "storage": true,
    "searchIndex": true
  }
}

Common Use Cases

Delete all failed uploads

const failed = await fetch("/v1/public/documents?status=failed", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
}).then((r) => r.json());

for (const doc of failed.data) {
  await fetch(`/v1/public/documents/${doc.documentId}`, {
    method: "DELETE",
    headers: { "X-API-Key": "YOUR_API_KEY" },
  });
  console.log(`Deleted: ${doc.documentName}`);
}

Delete documents older than 6 months

const sixMonthsAgo = new Date();
sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6);

const old = await fetch(
  `/v1/public/documents?createdBefore=${sixMonthsAgo.toISOString()}`,
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
).then((r) => r.json());

for (const doc of old.data) {
  await fetch(`/v1/public/documents/${doc.documentId}`, {
    method: "DELETE",
    headers: { "X-API-Key": "YOUR_API_KEY" },
  });
}

Delete specific candidate's documents

// First, list all documents
const all = await fetch("/v1/public/documents?status=completed", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
}).then((r) => r.json());

// Filter by candidate email
const toDelete = all.data.filter((doc) => {
  // Get full details
  return fetch(`/v1/public/documents/${doc.documentId}`, {
    headers: { "X-API-Key": "YOUR_API_KEY" },
  })
    .then((r) => r.json())
    .then((details) => details.contact?.email === "candidate@example.com");
});

// Delete them
for (const doc of toDelete) {
  await fetch(`/v1/public/documents/${doc.documentId}`, {
    method: "DELETE",
    headers: { "X-API-Key": "YOUR_API_KEY" },
  });
}

View full documentation →


Best Practices

Polling Strategy

Use exponential backoff

async function pollWithBackoff(documentId) {
  let delay = 5000; // Start with 5s
  const maxDelay = 30000; // Max 30s

  while (true) {
    const data = await getDocument(documentId);

    if (data.status === "completed" || data.status === "failed") {
      return data;
    }

    await new Promise((r) => setTimeout(r, delay));
    delay = Math.min(delay * 1.5, maxDelay); // Increase delay
  }
}

Set reasonable timeouts

const maxAttempts = 18; // 90 seconds max
const pollInterval = 5000; // 5 seconds

Handle all states

switch (data.status) {
  case "completed":
    return processData(data);
  case "failed":
    return handleError(data.error);
  case "invalid":
    return notifyUser("Not a valid CV");
  case "uploading":
  case "pending":
    return pollAgain();
}

Error Handling

Always check response status

const response = await fetch(url);
if (!response.ok) {
  const error = await response.json();
  throw new Error(error.message);
}

Implement retries for transient errors

async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(url, options);
      if (response.ok) return response;

      if (response.status >= 500) {
        // Server error - retry
        await new Promise((r) => setTimeout(r, 1000 * (i + 1)));
        continue;
      }

      // Client error - don't retry
      throw new Error(await response.text());
    } catch (error) {
      if (i === maxRetries - 1) throw error;
    }
  }
}

Data Management

Store document IDs in your database

// After upload
const { documentId } = await uploadCV(file);
await db.candidates.update(candidateId, { documentId });

Track upload metadata

await db.uploads.create({
  documentId,
  candidateId,
  uploadedBy: userId,
  uploadedAt: new Date(),
  status: "processing",
});

Clean up regularly

// Delete failed uploads older than 7 days
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);

const oldFailed = await fetch(
  `/v1/public/documents?status=failed&createdBefore=${sevenDaysAgo.toISOString()}`,
  { headers: { "X-API-Key": API_KEY } }
).then((r) => r.json());

for (const doc of oldFailed.data) {
  await deleteDocument(doc.documentId);
}

API Reference


Troubleshooting

Document stuck in "pending"

Wait time: Up to 90 seconds
Solution: If longer, contact support with documentId

"Document not found" error

Causes:

  • Wrong documentId
  • Document belongs to different organization
  • Document was deleted

Solution: Verify documentId and API key

List returns empty but summary shows documents

Cause: Filters too restrictive
Solution: Remove filters or adjust date range

Delete fails with 403

Cause: Document belongs to different organization
Solution: Verify you're using correct API key


Next Steps