Base URL: http://localhost:8000/api/v1
Interactive docs: http://localhost:8000/api/docs (Swagger UI)
Authentication: All endpoints (except /auth/login and /auth/refresh) require Authorization: Bearer <access_token>
// Request
{ "email": "admin@p99soft.com", "password": "Admin@P99#2025" }
// Response 200
{
"access_token": "eyJ...",
"refresh_token": "...",
"token_type": "bearer",
"user": {
"id": "uuid",
"org_id": "uuid",
"email": "admin@p99soft.com",
"name": "P99soft Admin",
"role": "super_admin",
"permissions": ["candidates:read", "candidates:create", ...]
}
}
{ "refresh_token": "..." }
// Returns same structure as /auth/login
{ "refresh_token": "..." }
// Revokes the refresh token
Returns current user profile + permissions.
{ "name": "New Name", "password": "NewPass123", "department": "HR" }
List all users in the org. Requires users:read.
{
"email": "jane@company.com",
"name": "Jane Smith",
"password": "SecurePass123",
"role": "recruiter",
"department": "HR",
"title": "Senior Recruiter"
}
Requires users:manage.
List all roles (system + org custom). Requires roles:manage.
{
"name": "Custom Role",
"description": "...",
"permissions": ["candidates:read", "jobs:read"]
}
List all available permissions.
{ "user_id": "uuid", "role_name": "hiring_manager" }
Query params: search, source, skill, page, page_size
{
"name": "John Doe",
"email": "john@example.com",
"current_title": "Senior Engineer",
"years_exp": 6,
"skills": ["Python", "React"],
"location": "Hyderabad",
"source": "linkedin"
}
candidates:delete{ "content": "Great candidate, strong Python background" }
Query params: status, search
{
"title": "Senior Backend Engineer",
"department": "Engineering",
"skills": ["Python", "FastAPI", "PostgreSQL"],
"locations": ["Hyderabad", "Pune"],
"min_exp": 4,
"headcount": 2,
"priority": "high"
}
Query params: job_req_id, stage
{
"candidate_id": "uuid",
"job_req_id": "uuid",
"stage": "screening"
}
{
"stage": "round_1",
"notes": "Strong screening call"
}
Valid stages: sourced, screening, assessment, round_1, round_2, culture_fit, offer_negotiation, offer_extended, offer_accepted, joined, rejected, withdrawn
Full entry with candidate, job, rounds, and offer.
{
"pipeline_entry_id": "uuid",
"round_number": 1,
"round_type": "technical",
"title": "System Design",
"scheduled_at": "2025-01-15T10:00:00",
"interviewer_id": "uuid"
}
{
"status": "completed",
"rating": 4,
"recommendation": "proceed",
"feedback_text": "Strong candidate...",
"strengths": "Architecture knowledge",
"concerns": "Needs more React experience"
}
{
"pipeline_entry_id": "uuid",
"offered_ctc": 24.0,
"variable_component": 4.0,
"joining_date": "2025-02-01",
"offer_expiry": "2025-01-20"
}
{ "status": "accepted" }
Returns tab names for a Google Sheets URL.
{ "url": "https://docs.google.com/spreadsheets/d/...", "tab": "Q1 Roles" }
{
"jds": [
{
"role": "Senior Backend Engineer",
"skills": ["Python", "FastAPI"],
"locations": ["Hyderabad"],
"min_exp": 4
}
]
}
Returns { "job_ids": ["uuid1", ...], "count": 1 }
SSE stream of real-time log messages.
Downloads Excel file with scored candidates.
{
"open_jobs": 12,
"total_candidates": 450,
"active_pipeline": 67,
"offers_extended": 4,
"joined": 8
}
Array of { stage, count } for all pipeline stages.
Array of { source, count } by candidate source.
Sourcing job stats by status.
Time series for dashboard KPI sparklines. Permission: analytics:read.
{
"period": "weekly",
"pipeline_added": [{ "label": "W12 May", "cnt": 4 }],
"hires": [{ "label": "W12 May", "cnt": 1 }]
}
weekly (default): last 8 weeks — pipeline_added uses PipelineEntry.created_at; hires uses joined stage updated_at.daily: last 14 days.Permission: analytics:leaderboard. Cancelled interview rounds are excluded from totals and trends.
Returns { "leaderboard": [...], "trend": [...] }. Each leaderboard row: user_id, name, department, role, total_interviews (non-cancelled rounds), scheduled, completed.
All AI endpoints require ai:use permission.
// Request
{ "candidate_id": "uuid", "job_req_id": "uuid" }
// Response
{
"score": 7.8,
"reasoning": "Strong Python and FastAPI background with 6 years experience...",
"strengths": ["FastAPI expertise", "System design", "Team player"],
"gaps": ["No Kubernetes experience", "Below min experience by 1 year"]
}
Also persists ai_score, ai_reasoning, ai_strengths, ai_gaps to the candidate record.
// Request
{ "raw_text": "We are looking for a Senior Engineer with 5+ years..." }
// Response
{
"title": "Senior Backend Engineer",
"seniority": "senior",
"skills": ["Python", "FastAPI", "PostgreSQL", "Docker"],
"min_exp": 5,
"must_haves": ["Python 3.10+", "REST API design"],
"nice_to_haves": ["Kubernetes", "AWS"],
"locations": ["Hyderabad", "Remote"],
"department": "Engineering"
}
// Request
{ "job_req_id": "uuid", "candidate_id": "uuid", "round_type": "technical" }
// round_type: technical | behavioral | culture_fit | hr
// Response
{ "questions": ["Question 1...", "Question 2...", ...(10 total)] }
// Request
{ "pipeline_entry_id": "uuid" }
// Response
{
"subject": "Offer of Employment – Senior Backend Engineer at P99soft",
"letter_text": "Dear John,\n\nWe are delighted to offer..."
}
Also saves letter_text to the offer record in the database.
// Request
{
"candidate_id": "uuid",
"stage": "screening",
"job_req_id": "uuid",
"context": "Candidate has 7 years experience in fintech"
}
// Response
{
"subject": "Interview Invitation – Senior Backend Engineer at P99soft",
"body": "Hi John,\n\nI came across your profile and was impressed..."
}
// Request
{ "job_req_id": "uuid" }
// Response
{
"linkedin_queries": ["(Senior Engineer OR Lead Engineer) AND (Python OR FastAPI) ...", ...],
"github_queries": ["language:python location:Hyderabad ..."],
"naukri_queries": ["python fastapi senior engineer hyderabad", ...],
"general_queries": ["site:github.com python developer Hyderabad", ...]
}
{ "status": "ok", "app": "P99soft Recruitment Platform", "version": "3.0.0", "env": "development" }
| Code | Meaning |
|---|---|
| 400 | Bad request / validation error |
| 401 | Missing or invalid/expired JWT |
| 403 | Insufficient permission or role |
| 404 | Resource not found (in your org) |
| 409 | Conflict (duplicate email, duplicate pipeline entry) |
| 422 | Pydantic validation error (request body) |
| 429 | Rate limit exceeded |
| 500 | Internal server error |
All error responses follow:
{ "detail": "Error message here" }