Quick Start
The fastest way to get a report - a single API call with waitForCompletion.
Create an API key from your dashboard. Your key will look like gr_live_Ab3xY9... - copy it immediately, it's only shown once.
Use waitForCompletion: true to get the report in a single request (waits up to 120s).
curl -X POST https://genready.ai/api/v1/analyze \
-H "Authorization: Bearer gr_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "options": {"waitForCompletion": true}}'The response includes scores, detailed metrics, and a prioritized fix checklist.
json{
"data": {
"reportId": "acae9782-f6aa-46d0-ae79-8aca8eb5a3bf",
"url": "https://example.com",
"scope": "full",
"status": "completed",
"scores": {
"overall": 68,
"content": 62,
"crawlability": 74,
"details": { "...": "see Endpoints section for full shape" }
},
"fixChecklist": [
{
"severity": "critical",
"category": "crawlability",
"metric": "Firewall",
"problem": "WAF is blocking AI crawlers.",
"fix": "Whitelist AI crawler user agents in your WAF configuration."
},
{
"severity": "high",
"category": "content",
"metric": "Facts & Details",
"problem": "Only 12 entities found in 2,400 words (0.5% density).",
"fix": "Replace vague claims with named sources and data."
}
],
"recommendations": [ ... ]
}
}Prefer async? Omit waitForCompletion and poll GET /reports/:id/status until status is completed. Or register a webhook to get notified automatically.
Authentication
All API requests require a valid API key sent as a Bearer token in the Authorization header.
Authorization: Bearer gr_live_xxxxxCreate and manage your API keys at /api-keys. Keys are shown once at creation - store them securely.
Base URL
https://genready.ai/api/v1All endpoint paths below are relative to this base URL.
Endpoints
/analyzeSubmit a URL for AI readiness analysis.
Request body:
json{
"url": "https://example.com/page",
"scope": "full",
"options": {
"waitForCompletion": false
}
}Scope options:
| Scope | What runs | Credit cost | Est. time |
|---|---|---|---|
| "full" (default) | Content + Crawlability + AI analysis | 1 credit | ~25-40s |
| "content" | Content quality only | 1 credit | ~15-25s |
| "crawlability" | Technical crawlability only | 1 credit | ~10-15s |
Response (async - 202):
json{
"data": {
"reportId": "acae9782-f6aa-46d0-ae79-8aca8eb5a3bf",
"scope": "full",
"status": "queued",
"estimatedSeconds": 30,
"statusUrl": "/api/v1/reports/acae9782-.../status",
"reportUrl": "/api/v1/reports/acae9782-..."
},
"meta": { "requestId": "req_abc123", "timestamp": "2026-03-17T06:00:00Z" }
}Response (sync - waitForCompletion: true, 200):
Returns the full report directly (same shape as GET /reports/:id). Times out after 120s with a 202 if analysis is still running.
Error codes:
| Status | Code | Reason |
|---|---|---|
| 400 | INVALID_URL | URL failed validation |
| 400 | INVALID_SCOPE | Scope must be "full", "content", or "crawlability" |
| 402 | CREDITS_EXHAUSTED | Monthly API credits used up |
| 429 | RATE_LIMITED | Too many requests per minute |
/reports/:idRetrieve a completed analysis report.
Returns the full report data. Response shape adapts to the scope used during analysis.
Enriched metrics: Each metric in details includes self-documenting metadata: key, name, description, maxScore, category, subcategory, and weight - so you don't need to hardcode metric definitions.
json{
"data": {
"reportId": "acae9782-...",
"url": "https://example.com/page",
"scope": "full",
"status": "completed",
"createdAt": "2026-03-17T06:00:00Z",
"completedAt": "2026-03-17T06:00:32Z",
"scores": {
"overall": 68,
"content": 62,
"crawlability": 74,
"details": {
"entity_ratio": {
"key": "entity_ratio",
"name": "Facts & Details",
"description": "Measures the density of named entities...",
"score": 5.2,
"verdict": "warn",
"maxScore": 10,
"category": "content",
"subcategory": "helpful_content",
"weight": 1,
"wordCount": 1850
},
"robots_txt": {
"key": "robots_txt",
"name": "AI Access Rules",
"description": "Checks robots.txt for AI crawler access...",
"score": 18,
"verdict": "pass",
"maxScore": 18,
"category": "crawlability",
"subcategory": null,
"weight": null,
"aiCrawlersAllowed": true,
"blockedBots": []
},
"ttfb": {
"key": "ttfb",
"name": "Loading Speed",
"description": "Time to first byte measurement...",
"score": 7,
"verdict": "warn",
"maxScore": 10,
"category": "crawlability",
"subcategory": null,
"weight": null,
"ms": 620
},
"...": "additional metrics omitted for brevity"
}
},
"fixChecklist": [
{
"severity": "critical",
"category": "crawlability",
"metric": "Firewall",
"problem": "WAF is blocking AI crawlers.",
"fix": "Whitelist AI crawler user agents in your WAF configuration."
},
{
"severity": "high",
"category": "content",
"metric": "Links to Sources",
"problem": "Only 1 outbound link in 1,850 words.",
"fix": "Add 3-5 citations to authoritative sources."
}
],
"recommendations": [
{
"priority": "high",
"category": "content",
"title": "Add more statistics and data points",
"description": "Pages with 3+ cited statistics are more likely to be referenced by AI."
}
]
}
}Fix checklist:
The fixChecklist array contains actionable items sorted by severity: critical → high → medium → low. Each item includes the affected metric, a description of the problem, and a concrete fix instruction. Use this to build "what to fix first" UIs.
If the report is still in progress, returns 202 with status information. If the report belongs to another user, returns 404.
/reports/:id/statusCheck analysis progress for a running or completed report.
json{
"data": {
"reportId": "acae9782-...",
"scope": "full",
"status": "analyzing",
"progress": 65,
"steps": {
"fetch": "completed",
"content_analysis": "in_progress",
"crawlability_check": "pending",
"ai_analysis": "pending"
}
}
}Status values: queued → analyzing → completed | failed
/reportsList your analysis reports with pagination and filtering.
Query parameters:
| Param | Default | Description |
|---|---|---|
| page | 1 | Page number |
| limit | 20 | Items per page (max 100) |
| status | - | Filter: "completed", "failed", "analyzing" |
| since | - | ISO date - reports created after this time |
| url | - | Filter by analyzed URL (partial match) |
json{
"data": {
"reports": [
{
"reportId": "acae9782-...",
"url": "https://example.com",
"status": "completed",
"scores": { "overall": 78, "content": 71, "crawlability": 85 },
"createdAt": "2026-03-17T06:00:00Z"
}
],
"pagination": { "page": 1, "limit": 20, "total": 47, "totalPages": 3 }
}
}/usageCheck your current billing period usage.
json{
"data": {
"plan": "pro",
"billingPeriod": {
"start": "2026-03-01T00:00:00Z",
"end": "2026-03-31T23:59:59Z"
},
"reports": { "used": 142, "limit": 500, "remaining": 358 },
"apiCredits": { "used": 87, "limit": 200, "remaining": 113 }
}
}/usage/dailyDaily breakdown of API calls and reports for the current month.
json{
"data": {
"days": [
{ "date": "2026-03-01", "apiCalls": 12, "reports": 5 },
{ "date": "2026-03-02", "apiCalls": 8, "reports": 3 },
{ "date": "2026-03-03", "apiCalls": 23, "reports": 11 }
]
}
}Only days with activity are included. Days with zero usage are omitted.
/pingHealth check endpoint. No authentication required.
json{
"data": { "status": "ok", "version": "1.0", "timestamp": "2026-03-17T06:00:00Z" }
}Webhooks
Register webhook URLs to receive notifications when analyses complete - no polling required. Manage webhooks via the CRUD endpoints below, and verify payloads with the signing secret returned at creation.
Webhook payload format:
json{
"event": "report.completed",
"reportId": "acae9782-...",
"url": "https://example.com/page",
"scope": "full",
"scores": { "overall": 78, "content": 71, "crawlability": 85 },
"reportUrl": "https://genready.ai/api/v1/reports/acae9782-...",
"timestamp": "2026-03-17T06:01:30Z"
}Signature verification:
Every webhook includes an X-GenReady-Signature header with an HMAC-SHA256 signature. Verify it using the secret returned when you created the webhook.
javascriptconst crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(`sha256=${expected}`),
Buffer.from(signature)
);
}Retry behavior: Failed deliveries are retried 3 times with exponential backoff (5s, 30s, 300s). Webhook URLs must be HTTPS.
/webhooksCreate a new webhook. The signing secret is returned only once - store it securely.
Request body:
json{
"url": "https://your-app.com/hooks/genready",
"events": ["report.completed"]
}Response (201):
json{
"data": {
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"apiKeyId": "key-id-...",
"url": "https://your-app.com/hooks/genready",
"secret": "whsec_abc123...",
"events": ["report.completed"],
"active": true,
"createdAt": "2026-03-17T06:00:00Z"
}
}The secret is shown only at creation. You'll need it to verify webhook signatures.
/webhooksList all your webhooks.
json{
"data": {
"webhooks": [
{
"id": "d290f1ee-...",
"apiKeyId": "key-id-...",
"url": "https://your-app.com/hooks/genready",
"secret": "whsec_****",
"events": ["report.completed"],
"active": true,
"failCount": 0,
"lastDeliveryAt": "2026-03-17T06:01:30Z",
"lastDeliveryStatus": 200,
"createdAt": "2026-03-17T06:00:00Z",
"updatedAt": "2026-03-17T06:00:00Z"
}
]
}
}The secret is always masked (whsec_****) - it's only shown once at creation.
/webhooks/:idUpdate a webhook's URL, events, or active status.
Request body (all fields optional):
json{
"url": "https://your-app.com/hooks/new-endpoint",
"events": ["report.completed"],
"active": false
}Response (200):
json{
"data": {
"id": "d290f1ee-...",
"apiKeyId": "key-id-...",
"url": "https://your-app.com/hooks/new-endpoint",
"secret": "whsec_****",
"events": ["report.completed"],
"active": false,
"failCount": 0,
"updatedAt": "2026-03-17T07:00:00Z"
}
}/webhooks/:idDelete a webhook permanently.
json{
"data": { "deleted": true }
}/webhooks/:id/deliveriesView delivery history for a webhook (paginated).
Query parameters:
| Param | Default | Description |
|---|---|---|
| page | 1 | Page number |
| limit | 20 | Items per page (max 100) |
json{
"data": {
"deliveries": [
{
"id": "del-uuid-...",
"event": "report.completed",
"reportId": "acae9782-...",
"statusCode": 200,
"responseBody": "OK",
"attempt": 1,
"durationMs": 142,
"deliveredAt": "2026-03-17T06:01:30Z"
}
],
"pagination": { "page": 1, "limit": 20, "total": 5, "totalPages": 1 }
}
}/webhooks/:id/testSend a test webhook payload to verify your endpoint.
json{
"data": {
"success": true,
"statusCode": 200,
"durationMs": 87,
"responseBody": "OK"
}
}Rate Limits
All API key users share the same rate limit: 60 requests per minute.
| Limit | Value |
|---|---|
| Requests per minute | 60 |
| Rate limit window | 1 minute (sliding) |
Higher rate limits for Business and Enterprise plans are planned for the future. API credit quotas vary by plan - check GET /usage for your current limits.
Rate limit headers:
Every API response includes these headers:
| Header | Description |
|---|---|
| RateLimit-Limit | Maximum requests per minute |
| RateLimit-Remaining | Requests remaining in current window |
| RateLimit-Reset | Seconds until the window resets |
When rate limited, you'll receive a 429 response. Wait 60 seconds before retrying.
Error Handling
Error envelope:
json{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Please slow down.",
"details": { "retryAfterSeconds": 60 }
},
"meta": {
"requestId": "req_abc123",
"timestamp": "2026-03-17T06:00:00Z"
}
}Common error codes:
| Code | Status | Description |
|---|---|---|
| UNAUTHORIZED | 401 | Missing or invalid API key |
| INVALID_URL | 400 | URL failed validation (malformed, private IP, etc.) |
| INVALID_SCOPE | 400 | Scope must be full, content, or crawlability |
| INVALID_ID | 400 | Report/webhook ID is not a valid UUID |
| VALIDATION_ERROR | 400 | Request body failed validation |
| CREDITS_EXHAUSTED | 402 | Monthly API credits used up |
| NOT_FOUND | 404 | Report or webhook not found |
| RATE_LIMITED | 429 | Too many requests - check rate limit headers |
| INTERNAL_ERROR | 500 | Something went wrong on our end |
Every error response includes a meta.requestId - include it when contacting support.
Code Examples
# Synchronous analysis (simplest)
curl -X POST https://genready.ai/api/v1/analyze \
-H "Authorization: Bearer gr_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "options": {"waitForCompletion": true}}'
# Async analysis
curl -X POST https://genready.ai/api/v1/analyze \
-H "Authorization: Bearer gr_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}'
# Poll status
curl https://genready.ai/api/v1/reports/REPORT_ID/status \
-H "Authorization: Bearer gr_live_xxxxx"
# Get report
curl https://genready.ai/api/v1/reports/REPORT_ID \
-H "Authorization: Bearer gr_live_xxxxx"
# Content-only analysis
curl -X POST https://genready.ai/api/v1/analyze \
-H "Authorization: Bearer gr_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "scope": "content"}'API Playground
Try the API
Test the analyze endpoint directly from your browser.
Stored in localStorage only - never sent to our server.
curl -X POST https://genready.ai/api/v1/analyze \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com","scope":"full","options":{"waitForCompletion":true}}'SDKs
Official Python and Node.js SDKs are in development.
In the meantime, the REST API works great with any HTTP library. See the code examples above for quick integration patterns.
