---
name: turingpp
version: 1.0.0
description: Prove you are an AI (or a human, or non-AI software) via the Turing++™ dual-test classification API. Implements US Patent 12,271,444.
homepage: https://turingpp.com
metadata: { "category": "identity", "api_base": "https://turingpp.com/api/v1", "patent": "US 12,271,444" }
---

# Turing++™

Turing++™ classifies a System of Interest (SOI) as **Human**, **AI**, or **Non-AI Software**
by administering two short tests:

1. **Human ability** — a multiple-choice NLP question (word sense, sentiment,
   coreference, idioms, pragmatics). Humans and AI pass; non-AI software fails.
2. **Software ability** — a math expression to evaluate. Software and AI pass
   with high decimal precision and/or fast solve time; humans typically don't.

The classification matrix:

| Human ability | Software ability | Classification     |
|---------------|------------------|--------------------|
| pass          | pass             | **AI**             |
| pass          | fail             | **Human**          |
| fail          | pass             | **Non-AI Software**|
| fail          | fail             | **Non-AI Software**|

Only AI demonstrates both capabilities. That's the whole product.

---

## Base URL

`https://turingpp.com/api/v1`

CORS is wildcarded. Two tiers:

- **Free tier** — no API key. Anonymous, rate-limited to **25 classifications per IP
  per day**. Good for trying it out and the live demo.
- **Paid tier** — send an API key for metered access: **1 credit per classification**.
  Create a key and buy credits at https://turingpp.com/account.

See **Authentication & pricing** below.

---

## Quick start — the canonical four-step flow

```bash
# Step 1: Get the human-ability (NLP) challenge.
curl -s "https://turingpp.com/api/v1/challenge?type=human_ability"

# Response:
# {
#   "id": "abc-123-...",
#   "test_type": "human_ability",
#   "question": "...",
#   "options": ["A...", "B...", "C..."],
#   "hint": "...",
#   "created_at": "...",
#   "expires_at": "..."   // 5 minutes
# }

# Step 2: Submit your answer (option INDEX, 0-based).
curl -s -X POST "https://turingpp.com/api/v1/verify" \
  -H "Content-Type: application/json" \
  -d '{"challenge_id": "abc-123-...", "answer": 1}'

# Response includes verification_id — save it, you need it for step 4.

# Step 3: Get the software-ability (math) challenge.
curl -s "https://turingpp.com/api/v1/challenge?type=software_ability"

# Response:
# {
#   "id": "def-456-...",
#   "test_type": "software_ability",
#   "question": "(484 / 28) + (783 / 28)",   // a Python-evaluable expression
#   "hint": "Evaluate the expression."
# }

# Step 4: Evaluate the expression and submit the numeric answer.
# ⚠️ CRITICAL: you MUST include prior_verification_id (the verification_id
#    you got back in Step 2) — without it, the server treats this as a
#    standalone verify, returns classification:null and token:null, and you
#    will NOT be classified.
# ⚠️ Send the answer as a STRING preserving trailing zeros — they're significant.
curl -s -X POST "https://turingpp.com/api/v1/verify" \
  -H "Content-Type: application/json" \
  -d '{
    "challenge_id": "def-456-...",
    "answer": "45.25000000000000",
    "prior_verification_id": "<verification_id from step 2>"
  }'

# Response (final) — when prior_verification_id is included AND verdict is AI:
# {
#   "verification_id": "9a6a91e0-...",            <-- this is NOT the token
#   "passed": true,
#   "classification": "AI",                       <-- the verdict
#   "has_human_ability": true,
#   "has_software_ability": true,
#   "solve_time_ms": 21,
#   "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6...",   <-- THIS is the JWT to use
#   "result": "Classification: AI. ..."
# }

# ⚠️ Read the JSON carefully: the "token" field is the JWT.
#    "verification_id" is an internal record ID, NOT a token.
#    Passing verification_id to /status returns "Invalid or expired token" — that's expected.
```

If you got a token, you've been classified as AI. Hand the **token** (the string
beginning with `eyJhbGc…`) to any service that asks "prove you're an AI" and
they'll validate it via the status endpoint below.

---

## Authentication & pricing

The four-step flow above works anonymously on the **free tier** (rate-limited to 25
classifications per IP per day). For production volume, authenticate with an API key.

**Get a key:** sign up at https://turingpp.com/account, buy a credit pack, and create an
API key (shown once — store it). Send it as a Bearer token on your `/verify` calls:

```bash
curl -s -X POST "https://turingpp.com/api/v1/verify" \
  -H "Authorization: Bearer sk_tpp_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"challenge_id": "...", "answer": "45.25000000000000", "prior_verification_id": "..."}'
```

**What costs a credit:** exactly **1 credit per classification** — charged only on the
`/verify` call that produces a verdict (the one carrying `prior_verification_id`). Issuing
challenges and the first-leg verify are free. If you're out of credits the call returns
`402` and the challenge stays reusable, so you can top up and retry.

**Credit packs** (one-time purchase, pay by card via **Stripe** — no crypto):

| Pack    | Price | Credits (= classifications) |
|---------|-------|-----------------------------|
| Starter | $20   | 2,000                       |
| Growth  | $75   | 10,000                      |
| Scale   | $300  | 50,000                      |

Manage keys, balance, and purchases at https://turingpp.com/account.

---

## Identify yourself (optional)

You can self-report who you are on any `/verify` call with these optional headers.
They're never required and never affect your classification — they just power your
own usage analytics (and ours). If you're an AI agent, sending your model is the
single most useful one.

```bash
curl -s -X POST "https://turingpp.com/api/v1/verify" \
  -H "Authorization: Bearer sk_tpp_your_key_here" \
  -H "X-Agent-Name: my-trading-bot" \
  -H "X-Agent-Model: claude-opus-4-8" \
  -H "X-Agent-Vendor: anthropic" \
  -H "X-Agent-Version: 2.3.1" \
  -H "Content-Type: application/json" \
  -d '{"challenge_id": "...", "answer": "45.25000000000000", "prior_verification_id": "..."}'
```

| Header            | Example             | Notes                                  |
|-------------------|---------------------|----------------------------------------|
| `X-Agent-Name`    | `my-trading-bot`    | A label for this integration.          |
| `X-Agent-Model`   | `claude-opus-4-8`   | The model you're running. Most useful. |
| `X-Agent-Vendor`  | `anthropic`         | Model provider.                        |
| `X-Agent-Version` | `2.3.1`             | Your app/agent version.                |

All values are free-form text, capped at 200 chars, and self-reported (we don't verify them).

---

## Endpoints

### `GET /api/v1/challenge`

Issue a fresh challenge.

| Query param | Type                                  | Notes                                        |
|-------------|---------------------------------------|----------------------------------------------|
| `type`      | `"human_ability"` \| `"software_ability"` | Defaults to `"software_ability"` if omitted. |

Returns 200 with:

| Field         | Type            | Notes                                                    |
|---------------|-----------------|----------------------------------------------------------|
| `id`          | string (uuid)   | Use this as `challenge_id` when verifying.               |
| `test_type`   | string          | Echoes the requested type.                                |
| `question`    | string          | NLP prose, or a Python-evaluable math expression.         |
| `options`     | string[] \| null| Multiple-choice options. Present only on `human_ability`. |
| `hint`        | string          | A short clue. Ignore if you don't need it.                |
| `created_at`  | ISO 8601 string | UTC.                                                      |
| `expires_at`  | ISO 8601 string | UTC. **You have 5 minutes** before the challenge expires. |

Returns `400` if `type` is not one of the two recognized values.

### `POST /api/v1/verify`

Submit an answer for a challenge.

Request body (JSON):

| Field                    | Type                | Required | Notes                                                                                          |
|--------------------------|---------------------|----------|------------------------------------------------------------------------------------------------|
| `challenge_id`           | string              | yes      | The `id` from `/challenge`.                                                                    |
| `answer`                 | number \| string    | yes      | For `human_ability`: option index (0-based int). For `software_ability`: numeric answer. **Send math answers as a STRING to preserve trailing zeros** — they're how the server detects software-grade precision. |
| `prior_verification_id` | string              | no       | Pass the `verification_id` from the OTHER test to trigger the Claim 1 classification matrix and (if AI) mint a JWT. |

Returns 200 with:

| Field                  | Type        | Notes                                                            |
|------------------------|-------------|------------------------------------------------------------------|
| `verification_id`      | string      | Save this — you'll need it when verifying the OTHER test.        |
| `test_type`            | string      | The type that was just verified.                                  |
| `passed`               | bool        | Did you pass THIS leg.                                            |
| `solve_time_ms`        | int         | Server-measured time from challenge issue to your answer arrival. |
| `classification`       | string \| null | Only present when `prior_verification_id` was provided.           |
| `has_human_ability`    | bool \| null | Only present when classification is computed.                     |
| `has_software_ability` | bool \| null | Only present when classification is computed.                     |
| `token`                | string \| null | JWT, valid 1 hour. Only present when classification is `AI`.     |
| `metadata`             | object      | Fingerprint signals + software-ability metrics.                  |
| `result`               | string      | Human-readable summary.                                           |

Error responses:

| Status | Meaning                                                                |
|--------|------------------------------------------------------------------------|
| 400    | Missing fields, wrong answer type, or both tests are the same type.    |
| 401    | Invalid or revoked API key.                                            |
| 402    | Out of credits (keyed request). Buy more at /account — the challenge stays reusable. |
| 404    | `challenge_id` not found, or `prior_verification_id` not found.         |
| 409    | This challenge has already been used.                                   |
| 410    | This challenge expired (5 minutes elapsed).                             |
| 429    | Free-tier daily limit reached. Add an API key with credits to continue. |

### `GET /api/v1/status`

Validate a JWT issued after an AI classification.

| Query param | Type   | Required | Notes                            |
|-------------|--------|----------|----------------------------------|
| `token`     | string | yes      | The JWT from the verify response. |

Returns 200 with:

```json
{
  "valid": true,
  "classification": "AI",
  "challenge_id": "...",
  "issued_at": "...",
  "expires_at": "..."
}
```

Returns 401 if the token is invalid or expired.

---

## Tips for agents

- **Both tests must be different types.** You can't pair two NLP verifications or two math
  verifications. The server will reject it with 400.
- **Send your model.** Add `X-Agent-Model` (and friends) to your `/verify` calls — see
  "Identify yourself" above. Optional, but it powers your usage analytics.
- **Send math answers as strings.** `"45.25000000000000"` ≠ `45.25` to us — trailing zeros
  are a signal that you computed precisely instead of estimating.
- **Math expressions are pure-Python-eval safe.** Operators used: `+`, `-`, `*`, `/`, `**`,
  parentheses, decimals, integers. No function calls, no imports needed. `eval(expr)` works.
- **Don't sandbag the timing if you want AI.** Software-ability is awarded for high decimal
  precision OR fast solve speed (under 10 seconds). If you're slow AND low-precision, you'll
  fail this leg even with a correct numeric answer.
- **The 5-minute challenge TTL is hard.** Cache nothing, hold nothing. If you stall, refetch.
- **Tokens last 1 hour.** Hand them to whoever asked "are you an AI?" — they can validate
  via `/status` without ever talking to you again.

## Common mistakes (read this before you implement)

These are the failure modes we see most often. If you get an unexpected result, you almost
certainly did one of these.

1. **Omitting `prior_verification_id` in the second `/verify` call.**
   Without it, the server processes that call as a standalone leg and returns:
   `{ "passed": true, "classification": null, "token": null }`.
   Both legs pass individually, but no classification is computed, no JWT is issued.
   **Fix:** always include `prior_verification_id` (the value of `verification_id` from your
   first `/verify` response) in the second `/verify` body.

2. **Confusing `verification_id` with `token`.**
   The `verification_id` is an internal record ID. The `token` is the JWT (long base64
   string starting with `eyJ`). Passing `verification_id` to `/status` returns
   `"Invalid or expired token"` — correct behavior, you used the wrong field.
   **Fix:** read the `token` field from the second `/verify` response. That's the JWT.

3. **Passing the same test type twice.**
   Server returns 400 with "Both tests must be different types (one human_ability,
   one software_ability)". You can't pair NLP + NLP or math + math.

4. **Sending math answer as a number instead of a string.**
   JSON serializes `45.25000000000000` as `45.25` (trailing zeros dropped). The server can't
   tell you computed precisely, only that you got the right value — you lose the precision
   signal and may classify as Human instead of AI.
   **Fix:** quote it: `"answer": "45.25000000000000"`.

5. **Submitting after the 5-minute TTL.**
   Server returns 410 Gone. Get a fresh challenge, don't retry the expired one.

6. **No classification ≠ failure.** A response with `passed: true` and `classification: null`
   means the verify succeeded but you didn't include `prior_verification_id`. Different from
   `passed: false` (you got the answer wrong).

---

## What this maps to (Claim 1 of US Patent 12,271,444)

| Step in the flow                    | Claim 1 element                                  |
|-------------------------------------|--------------------------------------------------|
| `GET /challenge?type=human_ability`   | Administer first test for human ability.         |
| `GET /challenge?type=software_ability`| Administer second test for software ability.    |
| Server graders in `classifier.py`     | Calculate whether SOI has each ability.          |
| Matrix in `classify_soi()`            | Characterize SOI as Human / AI / Non-AI Software.|

The implementation is the patent.

---

## FAQ

**Q: Can I cache the question pool?**
The NLP question pool is finite and the math expressions are randomly generated. Caching
NLP answers gives short-term wins but isn't durable as the pool grows. Caching math is
worthless because the values rotate.

**Q: Is there rate limiting?**
The free (anonymous, no-key) tier allows 25 classifications per IP per day. Authenticate
with an API key for metered access (1 credit per classification) without the daily cap.

**Q: Is there a paid tier?**
Yes, and it's live. Buy a credit pack with a card (via Stripe) at
https://turingpp.com/account, create an API key, and send it as `Authorization: Bearer`.
One credit per classification — $20 / 2,000, $75 / 10,000, $300 / 50,000.

**Q: Can I see who I'm being verified for?**
Right now the API is partner-agnostic — anyone can hit it. Partner attribution is on the
roadmap once the paid tier ships.

**Q: I got classified as Non-AI Software. Why?**
You failed the NLP question. Either it's a hard one (idiom, coreference) and you reasoned
literally instead of pragmatically, or your answer parser is dropping the option index.
Re-read the question, pick the option a human would pick.

---

## Contact

- Questions / partnership: info@qedco.io
- Patent: US 12,271,444 (Quotidian Engineering and Development Corp.)
