RecruitSecure AI REST API v1 — programmatic access to semantic CV search and candidate management.
API access requires an Enterprise plan. Generate keys in Dashboard → Settings → API Keys.
All API requests require an API key. You can pass it in two ways:
Authorization header (recommended):
Authorization: Bearer rsa_your_api_key_hereX-API-Key header (alternative):
X-API-Key: rsa_your_api_key_hereAPI keys start with rsa_ and are tied to your organization. Keep them secret — treat them like passwords.
https://your-domain.com/api/v1| Method | Path | Description |
|---|---|---|
GET | /api/v1 | API version and endpoint discovery |
POST | /api/v1/search | Semantic CV search |
GET | /api/v1/candidates | List candidates (paginated) |
GET | /api/v1/candidates/:id | Get candidate detail with CV text |
GET | /api/v1/keys | List API keys (session auth) |
POST | /api/v1/keys | Generate new API key (session auth) |
DELETE | /api/v1/keys/:prefix | Revoke an API key (session auth) |
POST /api/v1/search — Find candidates using natural language queries. Uses semantic embeddings + full-text search for accurate results.
Request body:
{
"query": "senior react developer with TypeScript experience",
"limit": 10 // optional, 1-50, default 10
}Response:
{
"data": [
{
"id": "uuid",
"filename": "alice-cv.pdf",
"candidateName": "Alice Johnson",
"candidateEmail": "alice@example.com",
"similarity": 0.923,
"uploadedAt": "2026-01-15T10:30:00Z",
"fileType": "pdf",
"wordCount": 450
}
],
"meta": {
"page": 1,
"limit": 10,
"total": 1
}
}GET /api/v1/candidates?page=1&limit=20 — List all candidates in your organization with pagination.
GET /api/v1/candidates/:id — Get full candidate detail including CV text content.
All API endpoints are rate limited per organization:
| Scope | Limit |
|---|---|
| General (all endpoints) | 60 requests / minute |
| Search | 30 requests / minute |
Every response includes rate limit headers:
X-RateLimit-Limit: 60 # Max requests per window
X-RateLimit-Remaining: 57 # Remaining requests
Retry-After: 12 # Seconds to wait (only when rate limited)When rate limited, the API returns HTTP 429 with a Retry-After header indicating how many seconds to wait.
| Code | HTTP | Description |
|---|---|---|
unauthorized | 401 | Missing, invalid, or revoked API key |
forbidden | 403 | Plan does not include API access |
not_found | 404 | Resource not found or not in your organization |
rate_limited | 429 | Too many requests — see Retry-After header |
validation_error | 400 | Invalid request body or parameters |
internal_error | 500 | Server error — retry or contact support |
Error response format:
{
"error": {
"code": "validation_error",
"message": "String must contain at least 1 character(s)"
}
}All successful responses wrap data in a consistent envelope:
// Single object
{
"data": { ... }
}
// Collection with pagination
{
"data": [ ... ],
"meta": {
"page": 1,
"limit": 20,
"total": 142
}
}curl -X POST https://your-domain.com/api/v1/search \
-H "Authorization: Bearer rsa_your_api_key" \
-H "Content-Type: application/json" \
-d '{"query": "python data engineer", "limit": 5}'const response = await fetch("https://your-domain.com/api/v1/search", {
method: "POST",
headers: {
"Authorization": "Bearer rsa_your_api_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
query: "python data engineer",
limit: 5,
}),
});
const { data, meta } = await response.json();
console.log(`Found ${meta.total} candidates`);
data.forEach(c => console.log(c.candidateName, c.similarity));import requests
response = requests.post(
"https://your-domain.com/api/v1/search",
headers={"Authorization": "Bearer rsa_your_api_key"},
json={"query": "python data engineer", "limit": 5},
)
result = response.json()
for candidate in result["data"]:
print(f"{candidate['candidateName']} — {candidate['similarity']}")Receive real-time HTTP notifications when events occur in your organization. Configure webhook endpoints in Dashboard → Settings → Webhooks.
Every webhook delivery is a POST request with a JSON body:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "document.upload",
"timestamp": "2026-02-28T14:30:00.000Z",
"organizationId": "org_uuid",
"data": {
"documentId": "doc_uuid",
"filename": "alice-cv.pdf",
"candidateName": "Alice Johnson",
"fileSize": 204800
}
}| Event | Trigger | Data Fields |
|---|---|---|
document.upload | CV uploaded | documentId, filename, candidateName, fileSize |
document.update | Pipeline stage or tags changed | documentId, field, oldValue, newValue / tags |
search.execute | Search performed | query, resultCount |
feedback.submit | Relevance feedback given | documentId, isRelevant |
webhook.test | Manual test from dashboard | message |
Every delivery includes an X-Webhook-Signature header containing an HMAC-SHA256 hex digest of the raw request body, signed with your endpoint's secret. Always verify signatures to ensure payloads are authentic.
Node.js:
const crypto = require("crypto");
function verifyWebhook(secret, body, signature) {
const expected = crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature),
);
}
// In your handler:
const isValid = verifyWebhook(
process.env.WEBHOOK_SECRET,
rawBody,
req.headers["x-webhook-signature"],
);Python:
import hmac, hashlib
def verify_webhook(secret: str, body: bytes, signature: str) -> bool:
expected = hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
# In your handler:
is_valid = verify_webhook(
os.environ["WEBHOOK_SECRET"],
request.body,
request.headers["X-Webhook-Signature"],
)Webhook deliveries are attempted once with a 5-second timeout. If your endpoint returns a non-2xx status code, the delivery is marked as failed. After 5 consecutive failures, the endpoint is automatically paused. You can re-enable it from the dashboard. A retry queue with exponential backoff is planned for a future release.