Humanora

Developers

Humanora API

Humanize and check text from your own code. The API uses the same model, word allowance and limits as the app, with JSON in and JSON out.

Base URL https://app.humanora.app/api/v1Version v1JSON over HTTPS

Quickstart#

  1. 1.Create a key in Settings → API keys and copy the secret (it's shown once).
  2. 2.Store it as an environment variable, e.g. HUMANORA_API_KEY. Call the API from your server only, never from browser code.
  3. 3.Send your first request:
curl https://app.humanora.app/api/v1/humanize \
  -H "Authorization: Bearer $HUMANORA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"text": "Paste at least fifty characters of text here to humanize it.", "tone": "standard", "level": "intermediate"}'

Authentication#

Every request needs an API key in the Authorization header as a Bearer token. Keys look like hum_live_ followed by 43 random characters. Cookies are ignored on /api/v1: a signed-in browser session can't call the API.

Header
Authorization: Bearer hum_live_••••••••••••••••••••••••••••••••••••••••••••

Each key has scopes. humanize allows POST /v1/humanize; detect allows POST /v1/detect. /v1/usage and /v1/me work with any valid key. Keys can expire and can be revoked or rotated at any time.

POST/v1/humanize#

Rewrites text so it reads naturally. Synchronous: the response arrives when the rewrite is done (usually 10–60 s, up to about 100 s). Charges one word per word of input, only on success.

FieldTypeDescription
textstring, required50 characters to 1,500 words. Split longer documents into parts.
tonestringOne of standard (default), casual, academic, formal.
levelstringHow much to change: simple, intermediate (default), advanced.
Request body
{
  "text": "Your draft text (50 characters to 1,500 words)…",
  "tone": "standard",
  "level": "intermediate"
}
200 response
{
  "id": "hmz_2f0c1e8b5a4d4f7c9e1b3a6d8c0e2f41",
  "object": "humanization",
  "output": "The rewritten text…",
  "tone": "standard",
  "level": "intermediate",
  "words_charged": 182,
  "credits": { "remaining": 4818, "allowance": 5000, "resets_at": "2026-11-01T00:00:00.000Z" },
  "created_at": "2026-10-13T09:30:12.004Z"
}

POST/v1/detect#

Scores how likely text is AI-generated, overall and per sentence. Free (no words charged) but shares your account's hourly detection limit with the app (20 checks per hour). text must be 15–1,500 words. start/end are character offsets into the text you sent; label is ai (≥ 0.65), mixed or human (≤ 0.35).

curl https://app.humanora.app/api/v1/detect \
  -H "Authorization: Bearer $HUMANORA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "At least fifteen words of text to check for AI-generated writing patterns, sentence by sentence."}'
200 response
{
  "object": "detection",
  "score": 0.82,
  "label": "ai",
  "words": 214,
  "sentences": [ { "start": 0, "end": 96, "score": 0.91 }, { "start": 97, "end": 188, "score": 0.64 } ],
  "method": "model",
  "created_at": "2026-10-13T09:31:40.517Z"
}

GET/v1/usage#

Your word allowance (shared with the app), plan limits and this key's last 30 days.

curl https://app.humanora.app/api/v1/usage \
  -H "Authorization: Bearer $HUMANORA_API_KEY"
200 response
{
  "object": "usage",
  "plan": "plus",
  "words": { "allowance": 50000, "used": 1840, "remaining": 48160, "resets_at": "2026-11-01T00:00:00.000Z" },
  "limits": { "requests_per_minute": 60, "concurrent_requests": 2 },
  "key": { "requests_30d": 112, "errors_30d": 3, "words_30d": 1840, "daily": [ { "day": "2026-10-13", "requests": 9, "errors": 0, "words": 410 } ] }
}

GET/v1/me#

Describes the calling key. Useful as a health check for your integration.

curl https://app.humanora.app/api/v1/me \
  -H "Authorization: Bearer $HUMANORA_API_KEY"
200 response
{
  "object": "key",
  "key": { "prefix": "hum_live_7Hc2kQp9Za", "name": "Production", "scopes": ["humanize", "detect"], "expires_at": null, "state": "active" },
  "account": { "plan": "plus" }
}

Errors#

Errors use one JSON shape. Every response carries an X-Request-Id header that matches request_id; include it when contacting support.

Error response
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded (60 requests per minute on the plus plan).",
    "request_id": "req_Qm3x9vT1bK0aZ2cW7yLp"
  }
}
StatusCodeMeaning
400invalid_requestA field is missing or invalid (the message names it). Also invalid_json, invalid_idempotency_key.
401missing_api_key · invalid_api_keyNo key, or the key isn't valid.
401key_revoked · key_expired · key_rotatedThe key was revoked, expired, or rotated and its grace period ended.
402insufficient_creditsNot enough words left. Nothing is charged.
403insufficient_scopeThe key lacks the scope this endpoint needs.
409idempotency_in_progressA request with the same Idempotency-Key is still running. Retry after Retry-After.
413 / 415payload_too_large · unsupported_media_typeBody over 256 KB, or not application/json.
422text_too_long · text_too_short · idempotency_key_reusedText outside limits, or a reused Idempotency-Key with a different body.
429rate_limited · concurrency_limit · detect_limit · api_lockedSlow down; wait for Retry-After seconds.
503 / 504model_unavailable · model_timeout · model_errorThe model is busy or down. Nothing is charged; retry with backoff.
500internal_errorUnexpected error on our side. Safe to retry with the same Idempotency-Key.

Rate limits#

Limits apply per key and per account (all keys combined), in requests per minute, plus a cap on requests running at the same time.

PlanRequests / minuteConcurrent requests
Free101
Plus602
Pro3004

Successful responses include RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset (seconds) and RateLimit-Policy. A 429 includes Retry-After. Repeated failed authentication locks out the source IP (20 failures per 10 minutes), and repeated bad secrets for one account pause its API access for 15 minutes.

Idempotency#

Send an Idempotency-Key header (any unique string up to 128 characters, such as a UUID) with POST /v1/humanize so a retry after a timeout never charges twice. For 24 hours, a repeat with the same key and body returns the original response with Idempotent-Replayed: true. The same key with a different body returns 422 idempotency_key_reused. Server errors (5xx) are not stored, so you can retry them with the same key. Keys are scoped to your API key.

Credits#

The API and the app share one word allowance. Words are reserved when a humanize request starts, committed on success and refunded on any failure, timeout or disconnect. Every authenticated response includes:

HeaderDescription
X-Words-AllowanceWords in your current period.
X-Words-RemainingWords left after this request.
X-Words-ResetWhen the allowance refills (ISO 8601).
X-Words-ChargedWords charged by this request (humanize only).

Key safety & rotation#

  • Keep keys server-side in environment variables or a secret manager. Never ship them in browser or mobile apps or commit them to git.
  • Use one key per environment or service, with only the scopes it needs, so you can revoke one without breaking the others.
  • Rotate regularly: Rotate issues a new secret and keeps the old one working for a grace period (24 hours by default, up to 7 days). Deploy the new key, confirm traffic moved (check Last used), then let the old key lapse or revoke it.
  • If a key leaks, revoke it immediately. Revocation takes effect on the next request.
  • We store only a keyed hash of each secret, so we can't show it again. Lost keys must be rotated.

Changelog#

v1 CurrentOctober 2026

First release: humanize, detect, usage and me endpoints; scoped keys with rotation; idempotent humanize; per-plan rate limits; shared word allowance.