Skip to content
    HomeAPI Documentation

    GenReady API

    v1

    Integrate AI readiness analysis into your applications, CI/CD pipelines, and workflows.

    Quick Start

    The fastest way to get a report - a single API call with waitForCompletion.

    1Get your API key

    Create an API key from your dashboard. Your key will look like gr_live_Ab3xY9... - copy it immediately, it's only shown once.

    Go to API Keys
    2Analyze a URL

    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}}'
    3Get your results

    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_xxxxx

    Create and manage your API keys at /api-keys. Keys are shown once at creation - store them securely.

    Base URL

    https://genready.ai/api/v1

    All endpoint paths below are relative to this base URL.

    Endpoints

    POST/analyze

    Submit a URL for AI readiness analysis.

    Request body:

    {
      "url": "https://example.com/page",
      "scope": "full",
      "options": {
        "waitForCompletion": false
      }
    }

    Scope options:

    ScopeWhat runsCredit costEst. time
    "full" (default)Content + Crawlability + AI analysis1 credit~25-40s
    "content"Content quality only1 credit~15-25s
    "crawlability"Technical crawlability only1 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:

    StatusCodeReason
    400INVALID_URLURL failed validation
    400INVALID_SCOPEScope must be "full", "content", or "crawlability"
    402CREDITS_EXHAUSTEDMonthly API credits used up
    429RATE_LIMITEDToo many requests per minute
    GET/reports/:id

    Retrieve 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.

    GET/reports/:id/status

    Check 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

    GET/reports

    List your analysis reports with pagination and filtering.

    Query parameters:

    ParamDefaultDescription
    page1Page number
    limit20Items 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 }
      }
    }
    GET/usage

    Check 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 }
      }
    }
    GET/usage/daily

    Daily 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.

    GET/ping

    Health 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.

    ScopeWhereRanks by
    PagefixChecklist on GET /reports/:idSeverity
    Siteactions on GET /domains/:id/fix-planSeverity, 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.

    GET/api/v1/domains

    List 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" }
    }
    GET/api/v1/domains/:id/fix-plan

    The 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.

    GET/api/v1/site-analyses/:id/fix-plan

    The 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.

    POST/api/v1/domains/:id/site-analyses

    Scan 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}}'
    POST/api/v1/findings/verify

    Check 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-..."}'
    PATCH/api/v1/findings/:id

    Record 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"}'
    GET/api/v1/artifacts/:kind

    Get 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.

    EventWhen it fires
    report.completedAn analysis finished. The default when you name no events.
    report.failedAn analysis could not complete.
    finding.regressedA 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.

    POST/webhooks

    Create 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.

    GET/webhooks

    List 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.

    PATCH/webhooks/:id

    Update 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"
      }
    }
    DELETE/webhooks/:id

    Delete a webhook permanently.

    {
      "data": { "deleted": true }
    }
    GET/webhooks/:id/deliveries

    View delivery history for a webhook (paginated).

    Query parameters:

    ParamDefaultDescription
    page1Page number
    limit20Items 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 }
      }
    }
    POST/webhooks/:id/test

    Send 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.

    LimitValue
    Requests per minute60
    Rate limit window1 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:

    HeaderDescription
    RateLimit-LimitMaximum requests per minute
    RateLimit-RemainingRequests remaining in current window
    RateLimit-ResetSeconds 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:

    CodeStatusDescription
    UNAUTHORIZED401Missing or invalid API key
    INVALID_URL400URL failed validation (malformed, private IP, etc.)
    INVALID_SCOPE400Scope must be full, content, or crawlability
    INVALID_ID400Report/webhook ID is not a valid UUID
    VALIDATION_ERROR400Request body failed validation
    CREDITS_EXHAUSTED402Monthly API credits used up
    NOT_FOUND404Report or webhook not found
    RATE_LIMITED429Too many requests - check rate limit headers
    INTERNAL_ERROR500Something 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

    Coming Soon

    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.