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.