Developer Reference

Aletheia API Documentation

1. Overview

Aletheia is a grooming-detection API for conversational text. It scores a full conversation for predatory or harmful intent and, when two speakers are present, identifies which speaker is the predatory party. It runs on a hierarchical, age-aware transformer that models complete conversations rather than isolated messages, so it reads intent as it develops across a full exchange instead of flagging individual keywords.

The API returns a harmful-intent score, a human-readable risk level, a predicted predatory speaker with per-speaker probabilities, and a confidence estimate. Conversations are never retained. The service processes text in memory and returns a result.

Base URL: https://api.polycreek.org

All requests and responses are JSON over HTTPS. Plain HTTP requests are redirected to HTTPS.


2. Authentication

Aletheia uses bearer-token authentication. Include your API key in the Authorization header on every request to a protected endpoint:

Authorization: Bearer YOUR_API_KEY

Keys are issued by Polycreek. Store your key as a secret. Never embed it in client-side code or commit it to version control. If a key is exposed, contact us to rotate it.

Auth errors

StatusMeaning
401 UnauthorizedNo API key was provided on a protected endpoint.
403 ForbiddenThe API key provided is invalid.

The /health endpoint is public and does not require a key.


3. Endpoints

GET /health

Liveness check. No authentication required.

Response

{ "status": "healthy" }

status is "healthy" when the model is loaded and ready, or "degraded" if the service is up but the model is not yet available. Use this for uptime monitoring and readiness checks.

POST /predict

Score a single conversation. Requires authentication.

Request body

FieldTypeRequiredDescription
conversationstringyesThe conversation text, 1 to 500,000 characters. Include both speakers' turns.
age_astringnoAge of speaker A. Accepts an exact age ("14"), a range ("13-17"), or a class ("minor", "adult").
age_bstringnoAge of speaker B, same formats.

Ages are optional. Provide whatever the platform already knows. Supplying ages activates the model's age-asymmetry pathway and improves accuracy, and omitting them runs a text-only prediction.

Example request

curl -X POST https://api.polycreek.org/predict \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation": "user1: hey how was school today\nuser2: ok i guess\n...",
    "age_a": "adult",
    "age_b": "14"
  }'

Example response

{
  "conversation_preview": "user1: hey how was school today\nuser2: ok i guess...",
  "harmful_score": 0.869,
  "is_harmful": true,
  "risk_level": "Critical",
  "predatory_user": "user1",
  "user_probabilities": {
    "user1": 0.91,
    "user2": 0.04,
    "neither": 0.05
  },
  "confidence": 0.83,
  "num_segments": 12,
  "used_age_features": true
}

POST /batch_predict

Score up to 100 conversations in a single request. Requires authentication. More efficient than issuing many single /predict calls.

Request body

FieldTypeRequiredDescription
conversationsarrayyes1 to 100 items. Each item is either a plain conversation string, or an object with conversation and optional age_a / age_b (same formats as /predict).

Example request

curl -X POST https://api.polycreek.org/batch_predict \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversations": [
      "user1: hey\nuser2: hi",
      { "conversation": "user1: ...\nuser2: ...", "age_a": "adult", "age_b": "13" }
    ]
  }'

Example response

{
  "predictions": [
    { "harmful_score": 0.12, "is_harmful": false, "risk_level": "Safe", "...": "..." },
    { "harmful_score": 0.74, "is_harmful": true,  "risk_level": "High Risk", "...": "..." }
  ]
}

Each item in predictions has the same shape as a /predict response, returned in the same order as the input.


4. Response Fields

Every prediction (from /predict or within a /batch_predict array) contains:

FieldTypeDescription
conversation_previewstringThe first 500 characters of the submitted conversation, for reference.
harmful_scorefloatProbability of predatory or harmful intent, from 0.0 to 1.0.
is_harmfulbooltrue when harmful_score exceeds 0.5.
risk_levelstringHuman-readable band derived from the score (see below).
predatory_userstringWhich speaker is predicted predatory: "user1", "user2", or "neither".
user_probabilitiesobjectProbability distribution across user1, user2, neither (sums to about 1.0).
confidencefloat0.0 to 1.0 estimate of the model's certainty, combining harmful-score margin and speaker-attribution entropy.
num_segmentsintNumber of segments the conversation was split into (long conversations are chunked internally).
used_age_featuresboolWhether speaker ages were supplied and used in this prediction.

5. Risk Levels

risk_level maps the numeric harmful_score to a band for triage and display:

Score rangeRisk level
0.00 to 0.19Safe
0.20 to 0.39Low Risk
0.40 to 0.59Medium Risk
0.60 to 0.79High Risk
0.80 to 1.00Critical

The binary is_harmful flag flips at 0.50. For most moderation workflows we recommend routing on risk_level (for example, auto-queue High Risk and Critical, sample Medium Risk for review) rather than the binary flag alone, and tuning your action threshold against your own tolerance for false positives.


6. Providing Speaker Ages

Ages are optional but recommended when known. The model has a dedicated age-asymmetry pathway: telling it that one speaker is an adult and the other a minor sharpens its distinction between predatory adult-minor dynamics and benign peer interaction.

Accepted formats for age_a / age_b:

  • Exact age. "14", "37"
  • Range. "13-17"
  • Class. "minor", "adult"

If you supply no ages, the model runs text-only. Very short conversations with no age signal can score warm (mid-range) purely from register. Supplying ages, or evaluating longer exchanges, resolves most of this.


7. Rate Limits

The API permits up to 60 requests per 60-second window. Exceeding this returns:

StatusMeaning
429 Too Many RequestsRate limit exceeded. Slow your request rate and retry.

To score many conversations, prefer /batch_predict (up to 100 per call) over rapid single calls. If your pilot needs a higher sustained rate, contact us to raise your limit.


8. Errors

Errors are returned as JSON with an appropriate HTTP status and a detail message:

{ "detail": "API key required (Bearer token)" }
StatusMeaning
401 UnauthorizedMissing API key.
403 ForbiddenInvalid API key.
422 Unprocessable EntityRequest body failed validation (for example, missing conversation, empty string, over 500,000 chars, or more than 100 items in a batch).
429 Too Many RequestsRate limit exceeded.
500 Internal Server ErrorPrediction failed unexpectedly.
503 Service UnavailableThe model is not currently loaded. Retry shortly.

Client applications should handle 429 and 503 with retry and backoff, and treat 401, 403, and 422 as non-retryable configuration errors.


9. Data Handling & Privacy

Aletheia is built for child-safety use and handles data accordingly:

  • No conversation retention. Submitted text is processed in memory to produce a score and is not stored by the service.
  • Hash-based auditing only. When operational logging is enabled, the service records a score and a SHA-256 hash of the conversation, never the raw text, so a flagged case can be joined back on your side without Aletheia holding the content.
  • Transport security. All traffic is served over HTTPS.

Because this API is intended to process conversations that may involve minors, integrators remain responsible for their own COPPA and data-handling obligations on the platform side. For deployments that cannot egress user chat at all, a single-tenant container that runs entirely inside your own environment is available. Contact Polycreek.


10. Quickstart

# 1. Check the service is up (no key needed)
curl https://api.polycreek.org/health

# 2. Score a conversation
curl -X POST https://api.polycreek.org/predict \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation": "user1: ...\nuser2: ...",
    "age_a": "adult",
    "age_b": "14"
  }'

Format conversations with one speaker turn per line (for example, user1: / user2: prefixes) so the model can attribute the predatory party. Read risk_level for triage, harmful_score for thresholding, and predatory_user to route the flagged party.

Need a key or a higher rate limit for a pilot? Get in touch.