Floreal Logo

Floreal API Documentation

What is Floreal?

Floreal is the AI layer for the HR industry—transforming how companies find, evaluate, and match candidates. We combine semantic search, intelligent automation, and conversational AI to streamline the entire recruitment workflow.

🚀 Our Products

1. Instant Match (Rolling out November 2025)

Real-time candidate matching powered by AI. Upload a job description and instantly get ranked candidates from your database with relevance scores, match explanations, and skills gap analysis. Perfect for high-volume recruiting and urgent hiring needs.

2. Search API

Build custom talent search into your applications. Our semantic search understands natural language queries and returns candidates based on meaning, not just keywords. Choose from Dense (semantic), Sparse (keyword), Hybrid (best quality), or Builtin-Rerank algorithms depending on your speed vs. accuracy needs.

3. HR Note Taker (Rolling out November 2025)

AI meeting assistant that joins your interviews, captures key insights, and automatically generates structured candidate evaluations. Never miss important details and eliminate manual note-taking during conversations.

4. Semantic CV Database

Automatically extract, structure, and index CVs for instant searchability. Our AI processes PDFs, DOCs, and text files to extract 100+ attributes including contact info, skills, experience, education, and domain expertise. Every CV becomes fully searchable through natural language. Includes automatic generation of skills competency matrices (fiches de compétences) showing technical abilities, domain expertise, and proficiency levels for standardized talent assessment.

5. Skills Competency Matrix

Generate comprehensive skill assessments and competency profiles for every candidate. Our AI analyzes CVs to create standardized skill matrices showing technical abilities, domain expertise, and proficiency levels—perfect for talent mapping and skills gap analysis.

Document Upload

Search Groups

Authentication

All endpoints require an API key:

Authorization: x-API-key YOUR_API_KEY

Get your API key at: https://www.floreal.ai/settings/api-keys

Base URL

https://api.floreal.ai

title: "API Quickstart" description: "Get started with CV upload, management, and candidate search in minutes"


API Quickstart

Build your talent search platform in minutes. Upload CVs, let AI extract structured data, and search for candidates using semantic and keyword matching.

What You'll Build

upload

📤 Upload CVs

Upload PDF/DOC CVs from URLs, files, or cloud storage

sparkles

🤖 AI Processing

Automatic extraction of contact, skills, and experience

magnifying-glass

🔍 Semantic Search

Find candidates using natural language queries

chart-bar

📊 Structured Data

100+ attributes ready for filtering and analysis


Prerequisites

Before starting, you'll need:

  • API Key - Get yours from dashboard settings
  • Base URL - https:/www.floreal.ai
  • HTTP Client - cURL, Postman, or any programming language

Complete Workflow in 3 Steps

Step 1: Upload a CV

Choose your upload method:

Easiest method - just provide a URL:

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://example.com/john-doe-cv.pdf",
    "documentName": "John Doe - Software Engineer",
    "documentDate": "11-2025"
  }'

Response:

{
  "documentId": "789e4567-e89b-12d3-a456-426614174000",
  "status": "uploading",
  "message": "Document fetched from URL and processing started",
  "estimatedProcessingTime": "30-60 seconds"
}

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 - 3 steps, direct to S3:

# 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 time: 30 to 90 seconds


Step 2: Wait for Processing

Poll the status endpoint until status === "completed":

async function waitForCompletion(documentId) {
  for (let i = 0; i < 18; i++) {
    // 90 seconds max
    const res = await fetch(
      `https://api.floreal.aiv1/public/documents/${documentId}`,
      { headers: { "X-API-Key": "YOUR_API_KEY" } }
    );
    const data = await res.json();

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

    if (data.status === "failed") {
      throw new Error(data.error?.message);
    }

    console.log(`⏳ Status: ${data.status}`);
    await new Promise((r) => setTimeout(r, 5000)); // Wait 5s
  }
  throw new Error("Timeout");
}

const result = await waitForCompletion("789e4567-...");
console.log("Contact:", result.contact);
console.log("Profile:", result.profile);
import time
import requests

def wait_for_completion(document_id):
    for i in range(18):  # 90 seconds max
        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'] == 'failed':
            raise Exception(data.get('error', {}).get('message'))

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

    raise Exception('Timeout')

result = wait_for_completion('789e4567-...')
print('Contact:', result['contact'])
print('Profile:', result['profile'])
# Poll manually every 5 seconds
curl "https://api.floreal.aiv1/public/documents/789e4567-..." \
  -H "X-API-Key: YOUR_API_KEY"

Step 3: Get Complete CV Data

Once status === "completed", the response includes:

{
  "documentId": "789e4567-e89b-12d3-a456-426614174000",
  "status": "completed",

  "summary": "Benjamin Gabay - Sales-marketing Professional with 4 years of experience...",

  "extractedText": "Contact +33766771226\nLinkedIn: linkedin.com/in/benjamin-gabay\n\nBenjamin 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", "Prospecting"],
    "industries": ["Real-estate", "Social Media"],
    "recentExpertise": ["Ghostwriting", "LinkedIn Coaching"],
    "hasManagement": false
  },

  "attributes": {
    "languages": {
      "french": 1,
      "languages_count": 1
    },
    "technical_skills": {
      "copywriting": 1,
      "content_creation": 1,
      "technical_skills_count": 3
    },
    "professional_experience": {
      "total_experience_years": 4,
      "number_of_employers": 3,
      "average_tenure_months": 16
    },
    "education_indicators": {
      "highest_degree": "Bachelor",
      "education_field": ["Philosophy", "Literature"],
      "first_school_name": "University of Paris I: Panthéon-Sorbonne"
    }
  }
}

What you get:

  • summary - AI-generated 2-3 sentence overview
  • extractedText - Complete CV text (5-15KB) for custom analysis
  • contact - Name, email, LinkedIn, location
  • profile - Domain, seniority, experience, skills, industries
  • attributes - 100+ structured fields for advanced filtering

Understanding the Data Structure

Summary

AI-generated professional overview including:

  • Name and location
  • Years of experience
  • Educational background
  • Key technical skills
  • Domain expertise
  • Recent specializations

Example:

"Benjamin Gabay - Sales-marketing Professional based in Paris, FRA with 4 years of experience. Educational background: Bachelor in Philosophy from University of Paris I. Technical expertise: Copywriting, Content Creation. Domain experience: Ghostwriting, LinkedIn Coaching, Lead Generation."

Extracted Text

Complete CV content (typically 5-15KB):

  • Full unredacted text from the original CV
  • Use for: full-text search, LLM analysis, custom parsing, document display
  • Includes all original formatting and details

Contact Information

{
  "firstName": "Benjamin",
  "lastName": "Gabay",
  "email": "benjamin.gabay@example.com", // or "Unknown" if not found
  "linkedin": "www.linkedin.com/in/benjamin-gabay",
  "location": {
    "city": "Paris",
    "country": "FRA" // ISO 3166-1 alpha-3 code
  }
}

Professional Profile

{
  "domain": "Sales Marketing", // Primary professional area
  "specialization": "Content Marketing", // Specific expertise
  "seniorityLevel": "Junior", // Junior | Senior | Executive
  "experienceYears": 4, // Total years of experience
  "technicalStack": ["Copywriting", "Content Creation"],
  "industries": ["Real-estate", "Social Media"],
  "recentExpertise": ["Ghostwriting", "LinkedIn Coaching"],
  "hasManagement": false // Leadership experience
}

Attributes (100+ Fields)

Languages

{
  "french": 1,
  "english": 1,
  "english_fluent": 1,
  "languages_count": 2,
  "cv_language_detected": "French"
}

Technical Skills

{
  "copywriting": 1,
  "python_experience": 1,
  "javascript_experience": 1,
  "technical_skills_count": 3,
  "programming_languages_count": 2
}

Professional Experience

{
  "total_experience_years": 4,
  "number_of_employers": 3,
  "average_tenure_months": 16,
  "current_position_duration": 4,
  "management_experience": 1,
  "job_stability_score": 3,
  "career_progression_score": 5
}

Education

{
  "highest_degree": "Bachelor",
  "education_field": ["Philosophy", "Literature"],
  "first_school_name": "University of Paris I: Panthéon-Sorbonne",
  "multiple_degrees": 1,
  "education_prestige_score": 3
}

Industry Verticals

{
  "real_estate": 1,
  "social_media": 1,
  "fintech": 1,
  "verticals_count": 3
}

Business Specialization

{
  "roi_analysis": 1,
  "training_experience": 1,
  "revenue_generation_experience": 1,
  "client_relationship_experience": 1,
  "business_orientation": 3
}

Market & Client Experience

{
  "emea": 1,
  "startup": 1,
  "b2b_focus": 1,
  "enterprise": 1,
  "market_segments_count": 4
}

Searching for Candidates

Once CVs are uploaded and processed, search using natural language or keywords:

Dense Search (Semantic)

Best for: Natural language queries, conceptual matching

curl -X POST "https://api.floreal.aiv1/public/searches/dense" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "experienced backend engineer who can lead teams",
    "top_k": 20
  }'

Response:

{
  "searchId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "processing",
  "searchType": "dense",
  "estimatedTime": "5-10 seconds",
  "statusUrl": "/v1/public/searches/550e8400-...",
  "resultsUrl": "/v1/public/searches/550e8400-.../results"
}

Sparse Search (Keywords)

Best for: Exact skill matching, technical terms

curl -X POST "https://api.floreal.aiv1/public/searches/sparse" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "Python, FastAPI, PostgreSQL, Docker, AWS",
    "top_k": 20
  }'

Hybrid Search (Best Quality)

Best for: Complex queries, maximum accuracy

curl -X POST "https://api.floreal.aiv1/public/searches/hybrid" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "senior Python developer with startup experience who can mentor juniors",
    "top_k": 30,
    "rerank_top_n": 20
  }'

Processing time: 15-30 seconds


Getting Search Results

Step 1: Poll for Completion

curl "https://api.floreal.aiv1/public/searches/550e8400-..." \
  -H "X-API-Key: YOUR_API_KEY"

Wait for:

{
  "searchId": "550e8400-...",
  "status": "completed", // ← Ready!
  "resultsCount": 42,
  "resultsUrl": "/v1/public/searches/550e8400-.../results"
}

Step 2: Fetch Results

curl "https://api.floreal.aiv1/public/searches/550e8400-.../results" \
  -H "X-API-Key: YOUR_API_KEY"

Response structure:

{
  "searchId": "550e8400-...",
  "query": "experienced backend engineer",
  "searchType": "dense",
  "results": [
    {
      "documentId": "789e4567-...",
      "documentName": "Benjamin_Gabay_CV.pdf",
      "score": 0.856,

      "extractedText": "Complete CV text here...",
      "summary": "Benjamin Gabay - Technology Professional...",

      "contact": {
        "firstName": "Benjamin",
        "lastName": "Gabay",
        "email": "benjamin@example.com",
        "linkedin": "linkedin.com/in/benjamin-gabay",
        "location": { "city": "Paris", "country": "FRA" }
      },

      "profile": {
        "domain": "Technology",
        "specialization": "Backend Development",
        "seniorityLevel": "Senior",
        "experienceYears": 8,
        "technicalStack": ["Python", "PostgreSQL", "Docker"],
        "hasManagement": true
      },

      "attributes": {
        "languages": { "english": 1, "french": 1 },
        "technical_skills": { "python_experience": 1 },
        "professional_experience": { "total_experience_years": 8 }
      }
    }
  ],
  "totalResults": 42
}

Document Management

List All Documents

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

Response:

{
  "data": [
    {
      "documentId": "789e4567-...",
      "documentName": "Benjamin Gabay - CV",
      "status": "completed",
      "documentDate": "11-2025",
      "candidate": {
        "firstName": "Benjamin",
        "lastName": "Gabay",
        "fullName": "Benjamin Gabay",
        "domain": "Sales Marketing"
      },
      "timestamps": {
        "createdAt": "2025-11-05T14:30:00Z"
      }
    }
  ],
  "pagination": {
    "limit": 20,
    "offset": 0,
    "total": 127,
    "hasMore": true
  },
  "summary": {
    "completed": 115,
    "pending": 5,
    "failed": 3,
    "total": 127
  }
}

Delete Document

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

Removes from:

  1. Database (document record)
  2. Cloud Storage (S3 file)
  3. Vector Database (search embeddings)

Complete Integration Example

const BASE_URL = "https://api.floreal.ai";
const API_KEY = "YOUR_API_KEY";

// 1. Upload CV
async function uploadCV(url, name) {
  const response = await fetch(
    `${BASE_URL}/v1/public/documents/upload-from-url`,
    {
      method: "POST",
      headers: {
        "X-API-Key": API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        url: url,
        documentName: name,
        documentDate: "11-2025",
      }),
    }
  );

  const { documentId } = await response.json();
  console.log("✅ Upload started:", documentId);

  // Wait for processing
  return await waitForProcessing(documentId);
}

// 2. Wait for processing
async function waitForProcessing(documentId) {
  for (let i = 0; i < 18; i++) {
    const response = await fetch(
      `${BASE_URL}/v1/public/documents/${documentId}`,
      {
        headers: { "X-API-Key": API_KEY },
      }
    );

    const data = await response.json();

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

    if (data.status === "failed") {
      throw new Error(`Failed: ${data.error?.message}`);
    }

    console.log(`⏳ ${data.status}...`);
    await new Promise((r) => setTimeout(r, 5000));
  }
  throw new Error("Timeout");
}

// 3. Search for candidates
async function searchCandidates(query) {
  // Start search
  const searchResponse = await fetch(`${BASE_URL}/v1/public/searches/dense`, {
    method: "POST",
    headers: {
      "X-API-Key": API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ query, top_k: 20 }),
  });

  const { searchId } = await searchResponse.json();
  console.log("🔍 Search started:", searchId);

  // Wait for completion
  for (let i = 0; i < 20; i++) {
    const statusResponse = await fetch(
      `${BASE_URL}/v1/public/searches/${searchId}`,
      {
        headers: { "X-API-Key": API_KEY },
      }
    );

    const status = await statusResponse.json();

    if (status.status === "completed") {
      // Get results
      const resultsResponse = await fetch(
        `${BASE_URL}/v1/public/searches/${searchId}/results`,
        {
          headers: { "X-API-Key": API_KEY },
        }
      );

      const results = await resultsResponse.json();
      console.log(`✅ Found ${results.totalResults} candidates`);
      return results;
    }

    if (status.status === "failed") {
      throw new Error(`Search failed: ${status.error}`);
    }

    await new Promise((r) => setTimeout(r, 3000));
  }
  throw new Error("Search timeout");
}

// Usage
async function main() {
  // Upload CVs
  const cv1 = await uploadCV(
    "https://example.com/john-doe.pdf",
    "John Doe - Software Engineer"
  );
  console.log("Contact:", cv1.contact);
  console.log("Profile:", cv1.profile);

  // Search
  const results = await searchCandidates("experienced Python developer");
  results.results.forEach((candidate) => {
    console.log(`${candidate.contact.fullName} - Score: ${candidate.score}`);
  });
}

main();
import requests
import time

BASE_URL = 'https://api.floreal.ai'
API_KEY = 'YOUR_API_KEY'

def upload_cv(url, name):
    """Upload CV and wait for processing"""
    # Start upload
    response = requests.post(
        f'{BASE_URL}/v1/public/documents/upload-from-url',
        headers={
            'X-API-Key': API_KEY,
            'Content-Type': 'application/json'
        },
        json={
            'url': url,
            'documentName': name,
            'documentDate': '11-2025'
        }
    )

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

    # Wait for processing
    for i in range(18):
        status_response = requests.get(
            f'{BASE_URL}/v1/public/documents/{document_id}',
            headers={'X-API-Key': API_KEY}
        )

        data = status_response.json()

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

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

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

    raise Exception('Timeout')

def search_candidates(query):
    """Search for candidates"""
    # Start search
    search_response = requests.post(
        f'{BASE_URL}/v1/public/searches/dense',
        headers={
            'X-API-Key': API_KEY,
            'Content-Type': 'application/json'
        },
        json={'query': query, 'top_k': 20}
    )

    search_id = search_response.json()['searchId']
    print(f'🔍 Search started: {search_id}')

    # Wait for completion
    for i in range(20):
        status_response = requests.get(
            f'{BASE_URL}/v1/public/searches/{search_id}',
            headers={'X-API-Key': API_KEY}
        )

        status = status_response.json()

        if status['status'] == 'completed':
            # Get results
            results_response = requests.get(
                f'{BASE_URL}/v1/public/searches/{search_id}/results',
                headers={'X-API-Key': API_KEY}
            )

            results = results_response.json()
            print(f"✅ Found {results['totalResults']} candidates")
            return results

        if status['status'] == 'failed':
            raise Exception(f"Search failed: {status.get('error')}")

        time.sleep(3)

    raise Exception('Search timeout')

# Usage
if __name__ == '__main__':
    # Upload CV
    cv = upload_cv(
        'https://example.com/john-doe.pdf',
        'John Doe - Software Engineer'
    )
    print('Contact:', cv['contact'])
    print('Profile:', cv['profile'])

    # Search
    results = search_candidates('experienced Python developer')
    for candidate in results['results']:
        print(f"{candidate['contact']['fullName']} - Score: {candidate['score']}")

Quick Reference

Upload Methods Comparison

MethodComplexityRequestsBest For
URL⭐ Simple1Automation, webhooks, files already hosted
Direct⭐ Simple1Quick integration, local files
Presigned⭐⭐⭐ Complex3Large files, browser uploads

Search Algorithms Comparison

AlgorithmSpeedQualityBest For
Dense5-10sGoodNatural language, concepts
Sparse5-10sGoodKeywords, technical skills
Hybrid15-30sBestMaximum quality, complex queries
Builtin-Rerank10-20sBetterMiddle ground

Processing Times

OperationDuration
CV Upload0-15s
CV Processing30-90s
Dense/Sparse Search5-10s
Hybrid Search15-30s
Builtin-Rerank Search10-20s

Best Practices

Uploads

  • ✅ Use URL upload for automation and webhooks
  • ✅ Validate file size (max 10MB) before uploading
  • ✅ Store documentId in your database for reference
  • ✅ Handle failed uploads gracefully with retries
  • ✅ Use direct download URLs (not sharing links)

Polling

  • ✅ Poll every 5 seconds for CV processing
  • ✅ Poll every 3 seconds for search results
  • ✅ Set reasonable timeouts (90s for CVs, 60s for searches)
  • ✅ Handle both completed and failed states

Searching

  • ✅ Start with Dense search for most queries
  • ✅ Use Sparse for exact keyword matching
  • ✅ Use Hybrid when quality matters most
  • ✅ Cache search results - they don't change
  • ✅ Filter results by score (>0.7 for high quality)

Data Management

  • ✅ Store documentId and searchId in your database
  • ✅ Use extractedText for custom analysis
  • ✅ Parse attributes for advanced filtering
  • ✅ Delete outdated CVs to maintain clean database

Next Steps


Support

Need help? We're here to assist:

  • 💬 Chat: Available in Discord
  • 📚 Docs: Full documentation available
  • 🐛 Issues: Report bugs via support