HUMANIZER STUDIO / API v1Download OpenAPI 3.1 ↗

Your writing engine.
Your integrations.

Create a rewrite, follow its progress, and collect the finished draft from the same GPU engine that powers the studio.

Base URL/api/v1JSON · asynchronous jobs

Start a rewrite

Start a private session, send your text, then poll the returned job ID until its stage is complete, cancelled, or error. Keep the same session header throughout. The first job loads the model; later jobs reuse it.

SESSION_ID=$(curl -s https://human.saeidnia.com/api/v1/session \
  -H "Content-Type: application/json" --data '{}' \
  | python -c 'import json,sys; print(json.load(sys.stdin)["session_id"])')

curl https://human.saeidnia.com/api/v1/jobs \
  -H "X-Humanizer-Session: $SESSION_ID" \
  -H "Content-Type: application/json" \
  --data '{"text":"The garden has 12 beds. Volunteers water it each Saturday.","frozen_ranges":[{"start":0,"end":23}]}'

# HTTP 202
{"id":"0123456789abcdef0123456789abcdef"}

curl https://human.saeidnia.com/api/v1/jobs/JOB_ID \
  -H "X-Humanizer-Session: $SESSION_ID"

For another machine, use the HTTPS address. On the server PC, http://127.0.0.1:8393/api/v1 also works.

Private sessions

No account or service key is needed. Each studio tab automatically starts a private session. Refreshes keep that tab’s session; a fresh tab starts empty. Drafts cannot be read or cancelled from another session, even with their job IDs.

API clients first send POST /api/v1/session with {}, then pass the returned session_id in X-Humanizer-Session. Keep this token private. Sessions expire after an idle hour or a service restart. PowerShell:

$session = (Invoke-RestMethod https://human.saeidnia.com/api/v1/session -Method Post -ContentType "application/json" -Body '{}').session_id
$headers = @{ "X-Humanizer-Session" = $session }
Invoke-RestMethod https://human.saeidnia.com/api/v1/status -Headers $headers

Endpoints

POST/api/v1/session

Send {} to start a private session. Returns HTTP 201 with session_id and expires_in (idle timeout in seconds).

GET/api/v1/status

Model state, GPU name and actual offloaded layers, and your session’s active/latest job IDs. Without a session, both IDs are null. The shared engine_busy flag indicates only whether the GPU is occupied.

POST/api/v1/jobs

Accepts text and optional frozen_ranges. Returns 202 {"id":"…"}. Up to max_concurrent_jobs run simultaneously, sharing one model. When all slots are occupied, another submission returns 409. Check available_slots in status before retrying.

GET/api/v1/jobs/{id}

Returns the job’s current text, output, stage, passage counts, generated-token count, timing, frozen ranges and detail checks. Poll about once a second. Output grows while generating.

POST/api/v1/jobs/{id}/cancel

Send {}. Returns {"status":"stopping"}; poll until the job reaches a terminal stage. An already finished job returns its terminal stage as status. Partial output is retained. Cancellation closes only that job’s stream; another session’s rewrite keeps running. The model unloads after cancellation only when no jobs remain active.

POST/api/v1/unload

Send {} to release the idle model’s GPU memory. Returns 409 during a rewrite. The next rewrite reloads it.

GET/healthz

Public liveness check. Returns the service status and version without draft IDs or content.

/docs and /openapi.json are public. The studio’s existing /api/… routes remain compatible aliases of v1.

Job response

{
  "id": "0123456789abcdef0123456789abcdef",
  "stage": "complete",
  "input": "The garden has 12 beds. Volunteers water it each Saturday.",
  "output": "The garden has 12 beds. Each Saturday, volunteers water the garden.",
  "error": null,
  "frozen_ranges": [{"start": 0, "end": 23, "text": "The garden has 12 beds."}],
  "frozen_output_ranges": [{"start": 0, "end": 23, "text": "The garden has 12 beds."}],
  "completed_chunks": 1,
  "total_chunks": 1,
  "tokens_generated": 14,
  "started_at": 1792800000.0,
  "elapsed_seconds": 2.5,
  "finished_at": 1792800002.5,
  "check": {
    "missing_numbers": [], "extra_numbers": [],
    "citations_preserved": true, "truncated": false,
    "frozen_total": 1, "frozen_kept": 1
  }
}

Illustrative response. Wording and timing vary. Stages: queued → loading (when needed) → tokenizing → generating → complete. Cancellation or failure can end any active stage. An entirely frozen draft completes with zero model passages and zero generated tokens.

Keep words or sentences exact

Pass frozen_ranges as up to 128 selections with inclusive start and exclusive end offsets in the original, untrimmed text. Overlapping or adjacent selections are merged. Frozen wording stays exact and in order. Whole frozen sentences are copied. Word freezes are checked within complete sentence context; if the model changes a frozen selection, expands the sentence excessively, or its passage cannot fit, that passage keeps its original wording. The response reports these fallbacks in protected_passages_kept. Whole sentences usually produce smoother prose than individual words.

Offsets count UTF-16 code units, matching JavaScript’s selectionStart, selectionEnd, and String.slice. Emoji may occupy two units. Splitting a surrogate pair is rejected. Python conversion:

def utf16_offset(text, python_index):
    return len(text[:python_index].encode("utf-16-le")) // 2

start = text.index("keep this")
frozen = {"start": utf16_offset(text, start),
          "end": utf16_offset(text, start + len("keep this"))}

frozen_output_ranges locates the copied selections in the current output, using the same offset convention. A cancelled partial draft may not yet contain every frozen selection. Compare check.frozen_kept with check.frozen_total.

A complete Python client

Uses only Python’s standard library and creates its private session automatically.

import json, time, urllib.request

BASE = "https://human.saeidnia.com/api/v1"
SESSION = None

def api(path, body=None):
    data = None if body is None else json.dumps(body).encode()
    headers = {"Content-Type": "application/json",
               "User-Agent": "Humanizer-Studio-API-Client/1.0"}
    if SESSION:
        headers["X-Humanizer-Session"] = SESSION
    req = urllib.request.Request(BASE + path, data=data, headers=headers)
    with urllib.request.urlopen(req, timeout=15) as response:
        return json.load(response)

SESSION = api("/session", {})["session_id"]
job_id = api("/jobs", {"text": "Your draft here."})["id"]
while True:
    job = api("/jobs/" + job_id)
    if job["stage"] in {"complete", "cancelled", "error"}:
        break
    time.sleep(1)
if job["stage"] != "complete":
    raise RuntimeError(job["error"] or job["stage"])
print(job["output"])

Try your engine

Requests use this tab’s private session automatically. No service key or login is needed.

Ready when you are.

No request sent yet.

Limits, errors & retention

StatusMeaning
400Missing text, invalid JSON, Unicode or frozen ranges
401Session missing or expired; start a new session
403Host or browser origin not allowed
404Unknown or expired job / route
409Engine busy; retry after the current job ends
413Body or draft exceeds limits
415POST requires Content-Type: application/json

For Python clients, set a descriptive User-Agent as shown above. The Cloudflare edge rejects the default Python agent with HTTP 403 / error 1010 before a request reaches this API. Request errors return {"error":"Readable explanation"}. A rewrite failure after acceptance is reported as stage: "error" inside the job’s HTTP 200 response. Numeric mentions and bracketed citations are compared as text patterns; these checks do not verify meaning. Rewrite quality and detector scores are not guaranteed.