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.
https://app.humanora.app/api/v1Version v1JSON over HTTPSQuickstart#
- 1.Create a key in Settings → API keys and copy the secret (it's shown once).
- 2.Store it as an environment variable, e.g.
HUMANORA_API_KEY. Call the API from your server only, never from browser code. - 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.
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.
| Field | Type | Description |
|---|---|---|
text | string, required | 50 characters to 1,500 words. Split longer documents into parts. |
tone | string | One of standard (default), casual, academic, formal. |
level | string | How much to change: simple, intermediate (default), advanced. |
{
"text": "Your draft text (50 characters to 1,500 words)…",
"tone": "standard",
"level": "intermediate"
}{
"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."}'{
"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"{
"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"{
"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": {
"code": "rate_limited",
"message": "Rate limit exceeded (60 requests per minute on the plus plan).",
"request_id": "req_Qm3x9vT1bK0aZ2cW7yLp"
}
}| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A field is missing or invalid (the message names it). Also invalid_json, invalid_idempotency_key. |
| 401 | missing_api_key · invalid_api_key | No key, or the key isn't valid. |
| 401 | key_revoked · key_expired · key_rotated | The key was revoked, expired, or rotated and its grace period ended. |
| 402 | insufficient_credits | Not enough words left. Nothing is charged. |
| 403 | insufficient_scope | The key lacks the scope this endpoint needs. |
| 409 | idempotency_in_progress | A request with the same Idempotency-Key is still running. Retry after Retry-After. |
| 413 / 415 | payload_too_large · unsupported_media_type | Body over 256 KB, or not application/json. |
| 422 | text_too_long · text_too_short · idempotency_key_reused | Text outside limits, or a reused Idempotency-Key with a different body. |
| 429 | rate_limited · concurrency_limit · detect_limit · api_locked | Slow down; wait for Retry-After seconds. |
| 503 / 504 | model_unavailable · model_timeout · model_error | The model is busy or down. Nothing is charged; retry with backoff. |
| 500 | internal_error | Unexpected 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.
| Plan | Requests / minute | Concurrent requests |
|---|---|---|
| Free | 10 | 1 |
| Plus | 60 | 2 |
| Pro | 300 | 4 |
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:
| Header | Description |
|---|---|
X-Words-Allowance | Words in your current period. |
X-Words-Remaining | Words left after this request. |
X-Words-Reset | When the allowance refills (ISO 8601). |
X-Words-Charged | Words 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.