Query API
The Query API lets you take the logic and structure you define in Qluent and use it outside the product. Ask questions in plain English and receive structured data responses—perfect for building AI experiences.
Getting started
Prerequisites
- A Qluent project with connected data sources
- Project owner permissions (required to manage API keys)
- Your project UUID (found in Project Settings → General)
Creating an API key
- Navigate to Project Settings → API Settings
- Click Create API Key
- Enter a descriptive name (e.g., "Slack Integration", "Query agent")
- Copy the key immediately—it will only be shown once
We store only a one-way hash of your key. If you lose it, you'll need to create a new one.
One key, multiple projects
A single API key is scoped to your client (organization), not to an individual project. Include the project UUID in the URL path to direct each request to the right project. This means you only need to manage one API key while querying across as many projects as you have access to.
Authentication
All requests require your key in the X-API-Key header and the project UUID in the URL path:
curl -X POST "https://api.qluent.io/api/v1/project/your-project-uuid/query/" \
-H "X-API-Key: qk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"question": "What were total sales last month?", "user_email": "analyst@company.com"}'
Response headers
Every response includes headers you can use for debugging and rate limit monitoring:
| Header | Description |
|---|---|
X-Request-ID | Unique trace ID for the request. Use this to reference specific requests when reporting issues. |
X-RateLimit-Limit | Maximum requests allowed per rate limit window |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | Seconds until the rate limit resets |
Making requests
Endpoint
POST https://api.qluent.io/api/v1/project/{project_uuid}/query/
Request body
| Field | Type | Required | Description |
|---|---|---|---|
question | string | Yes | Natural language question to answer |
user_email | string | Yes | Email of the user making the request (for audit) |
thread_id | string | Thread ID for follow-up questions | |
callback_url | string | HTTPS URL for async webhook delivery |
Example request
curl -X POST "https://api.qluent.io/api/v1/project/your-project-uuid/query/" \
-H "X-API-Key: qk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"question": "What were total sales last month?",
"user_email": "analyst@company.com"
}'
Example response
{
"success": true,
"thread_id": "thr_abc123",
"message_id": "msg_def456",
"question": "What were total sales last month?",
"sql": "SELECT SUM(amount) FROM sales WHERE date >= '2024-01-01'",
"explanation": "Total sales last month were $125,000 across 847 transactions.",
"data": [{"total_sales": 125000, "transaction_count": 847}],
"columns": ["total_sales", "transaction_count"],
"row_count": 1,
"google_sheets_url": "https://..."
}
Large result handling
For queries returning more than 1,000 rows, the response includes a URL for accessing the full dataset:
| Field | Type | Description |
|---|---|---|
google_sheets_url | string | URL to open results in Google Sheets. Requires OAuth consent on first use. |
When this URL is present, the data array contains only the first 1,000 rows. Use the URL to access the complete dataset.
Asynchronous queries (webhooks)
If your integration can’t wait for a response, include callback_url in the request. Qluent returns a 202 immediately and posts the result to your webhook.
Example async request
curl -X POST "https://api.qluent.io/api/v1/project/your-project-uuid/query/" \
-H "X-API-Key: qk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"question": "What were total sales last month?",
"user_email": "analyst@company.com",
"callback_url": "https://your-server.com/webhook"
}'
Immediate response (202)
{
"status": "processing",
"request_id": "abc12345"
}
Webhook payload
{
"request_id": "abc12345",
"success": true,
"thread_id": "thr_abc123",
"message_id": "msg_def456",
"question": "What were total sales last month?",
"explanation": "Total sales last month were $125,000 across 847 transactions.",
"data": [{"total_sales": 125000, "transaction_count": 847}],
"columns": ["total_sales", "transaction_count"],
"row_count": 1,
"google_sheets_url": "https://..."
}
Webhook signature verification
Webhooks include the X-Qluent-Signature header. Verify it using HMAC‑SHA256 with your project’s webhook_secret:
import hmac
import hashlib
def verify_signature(body: bytes, signature: str, webhook_secret: str) -> bool:
expected = hmac.new(webhook_secret.encode(), body, hashlib.sha256).hexdigest()
provided = signature.replace("sha256=", "")
return hmac.compare_digest(expected, provided)
Callback URL requirements:
- Must use HTTPS
- Must be publicly accessible
- Must respond within 30 seconds
- Failed deliveries retried up to 3 times
Follow-up questions
Continue a conversation by including the thread_id from the previous response:
curl -X POST "https://api.qluent.io/api/v1/project/your-project-uuid/query/" \
-H "X-API-Key: qk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"question": "Break that down by region",
"thread_id": "thr_abc123",
"user_email": "analyst@company.com"
}'
Clarification responses
When a question is too vague or ambiguous, the API returns a 422 with error_code: "CLARIFICATION_NEEDED". The response includes a clarification object with structured follow-up questions your application can render to the user:
{
"success": false,
"thread_id": "thr_abc123",
"message_id": "msg_def456",
"question": "orders",
"clarification": {
"message": "I need some clarification:",
"questions": [
"Are you looking for order counts, order values, or order details?",
"What time period are you interested in?"
]
},
"error_code": "CLARIFICATION_NEEDED",
"error": null
}
| Field | Type | Description |
|---|---|---|
clarification.message | string | A summary of why clarification is needed |
clarification.questions | string[] | Specific follow-up questions the user should answer |
Use the thread_id from the response to send the user's clarification as a follow-up question.
In rare cases where the model can't break the clarification into distinct questions, questions will be an empty array and message will contain the full clarification text.
Streaming responses (SSE)
For real-time progress updates during query execution, use the streaming endpoint:
POST https://api.qluent.io/api/v1/project/{project_uuid}/query/stream
The request body is the same as the standard endpoint. The response is a Server-Sent Events stream with these event types:
| Event | Description |
|---|---|
status | Processing status updates |
sql | The generated SQL query |
result | Final query result with data |
clarification | Question requires clarification |
error | Error occurred during processing |
Feedback endpoint
Submit feedback on query responses to help improve result quality.
Endpoint
POST https://api.qluent.io/api/v1/project/{project_uuid}/feedback/
Request body
| Field | Type | Required | Description |
|---|---|---|---|
message_id | string | Yes | The message_id from a query response |
positive | boolean | Yes | true for positive feedback, false for negative |
comment | string | No | Optional feedback text (max 10,000 chars) |
user_email | string | Yes | Email of user submitting feedback (must have project access) |
Example request
curl -X POST "https://api.qluent.io/api/v1/project/your-project-uuid/feedback/" \
-H "X-API-Key: qk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"message_id": "msg_def456",
"positive": true,
"comment": "Exactly what I needed",
"user_email": "analyst@company.com"
}'
Example response
{
"success": true,
"feedback_id": 123,
"message": "Feedback submitted successfully"
}
Error codes
| Error Code | HTTP Status | Description |
|---|---|---|
INVALID_REQUEST | 400 | Malformed request body |
INVALID_API_KEY | 401 | Invalid or missing API key |
USER_NOT_AUTHORIZED | 403 | User does not have access to this project |
MESSAGE_NOT_FOUND | 404 | Invalid message_id or not in project |
Error handling
| Error Code | HTTP Status | Description |
|---|---|---|
INVALID_REQUEST | 400 | Malformed request body |
INVALID_API_KEY | 401 | Invalid or missing API key |
USER_NOT_AUTHORIZED | 403 | User does not have access to this project |
PROJECT_NOT_FOUND | 404 | Project not found |
THREAD_NOT_FOUND | 404 | Thread not found or expired |
CLARIFICATION_NEEDED | 422 | Question requires clarification |
CANNOT_ANSWER | 422 | Question cannot be answered with available data |
RATE_LIMIT_EXCEEDED | 429 | Too many requests |
EXECUTION_ERROR | 500 | Failed to execute query |
DATA_SOURCE_ERROR | 502 | Unable to connect to data source |
SERVICE_UNAVAILABLE | 503 | Service temporarily unavailable |
QUERY_TIMEOUT | 504 | Query execution timed out |
Rate limits
| Limit | Value |
|---|---|
| Requests per minute | 100 per project |
| Concurrent requests | 10 per project |
| Max query execution time | 5 minutes |
When rate limited, implement exponential backoff using the retry_after field in the response.
Security FAQ
How are API keys generated and stored?
Your API key is generated using cryptographically secure randomness. The full key (qk_...) is shown only once at creation—we never store or display it again. We store only a one-way hash, so even if our database were compromised, your actual key cannot be recovered.
How does authentication work?
Every API request requires your key in the X-API-Key header and the target project UUID in the URL path. We hash the incoming key and compare it against stored hashes—your raw key never touches our database. A single API key can access multiple projects—include the project UUID in the URL to direct each request to the right one.
How is streaming secured?
The SSE streaming endpoint uses the same API key authentication as the standard query endpoint. All data is transmitted over HTTPS with TLS encryption. Each event in the stream is authenticated as part of the same request that provided the API key.
Can I encrypt API responses?
Yes. You can configure PGP encryption per API key:
- Upload your PGP public key in API Settings
- All responses are encrypted with your public key before transmission
- Only you can decrypt with your private key
Even Qluent cannot read the encrypted payload. Encryption can be enabled, updated, or removed at any time from Project Settings → API Settings.
Who can manage API keys?
Only project owners can create, configure, or revoke API keys. Keys can be instantly revoked if compromised. We track last_used_at so you can audit key activity.
What about rate limiting?
Rate limits are applied per project (not per API key). Every response includes X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers so you can monitor your usage—see Response headers for details.
Managing keys
- View keys: Project Settings → API Settings
- Revoke keys: Click Revoke next to any key (takes effect immediately)
- Configure encryption: Click Configure to upload your PGP public key
Revoked keys stop working instantly. Applications using the key will receive 401 errors.