API Documentation

Everything you need to integrate Candor's deception detection into your application. Start with a free API key — no credit card required.

Overview

Candor exposes a single REST endpoint: POST /api/analyze. Send text, get back a deception score (0–100), a verdict, five linguistic signal breakdowns, and a list of flagged sentences — all in one call.

The API uses linguistic analysis grounded in peer-reviewed deception research (Newman 2003, DePaulo 2003, Pennebaker 2011). It's designed for programmatic use: fraud detection pipelines, content integrity systems, legal analysis tools, and anywhere reliable language analysis adds value.

💡
New here? Jump straight to Your First Request — it takes about 2 minutes to get a live response.

Base URL

https://getcandor.polsia.app

Request format

All requests are JSON over HTTPS. Set Content-Type: application/json on every request.

Authentication

Every request needs your API key. You get one automatically when you create a free account at getcandor.polsia.app/auth. API keys look like this:

ck_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6

Pass your key in the X-API-Key request header on every call:

HTTP HEADER
X-API-Key: ck_your_api_key_here
⚠️
Keep your key secret. Don't commit it to git or paste it into public forums. If your key is compromised, sign in to your account and contact support to rotate it.

What happens without a key?

Requests without a valid X-API-Key header return a 401 Unauthorized error. The API doesn't allow anonymous calls — all usage is tracked to your account for billing purposes.

Your First Request

1
Create a free account

Go to getcandor.polsia.app/auth and sign up. Your API key is generated immediately.

2
Copy your API key

You'll see your key on the dashboard, formatted as ck_.... Copy it.

3
Make your first call

Replace YOUR_API_KEY below and run this in your terminal:

CURL
curl -X POST https://getcandor.polsia.app/api/analyze \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -d '{"text": "I definitely did not take the money. I was nowhere near the office that day."}'
4
Read the response

You'll get back a JSON object with a score, verdict, signals, and flagged sentences. See Response Format for a full breakdown.

POST /api/analyze

Analyzes a text string for deception signals and returns a structured report.

Request body

text
string · required
The text to analyze. Minimum 10 characters, maximum 10,000 characters. Shorter texts (under 20 words) produce lower-confidence results since there's less signal to analyze.

Example requests

CURL
curl -X POST https://getcandor.polsia.app/api/analyze \
  -H "Content-Type: application/json" \
  -H "X-API-Key: ck_your_api_key_here" \
  -d '{"text": "The transaction was entirely legitimate and authorized by all parties involved."}'
PYTHON
import requests

api_key = "ck_your_api_key_here"
text = "The transaction was entirely legitimate and authorized by all parties involved."

response = requests.post(
    "https://getcandor.polsia.app/api/analyze",
    headers={
        "Content-Type": "application/json",
        "X-API-Key": api_key,
    },
    json={"text": text}
)

data = response.json()

if data["success"]:
    analysis = data["analysis"]
    print(f"Score: {analysis['score']}/100")
    print(f"Verdict: {analysis['verdict']}")
    print(f"Deceptive: {analysis['is_deceptive']}")
else:
    print(f"Error: {data['message']}")
JAVASCRIPT
const response = await fetch("https://getcandor.polsia.app/api/analyze", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "ck_your_api_key_here",
  },
  body: JSON.stringify({
    text: "The transaction was entirely legitimate and authorized by all parties involved.",
  }),
});

const data = await response.json();

if (data.success) {
  const { score, verdict, is_deceptive, signals } = data.analysis;
  console.log(`Score: ${score}/100 — ${verdict}`);
  console.log(`Deceptive: ${is_deceptive}`);
} else {
  console.error(`Error: ${data.message}`);
}

Response Format

A successful response always has "success": true and an analysis object.

JSON RESPONSE
{
  "success": true,
  "analysis": {
    "score": 67,
    "confidence": 0.85,
    "verdict": "high_risk",
    "is_deceptive": true,
    "signals": {
      "pronoun_distancing": {
        "score": 72,
        "finding": "Low first-person pronoun usage suggests psychological distancing from the claims."
      },
      "hedging": {
        "score": 48,
        "finding": "Moderate use of tentative language reduces commitment to stated facts."
      },
      "detail_specificity": {
        "score": 61,
        "finding": "Vague temporal and spatial references; lacks sensory detail expected in truthful recall."
      },
      "cognitive_complexity": {
        "score": 71,
        "finding": "Simple sentence structures may indicate reduced cognitive load from not constructing truthful narrative."
      },
      "emotional_leakage": {
        "score": 69,
        "finding": "Incongruent emotional tone — over-emphasis on legitimacy without organic elaboration."
      }
    },
    "flagged_sentences": [
      {
        "text": "The transaction was entirely legitimate and authorized by all parties involved.",
        "reason": "Over-assertion of legitimacy without supporting context; scripted-sounding.",
        "severity": "high"
      }
    ],
    "summary": "The text shows multiple deception indicators across linguistic dimensions."
  },
  "meta": {
    "input_length": 79,
    "processing_time_ms": 2847,
    "model": "candor-v1",
    "tier": "free",
    "usage_remaining": 47
  }
}

Field reference

analysis.score
integer · 0–100
Overall deception score. 0 = no signals detected, 100 = strong signals across all dimensions. Most normal text scores 10–25. Scores ≥ 42 are flagged as deceptive.
analysis.confidence
float · 0.0–1.0
Model's confidence in the analysis. Lower for very short texts (<20 words). A score of 0.9 means high confidence; 0.4 means the text was ambiguous.
analysis.verdict
string · enum
Human-readable risk level. One of: low_risk (0–25), moderate_risk (26–41), high_risk (42–75), very_high_risk (76–100).
analysis.is_deceptive
boolean
true when score ≥ 30. Recalibrated against a 227-sample LIAR political-speech corpus (precision=56%, recall=51%, F1=0.534). Use this for simple pass/fail decisions.
analysis.signals
object
Five sub-scores (0–100 each) with a one-sentence finding for each. See The 5 Signals.
analysis.flagged_sentences
array
Sentences from the input that contain notable deception markers. Each has text, reason, and severity ("low", "medium", "high"). May be empty if no individual sentences stand out.
analysis.summary
string
One-paragraph natural-language summary of the analysis findings.
meta.usage_remaining
integer
How many analyses remain in your current billing period. Only present when authenticated with an API key.

The 5 Signals

Each signal maps to a dimension identified in peer-reviewed deception research. Scores are 0–100 per signal; the overall score is a weighted composite.

Signal Key field What it measures
Pronoun Distancing pronoun_distancing Reduced use of "I", "me", "my" signals psychological distancing from statements. Liars avoid ownership of their claims. (Newman 2003, Pennebaker 2011)
Hedging & Uncertainty hedging Tentative language ("maybe", "perhaps", "sort of", "I think") signals lack of commitment to claims — the writer leaving room to retreat.
Detail Specificity detail_specificity Truthful accounts contain sensory detail, temporal markers, spatial references. Deceptive accounts tend to be vague or suspiciously scripted. (DePaulo 2003)
Cognitive Complexity cognitive_complexity Lying requires more mental effort, producing detectable linguistic artifacts: simpler structures, fewer embedded clauses. (Vrij et al. 2010)
Emotional Leakage emotional_leakage Incongruent emotional tone — emotions that don't fit the context, over-assertion without organic elaboration. (Hancock et al. 2004)

Error Codes

All errors return JSON with "success": false and a human-readable message.

Code Meaning How to fix it
400 Bad Request Missing text field, text too short (<10 chars), or text too long (>10,000 chars). Check your request body.
401 Unauthorized Missing or invalid X-API-Key header. Double-check you're sending the header and that the key matches exactly what's shown in your dashboard.
402 Limit Reached You've used your monthly analysis quota. Limits reset on the 1st of every month. Upgrade your plan for more.
429 Rate Limited Too many requests. Free plan allows 20 requests/minute. Wait 60 seconds, then retry. Add exponential backoff for production systems.
500 Server Error Analysis failed on our end. Retry once — these are usually transient. If it persists, check our status or contact support.

Error response shape

JSON ERROR
{
  "success": false,
  "message": "Monthly limit reached. Upgrade to continue."
}

Rate Limits

Rate limits are per API key, applied on a rolling 60-second window.

Plan Requests / minute Notes
Free 20 req/min Sufficient for testing and low-volume integrations.
Pro 60 req/min Suitable for most production workloads.
Enterprise Custom Contact us to discuss custom limits for high-volume pipelines.

Handling 429s in code

Use exponential backoff when you receive a 429. Here's a simple pattern:

PYTHON · RETRY WITH BACKOFF
import time, requests

def analyze_with_retry(text, api_key, max_retries=3):
    for attempt in range(max_retries):
        response = requests.post(
            "https://getcandor.polsia.app/api/analyze",
            headers={"X-API-Key": api_key},
            json={"text": text}
        )
        if response.status_code == 429:
            wait = 2 ** attempt  # 1s, 2s, 4s
            time.sleep(wait)
            continue
        return response.json()
    raise Exception("Rate limit exceeded after retries")

Plans & Limits

Monthly analysis quotas reset on the 1st of each month. View full pricing →

Free
$0
forever
50 analyses / month
20 req/min rate limit
All 5 signals
Full JSON response
Enterprise
Custom
volume pricing
50,000+ analyses / month
Custom rate limits
SLA & dedicated support
Custom model tuning

Troubleshooting

Getting a 401 even with the right key?

Check that you're using the header name X-API-Key (not Authorization). Also confirm there are no extra spaces before or after the key value. Copy directly from your dashboard to avoid typos.

Score seems too high / too low?

The model is calibrated so that most normal text scores 10–25. If you're getting unexpectedly high scores on text you believe is truthful, consider whether the text is highly formal, legalistic, or scripted — those styles can trigger some signals. Very short texts (<20 words) also tend to produce noisier scores; check the confidence field.

Response is slow?

The first request after a period of inactivity may take 5–15 seconds (cold start). Subsequent requests typically return in 2–4 seconds. If you consistently see latency over 10 seconds, contact support.

No flagged_sentences in the response?

This is normal. The model only flags sentences with genuine, specific deception markers. A moderate overall score can occur from subtle signals spread across the text without any single sentence standing out. Check the signals object for which dimensions drove the score.

Request body appears empty?

Make sure you're setting the Content-Type: application/json header and that your JSON is valid. If using curl, wrap the JSON in single quotes (not double) to avoid shell interpolation issues.

FAQ

Is my text stored or used for training?
Candor stores the first 500 characters of each analyzed text for internal quality monitoring and abuse prevention. We do not use your data to train models or share it with third parties. See our Privacy Policy for full details.
Can I use Candor for high-stakes decisions (insurance claims, legal proceedings)?
Candor is a probabilistic tool, not a definitive lie detector. Use it as one input in a broader review process, not as the sole basis for consequential decisions. See our Disclaimer for details on appropriate use.
Does the API work for languages other than English?
The model is trained on English-language text and performs best on English. Non-English inputs will be processed but accuracy will be significantly lower. Multi-language support is on our roadmap.
Do my monthly quotas roll over?
No. Unused analyses do not carry over to the next month. Quotas reset on the 1st of each calendar month regardless of when your billing cycle started.
What's the maximum text length?
10,000 characters per request (roughly 1,500–2,000 words). For longer documents, split them into passages and analyze each separately, then aggregate the scores. Note that context-splitting may affect some signals like emotional consistency.
How do I get an Enterprise plan?
Email support@getcandor.app with your use case and estimated monthly volume. We'll get back to you within 24 hours.
Can I regenerate my API key?
Key rotation isn't available via the dashboard yet. If your key is compromised, email support@getcandor.app and we'll rotate it manually within a few hours.