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.
| Name | Type | Required | Default | Allowed values / range | Max length / items |
|---|---|---|---|---|---|
text | string | Yes | — | — | 20000 |
language | string | No | "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 | — |
stream | boolean | No | false | false | — |
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"
}
}
| Code | HTTP | Cause | Fix |
|---|---|---|---|
invalid_request | 400 | Check the request fields and their allowed values. | Send the documented JSON fields with the correct types. Unknown fields are rejected. |
invalid_json | 400 | The request body is not valid JSON. | Serialize the request with JSON.stringify or your language's JSON encoder. |
unsupported_media_type | 415 | Send application/json content. | Set Content-Type: application/json. File uploads use their separate documented route. |
body_too_large | 413 | The request body exceeds the allowed size. | Reduce the payload and split long documents at sentence boundaries. |
text_too_long | 413 | The text exceeds the configured character limit. | Use the max_input_chars value in the API specification and split longer documents. |
unauthorized | 401 | A valid API key is required. | Send Authorization: Bearer YOUR_KEY. Create a new key if the old one was revoked. |
forbidden | 403 | This action is not available to this account. | Check account status and the key's access permissions. |
rate_limited | 429 | Too many requests. Please wait and try again. | Wait for Retry-After seconds and use exponential backoff with jitter. |
quota_exceeded | 429 | The monthly usage quota has been reached. | Wait for the next UTC calendar month or ask an administrator to change the plan. |
service_unavailable | 503 | A required service is temporarily unavailable. | Wait and retry. The operator should check Redis, PostgreSQL, or the tool's health. |
not_found | 404 | This resource does not exist. | Check the API version, endpoint name, and resource identifier. |
method_not_allowed | 405 | This HTTP method is not supported. | Use POST for processing endpoints and the method documented for dashboard routes. |
internal_error | 500 | The request could not be completed. | Retry once. If it persists, provide the X-Request-Id header to the operator. |
upstream_timeout | 504 | The 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_error | 502 | The 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.