Browse documentation
METAPHRASE / ENDPOINTSREST API v1
Open in AI
Open in ChatGPT Open in Claude
Markdown

grammar

Find language issues and return suggestions with corrected text, without an LLM.

Endpoint and method

POST https://metaphrase.msalman.com/api/v1/grammar

Authentication

Send Authorization: Bearer <key> and Content-Type: application/json. Create a key at the dashboard. Store it in backend environment variable METAPHRASE_API_KEY; never expose it in a browser, mobile binary, public repository, or widget. See authentication and a working backend proxy.

Request parameters

JSON uses snake_case. Unknown fields are rejected. Omit optional values instead of sending null. Maximum text length: 20,000 Unicode code points.

NameTypeRequiredDefaultAllowed values / rangeMax length / items
textstringYes——20000
languagestringNo"en-US"auto, ar, ast-ES, be-BY, br-FR, ca-ES, ca-ES-valencia, ca-ES-balear, da-DK, de, de-DE, de-AT, de-CH, de-DE-x-simple-language, el-GR, en, en-US, en-GB, en-AU, en-CA, en-NZ, en-ZA, eo, es, es-AR, fa, fr, fr-CA, fr-CH, fr-BE, ga-IE, gl-ES, it, ja-JP, km-KH, nl, nl-BE, pl-PL, pt, pt-PT, pt-BR, pt-AO, pt-MZ, ro-RO, ru-RU, sk-SK, sl-SI, sv, ta-IN, tl-PH, uk-UA, zh-CN, crh-UA, nl-NL, de-DE-x-simple-language-DE, es-ES, it-IT, fa-IR, sv-SE, de-LU, fr-FR—
streambooleanNofalsefalse—
  • text: Plain text. Limit counts Unicode code points.
  • language: A LanguageTool language code, or auto. LanguageTool supports only the listed codes; Urdu/Hindi are translation targets, not grammar languages.
  • stream: Use true for SSE on paraphrase/rewrite, translate or summarize. Other operations require false; alternatives always returns JSON.

Offsets and lengths use UTF-16 code units, matching JavaScript. Python strings use Unicode code points: use text.encode('utf-16-le')[offset * 2:(offset + length) * 2].decode('utf-16-le') to extract a match. original_text is unchanged. text applies the first replacement for non-overlapping matches. corrections_count counts applicable fixes, while matches may also contain overlaps or issues with no replacement.

Full example request

{
  "text": "The best ideas deserves to be shared.",
  "language": "en-US",
  "stream": false
}

Full example response

HTTP 200, application/json. This is an illustrative response; writing, source metadata, token counts and cost can vary. request_id is a server-generated UUID. Cache hits use usage.cached=true and report zero new tokens/cost. usage_estimated=true means the provider did not supply exact token counts.

{
  "request_id": "019b8df0-1234-4000-8000-000000000002",
  "tool": "grammar",
  "result": {
    "text": "The best ideas deserve to be shared.",
    "original_text": "The best ideas deserves to be shared.",
    "language": "en-US",
    "matches": [
      {
        "id": "0-15",
        "offset": 15,
        "length": 8,
        "message": "The verb form ‘deserves’ does not seem to match the subject ‘ideas’.",
        "short_message": "",
        "category": "grammar",
        "rule_id": "SUBJECT_VERB_AGREEMENT_PLURAL",
        "replacements": [
          "deserve",
          "deserved",
          "will deserve"
        ],
        "original": "deserves"
      }
    ],
    "corrections_count": 1
  },
  "usage": {
    "input_tokens": 0,
    "output_tokens": 0,
    "total_tokens": 0,
    "estimated_cost_usd": 0,
    "usage_estimated": false,
    "cached": false,
    "provider": "self_hosted",
    "model": "LanguageTool 6.8"
  },
  "input_words": 7,
  "output_words": 7
}

Errors: cause and fix

Every error uses this JSON shape and a link to its exact fix:

{
  "error": {
    "code": "unauthorized",
    "message": "A valid API key is required.",
    "docs_url": "https://metaphrase.msalman.com/docs/errors#unauthorized"
  }
}
CodeHTTPCauseFix
invalid_request400Check the request fields and their allowed values.Send the documented JSON fields with the correct types. Unknown fields are rejected.
invalid_json400The request body is not valid JSON.Serialize the request with JSON.stringify or your language's JSON encoder.
unsupported_media_type415Send application/json content.Set Content-Type: application/json. File uploads use their separate documented route.
body_too_large413The request body exceeds the allowed size.Reduce the payload and split long documents at sentence boundaries.
text_too_long413The text exceeds the configured character limit.Use the max_input_chars value in the API specification and split longer documents.
unauthorized401A valid API key is required.Send Authorization: Bearer YOUR_KEY. Create a new key if the old one was revoked.
forbidden403This action is not available to this account.Check account status and the key's access permissions.
rate_limited429Too many requests. Please wait and try again.Wait for Retry-After seconds and use exponential backoff with jitter.
quota_exceeded429The monthly usage quota has been reached.Wait for the next UTC calendar month or ask an administrator to change the plan.
service_unavailable503A required service is temporarily unavailable.Wait and retry. The operator should check Redis, PostgreSQL, or the tool's health.
not_found404This resource does not exist.Check the API version, endpoint name, and resource identifier.
method_not_allowed405This HTTP method is not supported.Use POST for processing endpoints and the method documented for dashboard routes.
internal_error500The request could not be completed.Retry once. If it persists, provide the X-Request-Id header to the operator.
upstream_timeout504The writing provider took too long to respond.Retry a shorter input or wait before retrying. Do not append a retry to partial streamed output.
upstream_error502The writing provider returned an invalid response.Retry once with a shorter input. If it continues, the operator should check the configured model's compatibility.

For streaming requests, failures after HTTP headers are sent arrive as an SSE error event with this same error object. Never treat partial text as a completed result or append output from a retry.

Rate limits and quotas

Defaults: Free 15 requests per 60 seconds and 500 accepted calls per key per UTC calendar month; Pro placeholders 120/60 seconds and 25,000/month. The operator can change plans; response headers are authoritative. Calls to all tools share a key's limits. Accepted cache hits and upstream failures consume quota. Authentication/schema failures and provider-not-configured preflight failures do not. X-RateLimit-Remaining, X-RateLimit-Reset, X-Quota-Remaining and X-Quota-Reset show remaining capacity and Unix-second reset times. For HTTP 429 wait Retry-After seconds; quota/budget limits need a reset or operator action. Requests may also receive 429 when the shared processing slots are busy. See retries and long texts.

Code samples

Complete backend programs. Set METAPHRASE_API_KEY in your server environment; never expose it in a frontend.

request.sh
#!/usr/bin/env bash
set -euo pipefail
: "${METAPHRASE_API_KEY:?Set METAPHRASE_API_KEY in your backend environment}"
BASE_URL="${METAPHRASE_BASE_URL:-https://metaphrase.msalman.com}"
curl --fail-with-body --silent --show-error \
  --request POST "${BASE_URL%/}/api/v1/grammar" \
  --header "Authorization: Bearer $METAPHRASE_API_KEY" \
  --header 'Content-Type: application/json' \
  --data-binary @- <<'JSON'
{
  "text": "The best ideas deserves to be shared.",
  "language": "en-US",
  "stream": false
}
JSON

Run: bash request.sh

Try the API with Scalar

Explore the schema and send a real request. Enter your key only in this playground.