Agent Skills: Grammarly Upgrade & Migration

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/grammarly-upgrade-migration

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/grammarly-pack/skills/grammarly-upgrade-migration

Skill Files

Browse the full folder contents for grammarly-upgrade-migration.

Download Skill

Loading file tree…

plugins/saas-packs/grammarly-pack/skills/grammarly-upgrade-migration/SKILL.md

Skill Metadata

Name
grammarly-upgrade-migration
Description
'Upgrade and migration guidance for Grammarly API version changes. Use

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.com endpoint 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.