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 90s).
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.
{
"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:
{
"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):
{
"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 90s 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.
While the analysis runs, the answer is 202 with status queued or analyzing. If it failed or was cancelled, the answer is 422 with ANALYSIS_FAILED or ANALYSIS_CANCELLED. Stop polling then.
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.
{
"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."
}
]
}
}{
"data": {
"reportId": "b1234-...",
"url": "https://example.com/page",
"scope": "content",
"status": "completed",
"createdAt": "2026-03-17T06:00:00Z",
"completedAt": "2026-03-17T06:00:22Z",
"scores": {
"content": 62,
"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
},
"fluff_penalty": {
"key": "fluff_penalty",
"name": "Unnecessary Words",
"description": "Detects filler words and bloat...",
"score": 7.1,
"verdict": "warn",
"maxScore": 10,
"category": "content",
"subcategory": "helpful_content",
"weight": 1
},
"tone_alignment": {
"key": "tone_alignment",
"name": "Writing Style Match",
"description": "Analyzes tone consistency...",
"score": 8.5,
"verdict": "pass",
"maxScore": 10,
"category": "content",
"subcategory": "uniqueness",
"weight": 1,
"dominantTone": "informational"
},
"...": "additional content metrics omitted"
}
},
"fixChecklist": [
{
"severity": "high",
"category": "content",
"metric": "Facts & Details",
"problem": "Only 12 entities found in 1,850 words (0.6% density).",
"fix": "Replace vague claims with named sources and data."
}
],
"recommendations": [ ... ]
}
}Content metrics include: entity_ratio, fluff_penalty, outbound_citations, semantic_cohesion, heading_cleanliness, table_list_density, json_ld_schema, metadata_freshness, originality, tone_alignment, author_bonus.
{
"data": {
"reportId": "c5678-...",
"url": "https://example.com/page",
"scope": "crawlability",
"status": "completed",
"createdAt": "2026-03-17T06:00:00Z",
"completedAt": "2026-03-17T06:00:12Z",
"scores": {
"crawlability": 85,
"details": {
"https": {
"key": "https",
"name": "HTTPS Security",
"description": "Verifies the page is served over HTTPS...",
"score": 18,
"verdict": "pass",
"maxScore": 18,
"category": "crawlability",
"subcategory": null,
"weight": null
},
"waf_detection": {
"key": "waf_detection",
"name": "Firewall",
"description": "Detects WAF blocking AI crawlers...",
"score": 15,
"verdict": "pass",
"maxScore": 15,
"category": "crawlability",
"subcategory": null,
"weight": null,
"wafDetected": false
},
"ttfb": {
"key": "ttfb",
"name": "Loading Speed",
"description": "Time to first byte measurement...",
"score": 10,
"verdict": "pass",
"maxScore": 10,
"category": "crawlability",
"subcategory": null,
"weight": null,
"ms": 187
},
"...": "additional crawlability metrics omitted"
}
},
"fixChecklist": [],
"recommendations": [ ... ]
}
}Crawlability metrics include: https, robots_txt, waf_detection, ttfb, xml_sitemap, ai_meta_directives, html_size, paywall, llm_txt, alt_text, internal_links.
Fix checklist:
The fixChecklist array contains actionable items sorted by severity: critical → high → medium → low. Each item carries metric, category, severity, problem and fix. Use this to build "what to fix first" UIs.
severity: "critical" is reserved for a problem that caps the page score no matter how good everything else is: a firewall blocking AI bots, a robots.txt rule blocking all crawlers, missing HTTPS, or a paywall. Content findings never report critical, so a critical item always means access is broken and should be fixed first.
metric is stable and shared across scopes - see Fix Lists.
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.
{
"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) |
{
"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.
{
"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.
{
"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.
{
"data": { "status": "ok", "version": "1.0", "timestamp": "2026-03-17T06:00:00Z" }
}Fix Lists
GenReady produces one kind of finding at two scopes. Both describe the same checks in the same vocabulary, so a consumer that reads one can read the other.
| Scope | Where | Ranks by |
|---|---|---|
| Page | fixChecklist on GET /reports/:id | Severity |
| Site | actions on GET /domains/:id/fix-plan | Severity, then share of the site affected |
Shared fields
severity, category, metric, problem and fix are identical in name and meaning at both scopes. metric is the join key: a robots.txt block is "AI Access Rules" in both, so findings about the same check can be correlated across scopes without parsing prose.
Site-scope-only fields
A site plan adds impactTier, affectedPageCount, confidence and verifyUrls. These describe prevalence across a site and have no meaning for a single page, so they are absent rather than filled with placeholder values. It also adds the discovery and agent_readiness categories, which come from site-wide checks such as llms.txt and agent standards.
Reading confidence
Site analysis samples pages per section rather than scanning every URL, so affectedPageCount can be an estimate. When confidence is not "exact", treat the count as extrapolated from a sample, not as a measured fact.
Ordering
actions arrives sorted by severity, then by share of the site affected. Work it top down; do not re-sort it to find the first thing worth doing.
Site endpoints need the domain:read scope
Keys do not carry it by default, because a site plan covers every page of a site rather than one report. Check "Read site data" when you create the key. An older key cannot be upgraded - create a new one. Calling without the scope returns 403 FORBIDDEN.
/api/v1/domainsList the domains this account monitors.
curl https://genready.ai/api/v1/domains \
-H "Authorization: Bearer $GENREADY_API_KEY"{
"data": {
"domains": [
{
"id": "11111111-...",
"hostname": "example.com",
"displayName": "Example",
"verificationStatus": "verified",
"createdAt": "2026-08-14T10:22:03.000Z",
"fixPlanUrl": "/api/v1/domains/11111111-.../fix-plan"
}
]
},
"meta": { "requestId": "req_...", "timestamp": "2026-09-03T09:00:00.000Z" }
}/api/v1/domains/:id/fix-planThe fix plan from the domain's most recent completed analysis.
curl https://genready.ai/api/v1/domains/11111111-.../fix-plan \
-H "Authorization: Bearer $GENREADY_API_KEY"{
"data": {
"domainId": "11111111-...",
"siteAnalysisId": "22222222-...",
"generatedAt": "2026-09-03T09:00:00.000Z",
"actions": [
{
"id": "issue-links-to-sources",
"source": "issue",
"metric": "Links to Sources",
"title": "Fix Links to Sources (~12 pages)",
"severity": "high",
"impactTier": "medium",
"category": "content",
"affectedPageCount": 12,
"confidence": "estimated",
"problem": "...",
"fix": "...",
"verifyUrls": ["https://example.com/blog/a"]
}
]
},
"meta": { "requestId": "req_...", "timestamp": "2026-09-03T09:00:00.000Z" }
}A domain with no completed analysis returns 404 NOT_FOUND, as does a domain owned by another account. Run one from the dashboard, or start one over the API with POST /domains/:id/site-analyses.
/api/v1/site-analyses/:id/fix-planThe plan for one specific run, by run id.
Same response shape. Use it when you recorded a siteAnalysisId and want that run back. Plans are recomputed on every request and none are stored, so this is the only way to ask about a particular run.
curl https://genready.ai/api/v1/site-analyses/22222222-.../fix-plan \
-H "Authorization: Bearer $GENREADY_API_KEY"Verify and Fix
These endpoints close the loop an agent runs: scan, fix, then prove the fix against the live site. Every finding has a stable id, so the same problem keeps the same id from one scan to the next. The same operations are available as MCP tools; see the MCP docs.
/api/v1/domains/:id/site-analysesScan a whole verified site without a person in the loop.
Discovers pages, picks the highest-impact ones (homepage first, utility pages last) and scans them. Each page scanned uses one API credit, so cap the run with curation.maxPages. Needs the site:analyze scope, which is off by default. Returns 202 with a siteAnalysisId; read the result from GET /site-analyses/:id/fix-plan.
curl -X POST https://genready.ai/api/v1/domains/11111111-.../site-analyses \
-H "Authorization: Bearer $GENREADY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"curation": {"mode": "auto", "maxPages": 25}}'/api/v1/findings/verifyCheck whether fixes landed, with one fetch per finding instead of a paid re-scan.
Name exactly one of reportId, domainId or siteAnalysisId, and optionally the findingIds to check. Each result is verified, still_failing or unverifiable (with a reason saying what would prove it). Included with Pro and Agency, up to 500 checks a day, and it uses no credits.
curl -X POST https://genready.ai/api/v1/findings/verify \
-H "Authorization: Bearer $GENREADY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"reportId": "acae9782-..."}'/api/v1/findings/:idRecord what you did about a finding.
Set status to fixed_pending after shipping a fix, ignored with a reason, or back to open. verified can't be set here: it is earned by calling POST /findings/verify after deploying.
curl -X PATCH https://genready.ai/api/v1/findings/robots.gptbot_blocked~a1b2c3d4e5f6a7b8 \
-H "Authorization: Bearer $GENREADY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "fixed_pending"}'/api/v1/artifacts/:kindGet a robots.txt, llms.txt or JSON-LD block to commit.
kind is robots_txt, llms_txt or jsonld. Pass reportId for a page, or domainId for a site (JSON-LD is per page). Anything GenReady didn't measure comes back as a TODO for a person, never a guess, and a generated robots.txt never widens crawler access. Included with Pro and Agency, no credits.
curl "https://genready.ai/api/v1/artifacts/robots_txt?domainId=11111111-..." \
-H "Authorization: Bearer $GENREADY_API_KEY"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.
| Event | When it fires |
|---|---|
| report.completed | An analysis finished. The default when you name no events. |
| report.failed | An analysis could not complete. |
| finding.regressed | A finding that verification proved fixed is failing again. Never fires on a first scan. |
Webhook payload format:
{
"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-Timestamp and an X-GenReady-Signature header. The signature is an HMAC-SHA256 of the timestamp, a dot and the raw request body, made with the secret returned when you created the webhook. Read the body as raw bytes, before any JSON parsing, and reject deliveries whose timestamp is more than 5 minutes old.
const crypto = require('crypto');
// rawBody: the request body exactly as received (a Buffer or string).
function verifyWebhook(rawBody, headers, secret) {
const timestamp = headers['x-genready-timestamp'];
const signature = headers['x-genready-signature'] || '';
const expected =
'sha256=' +
crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
return (
fresh &&
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))
);
}import hashlib, hmac, time
# raw_body: the request body exactly as received, as bytes.
def verify_webhook(raw_body: bytes, headers, secret: str) -> bool:
timestamp = headers["X-GenReady-Timestamp"]
signature = headers.get("X-GenReady-Signature", "")
signed = timestamp.encode() + b"." + raw_body
expected = "sha256=" + hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
fresh = abs(time.time() - int(timestamp)) < 300
return fresh and hmac.compare_digest(expected, signature)Retry behavior: A delivery that gets no 2xx response is retried 3 times, after about 30 seconds, 2 minutes and 10 minutes. Each retry carries a new timestamp and signature. Webhook URLs must be HTTPS.
/webhooksCreate a new webhook. The signing secret is returned only once - store it securely.
Request body:
{
"url": "https://your-app.com/hooks/genready",
"events": ["report.completed"]
}Response (201):
{
"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.
{
"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):
{
"url": "https://your-app.com/hooks/new-endpoint",
"events": ["report.completed"],
"active": false
}Response (200):
{
"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.
{
"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) |
{
"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.
{
"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:
{
"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"}'import os
import requests
# Keep the key out of your source. Set GENREADY_API_KEY in the environment.
API_KEY = os.environ["GENREADY_API_KEY"]
BASE = "https://genready.ai/api/v1"
headers = {"Authorization": f"Bearer {API_KEY}"}
# Simplest: synchronous analysis
resp = requests.post(f"{BASE}/analyze",
headers=headers,
json={
"url": "https://example.com",
"options": {"waitForCompletion": True}
})
report = resp.json()["data"]
print(f"AI Readiness Score: {report['scores']['overall']}/100")
# Check fix checklist
for item in report.get("fixChecklist", []):
print(f"[{item['severity']}] {item['metric']}: {item['problem']}")
print(f" Fix: {item['fix']}")// Keep the key server-side. Never ship it to a browser bundle.
const API_KEY = process.env.GENREADY_API_KEY;
const BASE = 'https://genready.ai/api/v1';
const headers = {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
};
// Synchronous - wait for completion
const resp = await fetch(`${BASE}/analyze`, {
method: 'POST',
headers,
body: JSON.stringify({
url: 'https://example.com',
options: { waitForCompletion: true }
})
});
const { data } = await resp.json();
console.log(`Score: ${data.scores.overall}/100`);
// Show fix checklist
data.fixChecklist?.forEach(item => {
console.log(`[${item.severity}] ${item.metric}: ${item.problem}`);
});// Keep the key server-side. Never ship it to a browser bundle.
const API_KEY = process.env.GENREADY_API_KEY;
const BASE = 'https://genready.ai/api/v1';
const headers = {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
};
// Submit analysis (async)
const analyzeResp = await fetch(`${BASE}/analyze`, {
method: 'POST',
headers,
body: JSON.stringify({ url: 'https://example.com' })
});
const { data: { reportId } } = await analyzeResp.json();
// Poll until complete
let status;
do {
await new Promise(r => setTimeout(r, 3000));
const statusResp = await fetch(
`${BASE}/reports/${reportId}/status`,
{ headers }
);
status = (await statusResp.json()).data.status;
} while (status !== 'completed' && status !== 'failed');
// Get report
const reportResp = await fetch(
`${BASE}/reports/${reportId}`,
{ headers }
);
const report = await reportResp.json();
console.log(`AI Readiness Score: ${report.data.scores.overall}/100`);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.
