name: grammarly-upgrade-migration description: 'Upgrade and migration guidance for Grammarly API version changes. Use when migrating
between Grammarly API versions or updating endpoint references.
' allowed-tools: Read, Write, Edit, Bash(curl:*), Grep version: 1.0.0 license: MIT author: Jeremy Longshore jeremy@intentsolutions.io tags:
- saas
- grammarly
- writing compatibility: Designed for Claude Code
Grammarly Upgrade & Migration
Overview
Grammarly exposes versioned APIs for writing analysis, AI detection, and plagiarism checking. Each API family versions independently — the Writing Score API may be on v2 while AI Detection remains on v1. Tracking these versions matters because Grammarly deprecates older API versions on a fixed timeline, and the response schemas differ significantly between versions (score breakdowns, suggestion categories, and confidence thresholds all change). Missing a version cutover means silent failures or degraded writing feedback quality.
Version Detection
const GRAMMARLY_BASE = "https://api.grammarly.com/ecosystem/api";
interface GrammarlyVersionMap {
scores: string;
aiDetection: string;
plagiarism: string;
latestAvailable: Record<string, string>;
}
async function detectGrammarlyVersions(apiKey: string): Promise<GrammarlyVersionMap> {
const endpoints = {
scores: `${GRAMMARLY_BASE}/v2/scores`,
aiDetection: `${GRAMMARLY_BASE}/v1/ai-detection`,
plagiarism: `${GRAMMARLY_BASE}/v1/plagiarism`,
};
const versions: Record<string, string> = {};
for (const [api, url] of Object.entries(endpoints)) {
const res = await fetch(url, {
method: "HEAD",
headers: { Authorization: `Bearer ${apiKey}` },
});
versions[api] = res.headers.get("x-grammarly-api-version") ?? "unknown";
const deprecated = res.headers.get("x-grammarly-deprecated");
if (deprecated) console.warn(`${api} endpoint deprecated: ${deprecated}`);
}
return { scores: "v2", aiDetection: "v1", plagiarism: "v1", latestAvailable: versions };
}
Migration Checklist
- Audit codebase for all
api.grammarly.comendpoint references - Map each endpoint to its current API version (v1 vs. v2)
- Check if Writing Score v2 response includes new sub-score categories
- Verify AI Detection confidence threshold changes between versions
- Update plagiarism check response parser if source attribution format changed
- Test OAuth token flow — scope names may differ between API versions
- Validate suggestion category enums (clarity, engagement, delivery, correctness)
- Update error handling for new rate limit headers in v2
- Check if text submission size limits changed between versions
- Run A/B comparison of v1 vs. v2 scores on sample corpus to verify parity
Schema Migration
// Grammarly scores response changed from flat scores to structured breakdown
interface OldScoreResponse {
overall: number;
correctness: number;
clarity: number;
engagement: number;
delivery: number;
}
interface NewScoreResponse {
overall: { score: number; label: string };
dimensions: Array<{
name: "correctness" | "clarity" | "engagement" | "delivery";
score: number;
label: string;
suggestions: Array<{ category: string; message: string; severity: "critical" | "warning" | "info" }>;
}>;
metadata: { wordCount: number; readingTime: number; apiVersion: string };
}
function migrateScoreResponse(old: OldScoreResponse): NewScoreResponse {
const toLabel = (s: number) => (s >= 80 ? "Strong" : s >= 60 ? "Good" : "Needs work");
return {
overall: { score: old.overall, label: toLabel(old.overall) },
dimensions: (["correctness", "clarity", "engagement", "delivery"] as const).map((name) => ({
name,
score: old[name],
label: toLabel(old[name]),
suggestions: [],
})),
metadata: { wordCount: 0, readingTime: 0, apiVersion: "v1-migrated" },
};
}
Rollback Strategy
class GrammarlyClient {
private versionMap: Record<string, string> = { scores: "v2", aiDetection: "v1", plagiarism: "v1" };
private fallbackMap: Record<string, string> = { scores: "v1", aiDetection: "v1", plagiarism: "v1" };
constructor(private apiKey: string) {}
async checkText(text: string, api: "scores" | "aiDetection" | "plagiarism"): Promise<any> {
const version = this.versionMap[api];
try {
const res = await fetch(`https://api.grammarly.com/ecosystem/api/${version}/${api}`, {
method: "POST",
headers: { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json" },
body: JSON.stringify({ text }),
});
if (!res.ok) throw new Error(`Grammarly ${api} ${res.status}`);
return await res.json();
} catch (err) {
const fallback = this.fallbackMap[api];
if (fallback !== version) {
console.warn(`Falling back ${api} from ${version} to ${fallback}`);
this.versionMap[api] = fallback;
return this.checkText(text, api);
}
throw err;
}
}
}
Error Handling
| Migration Issue | Symptom | Fix |
|---|---|---|
| Score dimension renamed | Response missing engagement, has tone instead | Update parser to map new dimension names to internal schema |
| API version sunset | 410 Gone on v1 scores endpoint | Migrate all calls to v2 and update response parsing |
| OAuth scope mismatch | 403 after upgrading API version | Re-authorize with scopes matching new version requirements |
| Text length limit changed | 413 Payload Too Large on previously working requests | Chunk text into smaller segments per new version limits |
| Rate limit structure changed | 429 without X-RateLimit-Reset header | Switch to Retry-After header or implement exponential backoff |
Resources
Next Steps
For CI pipeline integration, see grammarly-ci-integration.