API Documentation

Score domains for trust and reliability programmatically.

Authentication

All API requests require a Bearer token. Get your API key from the dashboard under API Keys.

Authorization: Bearer lrn_live_abc123...

Base URL

https://lirnoa.com/api/v1

Score a Domain

GET   /score?domain={domain}

Query Parameters

ParameterTypeDescription
domain requiredstringDomain to score (e.g. example.com)
icp_idintegerID of one of your ICPs (see GET /icps). When provided, fit + verdict are computed against your ICP. When omitted, the default ICP applies.
forcebooleanSet to true to bypass cache and re-score
timeoutintegerTimeout in ms (1000-55000). Returns 202 if scoring exceeds this limit — retry shortly for the cached result.
referrerstringAttribution tag (e.g. clay). Max 50 chars.

Response Headers

HeaderDescription
X-Lirnoa-CacheHIT or MISS
X-Lirnoa-LatencyProcessing time in ms
X-RateLimit-LimitHourly rate limit
X-RateLimit-RemainingRequests remaining

Example Response

{ "domain": "example.com", "score": 82, "risk_level": "low", "verdict": "sales_ready", "verdict_reason": "Strong ICP match with high trust score (82/100) — route to sales.", "verdict_components": { "trust": "high", "fit": "strong" }, "recommendation": "Route to sales — strong fit", "confidence": 0.85, "cached": false, "dns": { "domain_age_days": 10950, "has_mx": true, "has_spf": true, "has_dkim": true, "has_dmarc": true, "ssl_valid": true, "is_blacklisted": false }, "web_presence": { "has_website": true, "has_contact_info": true, "has_social_links": true, "og_tags_present": true }, "business_verification": { "google_business_found": true, "companies_house_found": false, "kvk_registry_found": false, "linkedin_found": true }, "company": { "name": "Example Inc.", "industry": "Technology", "country": "US", "city": "San Francisco", "employee_range": "51-200", "domain_age_years": 30.0, "google_rating": 4.5, "google_review_count": 120 }, "risk_dimensions": { ... }, "quick_signals": [ { "key": "domain_age", "label": "Domain age", "value": "30 years", "sentiment": "positive" }, { "key": "linkedin", "label": "LinkedIn", "value": "Found — 51-200 employees", "sentiment": "positive" }, { "key": "company_registry", "label": "Company registry", "value": "Not found", "sentiment": "neutral" }, { "key": "google_business", "label": "Google Business", "value": "Found — 4.5★, 120 reviews", "sentiment": "positive" }, { "key": "web_presence", "label": "Web presence", "value": "SSL valid, contact page, social links", "sentiment": "positive" }, { "key": "third_party_mentions", "label": "Third-party mentions", "value": "Found on G2, Capterra", "sentiment": "positive" } ], "fit": { "score": 78, "label": "Strong match", "reasoning": "Matches: 50+ employees, established (3+ years)", "icp_label": "Default ICP" }, "analyst_assessment": { "summary": "Example Inc. is an established technology company.", "company_summary": "Example Inc. is an established technology company. They operate globally...", "risk_level": "Low", "recommendation": "Route to sales — strong fit", "recommendation_score": 1, "quick_signals": ["✅ Domain age: 30 years", "✅ LinkedIn found"] }, "scored_at": "2026-03-18T12:00:00.000Z", "latency_ms": 8500 }

Verdict triage

The verdict field is the actionable triage decision for an inbound signup. One of:

verdict_components exposes the two ingredients (trust: high/medium/low, fit: strong/possible/poor) so consumers can filter beyond the headline. verdict_reason is a short deterministic sentence explaining the verdict.

Standalone calls (no fit_criteria) are evaluated against a default ICP (50+ employees AND established / recently funded / actively hiring). The fit.icp_label field reads "Default ICP" in this case.

Quick signals

The top-level quick_signals array returns up to 8 typed signals: domain_age, linkedin, company_registry, google_business, web_presence, third_party_mentions, plus hiring_signals and tech_stack when commercial-activity scoring runs. Each entry has { key, label, value, sentiment } with sentiment in positive | negative | neutral | warning.

The legacy analyst_assessment.quick_signals array (free-form emoji-prefixed strings) is preserved for backward compatibility but is deprecated. Prefer the structured top-level field.

202 Accepted (Timeout)

When using the timeout parameter and scoring takes longer than the specified limit:

{ "status": "scoring", "domain": "newdomain.com", "message": "Domain is being scored. Retry shortly.", "retry_after_seconds": 30 }

List your ICPs

GET   /icps

Returns the active ICPs in your workspace. Use any returned id as the icp_id query param when calling /score.

Example Response

{ "icps": [ { "id": 12, "name": "Series B SaaS", "raw_criteria": "B2B SaaS, 50-200 employees, US or UK, sales-led motion", "interpretation": "Targeting growth-stage B2B SaaS in US/UK with active outbound motion.", "created_at": "2026-04-12T09:30:00.000Z", "updated_at": "2026-04-25T14:10:00.000Z" } ] }

Scoring against an ICP

Pass icp_id to score a domain against your ICP instead of the default. The response's fit.icp_label will reflect the ICP name; verdict, verdict_components, and the analyst's Sales Potential paragraph are all computed against the ICP's criteria.

curl -H "Authorization: Bearer lrn_live_YOUR_KEY" \ "https://lirnoa.com/api/v1/score?domain=example.com&icp_id=12"

Errors: 400 INVALID_ICP_ID if icp_id isn't a number; 404 ICP_NOT_FOUND if the ID doesn't belong to your workspace.

Code Examples

cURL

curl -H "Authorization: Bearer lrn_live_YOUR_KEY" \ "https://lirnoa.com/api/v1/score?domain=example.com"

JavaScript (Node.js)

const res = await fetch( 'https://lirnoa.com/api/v1/score?domain=example.com', { headers: { Authorization: 'Bearer lrn_live_YOUR_KEY' } } ); const data = await res.json(); console.log(data.score, data.risk_level);

Python

import requests res = requests.get( 'https://lirnoa.com/api/v1/score', params={'domain': 'example.com'}, headers={'Authorization': 'Bearer lrn_live_YOUR_KEY'} ) data = res.json() print(data['score'], data['risk_level'])

Rate Limits

PlanPer SecondPer HourMonthly Credits
Free ($0/mo)16025
Starter ($29/mo)3180200
Pro ($79/mo)5300750

Rate-limited requests return 429 Too Many Requests with a Retry-After header. Quota-exceeded requests return 402 Payment Required.

Errors

{ "error": { "code": "INVALID_DOMAIN", "message": "The provided domain is not valid." } }
CodeHTTPMeaning
MISSING_DOMAIN400No domain parameter provided
INVALID_DOMAIN400Domain could not be parsed
UNAUTHORIZED401Missing or invalid API key
RATE_LIMITED429Rate limit exceeded
QUOTA_EXCEEDED402Monthly quota exceeded
INTERNAL_ERROR500Server error