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

detect

Estimate AI-like writing cues overall and for each sentence; results may be wrong.

Endpoint and method

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

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
streambooleanNofalsefalse—
  • text: Plain text. Limit counts Unicode code points. At least 50 words are required. Scores are uncalibrated estimates, not authorship evidence.
  • stream: Use true for SSE on paraphrase/rewrite, translate or summarize. Other operations require false; alternatives always returns JSON.

This is an uncalibrated LLM heuristic, not a validated probability of authorship. At least 50 words are required. Always display disclaimer with every result. Do not make decisions about a person from this score alone. Sentence offsets and lengths use UTF-16 units; very long sentences may be divided into bounded spans.

Full example request

{
  "text": "The research team spent several long months carefully studying how local people use public spaces in their neighborhood and listening to residents describe their daily routines. They found that shade, comfortable seating, and safe crossings mattered more to participants than expensive decorative features, so the final proposal focused on those practical needs.",
  "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-000000000003",
  "tool": "detect",
  "result": {
    "text": "AI-like writing estimate: 50%\n\nAI detection is an uncalibrated estimate and may be wrong. Human writing can be flagged as AI, and AI writing can be missed. Never use this result alone to make decisions about a person.\n\n[50%] The research team spent several long months carefully studying how local people use public spaces in their neighborhood and listening to residents describe their daily routines.\n\n[50%] They found that shade, comfortable seating, and safe crossings mattered more to participants than expensive decorative features, so the final proposal focused on those practical needs.",
    "ai_estimate": 50,
    "method": "llm_heuristic",
    "disclaimer": "AI detection is an uncalibrated estimate and may be wrong. Human writing can be flagged as AI, and AI writing can be missed. Never use this result alone to make decisions about a person.",
    "sentences": [
      {
        "text": "The research team spent several long months carefully studying how local people use public spaces in their neighborhood and listening to residents describe their daily routines.",
        "offset": 0,
        "length": 177,
        "ai_estimate": 50
      },
      {
        "text": "They found that shade, comfortable seating, and safe crossings mattered more to participants than expensive decorative features, so the final proposal focused on those practical needs.",
        "offset": 178,
        "length": 184,
        "ai_estimate": 50
      }
    ]
  },
  "usage": {
    "input_tokens": 140,
    "output_tokens": 80,
    "total_tokens": 220,
    "estimated_cost_usd": 0.000054,
    "usage_estimated": false,
    "cached": false,
    "provider": "primary",
    "model": "gpt-6-luna"
  },
  "input_words": 52,
  "output_words": 93
}

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.
budget_exceeded429The writing service has reached its configured spending limit.The operator must review usage and adjust the configured budget before more paid requests can run.
provider_not_configured503The writing provider is not configured yet.The operator must configure LLM_PRIMARY_API_KEY or a fallback provider in .env and recreate the web container.
upstream_unavailable503The writing provider is temporarily unavailable.Retry with backoff. The operator can inspect provider availability without logging submitted text.
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.
output_truncated502The response reached its output limit before finishing.Use a shorter input or ask the operator to adjust the tool's output cap and chunk size.
text_too_short422More text is needed for a meaningful estimate.Submit at least the documented minimum word count. Very short texts cannot be assessed meaningfully.

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/detect" \
  --header "Authorization: Bearer $METAPHRASE_API_KEY" \
  --header 'Content-Type: application/json' \
  --data-binary @- <<'JSON'
{
  "text": "The research team spent several long months carefully studying how local people use public spaces in their neighborhood and listening to residents describe their daily routines. They found that shade, comfortable seating, and safe crossings mattered more to participants than expensive decorative features, so the final proposal focused on those practical needs.",
  "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.