p3n

API reference

Base URL: https://api.p3n.si. All requests and responses are JSON. Send Content-Type: application/json.

Authentication

Create a key on the API keys page and send it as a bearer token:

Authorization: Bearer p3n_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Billing

Every call reserves credit up front and is charged for the work it actually did. Each response includes charged_usd. If your balance is too low you get 402 insufficient_credit and nothing runs. Failed calls are not charged.

Errors

{"error": {"type": "insufficient_credit", "message": "..."}}
Status type Meaning
401 unauthorized Missing or invalid key
402 insufficient_credit Top up to continue
404 not_found Unknown skill, run or session
422 invalid_request, skill_error Bad input
429 rate_limited 60 requests per minute per key
502 upstream_error A model provider failed; not charged

Skills

GET /v1/skills lists them.

AI Detection

Was this written by a person or a model?

POST /v1/detect
{"text": "..."}

Deslop

Makes text mean something: removes filler, vague claims and padding.

POST /v1/deslop
{"text": "..."}

Humanize

Makes text stop reading as AI-generated.

POST /v1/humanize
{"text": "..."}

Response (a run):

{
  "id": "01JD...",
  "skill": "detect",
  "status": "succeeded",
  "output": {"score": 0.82, "verdict": "likely_ai", "flagged": [{"index": 0, "sentence": "...", "tells": ["fast-paced world"]}], "sentences": 4},
  "charged_usd": "0.002000",
  "error": null,
  "created_at": "2026-10-18T20:00:00+00:00"
}

Rewrite skills (deslop, humanize) return {"text": "..."} as output.

Editorial

Editorial is a conversation. Start a session, then send instructions.

POST /v1/editorial/sessions
{"text": "...", "options": {"domain": "legal", "voice": "plain, direct"}}
→ 201 {"session": {"id": "01JD...", ...}, "run": {...output.text...}}

POST /v1/editorial/sessions/{id}/messages
{"message": "tighter, and keep the second paragraph"}
→ {"session": {...}, "run": {...}}

GET /v1/editorial/sessions/{id}
→ the session with all messages

Sessions expire 24 hours after the last message.

Long inputs: async runs

Add "async": true (large inputs switch automatically). You get 202 with a run id and poll URL:

GET /v1/runs/{id}

Poll until status is succeeded or failed.

Account

GET /v1/me
→ {"email": "...", "balance_usd": "9.998000", "key": {"name": "laptop", "prefix": "a1b2c3d4e5f6"}}

Limits

  • Input up to 200,000 characters per call.
  • 60 requests per minute per key.