POST /api/analyze
Analyze Terms of Service text with AI to get flagged clauses, attention guidance, and full extraction.
Endpoint
POST /api/analyze
Analyzes Terms of Service text and returns structured review guidance including a summary, flagged clauses, full extraction breakdown, and a backward-compatible riskScore ranking signal.
Authentication
Include your Clerk session token in the Authorization header:
Authorization: Bearer <your_session_token>
Request Body
{
"text": "Terms of Service\n\n1. Acceptance of Terms\nBy accessing...",
"sourceUrl": "https://example.com/terms-of-service",
"domain": "example.com"
}
| Field | Type | Required | Description |
|---|---|---|---|
text | string | One of text or url | The Terms of Service text to analyze |
url | string | One of text or url | URL to extract text from (uses /api/extract internally) |
sourceUrl | string | No | Original public URL for already-extracted text; used as metadata and never fetched |
domain | string | No | Domain name (e.g., "example.com"). Used for display and grouping in scan history. |
Provide exactly one analysis input: text or url. When you already extracted text from a URL, send the text with sourceUrl so the origin is retained without fetching it again.
Response
{
"riskScore": 7,
"riskLevel": "red",
"summary": "This Terms of Service grants the company broad rights to user data and content. It includes mandatory arbitration with a class action waiver, unilateral right to modify terms, and a liability cap limited to the amount paid in the last 12 months.",
"flags": [
{
"category": "Data Privacy",
"severity": "high",
"text": "We may share your personal information with third-party partners for marketing and advertising purposes.",
"explanation": "Your personal data can be shared with third parties for advertising without additional consent.",
"riskScore": 8
},
{
"category": "Arbitration",
"severity": "high",
"text": "You agree to resolve any disputes through binding arbitration and waive your right to participate in a class action lawsuit.",
"explanation": "You give up the right to sue in court or join a class action. All disputes go through private arbitration.",
"riskScore": 8
},
{
"category": "Billing",
"severity": "medium",
"text": "We reserve the right to modify pricing at any time with 30 days notice.",
"explanation": "The company can raise your subscription price with just 30 days notice.",
"riskScore": 5
}
],
"fullExtraction": [
{
"title": "Data Privacy",
"keyPoints": [
{
"title": "Third-party data sharing",
"detail": "Collects name, email, IP address, device info, and usage data. Shares data with third-party advertisers.",
"severity": "warning"
},
{
"title": "Data deletion",
"detail": "Users can request data deletion via email.",
"severity": "info"
}
]
}
]
}
Response Fields
Top Level
| Field | Type | Description | |
|---|---|---|---|
riskScore | number | Backward-compatible internal ranking signal from 1-10; do not treat as legal advice or an authoritative legal risk determination | |
riskLevel | string | "green", "yellow", or "red" | |
summary | string | AI-generated overview of the document | |
flags | Array | List of flagged clauses with details | |
fullExtraction | Array \ | null | Structured breakdown by section |
usage | object \ | null | Rate limit and credit info (only for authenticated users) |
Flags Array
Each item in flags represents a clause that raised a concern. riskScore is retained for API compatibility and internal ranking only:
| Field | Type | Description | ||
|---|---|---|---|---|
category | string | Topic area (Data Privacy, Arbitration, Liability, Content & IP, Billing, Account) | ||
severity | "low" \ | "medium" \ | "high" | How significant the concern is |
text | string | Exact text from the original document | ||
explanation | string | Plain-English description of what the clause means | ||
riskScore | number | Backward-compatible internal ranking signal for this clause (1-10) |
Full Extraction Array
Each item in fullExtraction represents a section of the document:
| Field | Type | Description |
|---|---|---|
title | string | Section name |
keyPoints | Array | Key points, each with title, detail, quote, and severity |
LLM Pipeline
The analysis uses a multi-model pipeline with automatic fallback:
| Priority | Model | Provider |
|---|---|---|
| 1 | google/gemini-2.5-flash-lite | OpenRouter |
| 2 | xiaomi/mimo-v2.5 | OpenRouter |
| 3 | google/gemma-4-31b-it:free | OpenRouter |
| 4 | minimax/minimax-m3 | OpenRouter |
| 5 | gemini-2.5-flash-lite | Google Generative AI (direct) |
| 6 | Hardcoded fallback | Returns a neutral score with a "review manually" message |
If all models fail, the API returns neutral review guidance with a single "Unable to analyze" flag and a backward-compatible neutral riskScore.
Code Examples
JavaScript (fetch)
// First, extract the text
const extractRes = await fetch("https://themys.ca/api/extract", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_SESSION_TOKEN",
},
body: JSON.stringify({
url: "https://example.com/terms-of-service",
}),
});
const { text } = await extractRes.json();
// Then, analyze it
const analyzeRes = await fetch("https://themys.ca/api/analyze", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_SESSION_TOKEN",
},
body: JSON.stringify({
text,
sourceUrl: "https://example.com/terms-of-service",
domain: "example.com",
}),
});
const result = await analyzeRes.json();
console.log(`Internal ranking signal: ${result.riskScore}`);
console.log(`Summary: ${result.summary}`);
console.log(`Flags: ${result.flags.length} clauses flagged`);
cURL
# Step 1: Extract text
EXTRACT=$(curl -s -X POST https://themys.ca/api/extract \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_SESSION_TOKEN" \
-d '{"url": "https://example.com/terms-of-service"}')
TEXT=$(echo $EXTRACT | jq -r '.text')
# Step 2: Analyze text
curl -X POST https://themys.ca/api/analyze \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_SESSION_TOKEN" \
-d "{\"text\": \"$TEXT\", \"sourceUrl\": \"https://example.com/terms-of-service\", \"domain\": \"example.com\"}"
Error Responses
Missing Input (400)
{
"error": "Provide a URL or pasted terms text"
}
Analysis Failed (500)
{
"error": "Internal server error"
}
This is rare. Retry after a short wait. If it persists, the LLM providers may be experiencing outages.
Rate Limited (402)
{
"error": "Daily scan limit reached",
"upgradeRequired": true,
"tier": "free",
"scansRemaining": 0,
"scansLimit": 5
}
You've exceeded your plan's daily scan limit. Purchase credits or upgrade to continue.
Rate Limits
Each analysis request consumes one scan from your daily limit, or one credit if you've exceeded it.
| Plan | Daily Scans |
|---|---|
| Free | 5 |
| Base | 25 |
| Advanced | 100 |
Was this page helpful?