v1.0 Documentation
Integrate AI-assisted research credibility screening into your applications with our comprehensive REST API
https://www.verifyscience.co.uk
All API requests require authentication using your API key in the Authorization header as a Bearer token.
Authorization: Bearer vs_live_your_api_key_here
Choose the plan that fits your needs. All plans include the web dashboard with a shared verification quota. Reading your account and verification history via the API (GET /v1/me, GET /v1/verifications) works on every plan; creating verifications via the API (POST /v1/verifications) requires Professional or Enterprise.
| Tier | Price | Verifications/Month | Rate Limit | Batch API |
|---|---|---|---|---|
| Free | ยฃ0 | 3 | 5 requests/minute | โ |
| Starter | ยฃ4.99/month | 199 | 20 requests/minute | โ |
| Professional | ยฃ9.99/month | 499 | 60 requests/minute | โ up to 20 papers |
| Enterprise | ยฃ19.99/month | 1,999 | 120 requests/minute | โ up to 50 papers |
429 Too Many Requests response.
GET /v1/me
Retrieve information about your account, including subscription tier and remaining verifications.
{
"user_id": "user_abc123",
"email": "[email protected]",
"subscription": "professional",
"verifications_left": 485,
"verifications_limit": 499,
"rate_limit": "60 requests/minute",
"billing_mode": "byok",
"enabled_providers": ["anthropic", "openai", "google"],
"provider_keys": {
"anthropic": { "status": "active", "is_default": true, "key_hint": null, "legacy": true },
"openai": { "status": "active", "is_default": false, "key_hint": "9f2a" },
"google": { "status": "not_configured", "is_default": false, "key_hint": null }
},
"org": null
}
enabled_providers / provider_keys: Your account's resolved provider entitlement (tier default, unless your account or organisation has an override) and the verified-key status of each โ mirrors what the web app's Settings panel shows. provider_keys never contains key material, only status/hint. See Model & Provider Selection below.
org: Present only for accounts belonging to an organisation โ includes your role and the org's own resolved entitlement/key hints. null for individual accounts.
By default, verifications run on your plan's standard model โ no fields required, and nothing below changes that. If you'd rather run the analysis on a different AI model using your own API key, POST /v1/verifications accepts two optional fields. This is a choice of which AI model performs the analysis, not a quality tier โ VerifyScience does not rank or recommend one model over another.
| Field | Type | Description |
|---|---|---|
| provider | string ยท optional | One of "anthropic", "openai", "google", "mistral", "deepseek". Must be in your plan's entitlement (see table below) and you must have a verified, active key stored for it. Omit this field entirely to use your account's standard provider. |
| model | string ยท optional | A specific catalog model id (e.g. an alternative Anthropic model). If you send provider without model, that provider's standard model for your tier is used automatically. Selecting a model or provider requires bring-your-own-key (BYOK) billing. |
| Tier | Providers you may select |
|---|---|
| Free / Starter | Anthropic only (the standard model โ no selection needed) |
| Professional | Anthropic, OpenAI, Google |
| Enterprise | Anthropic, OpenAI, Google, Mistral, DeepSeek |
{
"content": "https://journals.plos.org/plosmedicine/article?id=10.1371/journal.pmed.0020124",
"method": "url",
"provider": "openai"
}
No silent substitution: if the requested provider or model can't run (no verified key, plan doesn't include it, or the provider is unavailable), the request is rejected with a clear error โ VerifyScience never silently falls back to a different provider than the one you asked for. See the provider-specific error codes below.
POST /v1/verifications
Submit a research paper for AI-powered methodological analysis. Returns a job ID immediately; poll for results.
{
"content": "https://journals.plos.org/plosmedicine/article?id=10.1371/journal.pmed.0020124",
"method": "url"
}
| Field | Type | Description |
|---|---|---|
| content | string ยท required | The paper to verify. URL, DOI URL, or raw text. Minimum 50 characters. For DOI links, use the full resolved article URL (e.g. journals.plos.org/...) rather than a bare doi.org/ link โ short DOIs will be rejected. |
| method | string ยท optional | "url" (default) or "text". Use "url" when content is a URL; "text" when pasting raw paper content. |
| provider | string ยท optional | Run this verification on a different AI provider using your own key. See Model & Provider Selection. |
| model | string ยท optional | A specific catalog model id. See Model & Provider Selection. |
DOI links: Use the full article page URL, not the short doi.org redirect. Open-access papers on PLOS, PubMed Central, arXiv, and bioRxiv work reliably. Paywalled papers should be submitted as text โ paste the abstract and methods section directly.
{
"id": "job_dc2f5b45-73ac-420e-9eac-a78c2f9a0abe",
"status": "processing",
"message": "Verification started successfully"
}
If you sent provider and/or model, the response also includes requested_provider/requested_model โ omitted entirely when no selection was made.
GET /v1/verifications/{id}
Retrieve the results of a verification job by its ID.
{
"id": "job_dc2f5b45-73ac-420e-9eac-a78c2f9a0abe",
"status": "processing",
"progress": 45
}
{
"id": "job_dc2f5b45-73ac-420e-9eac-a78c2f9a0abe",
"status": "completed",
"result": {
"confidence": 78,
"confidence_level": "High",
"summary": "This research demonstrates strong methodology...",
"domain_category": "Clinical trial methodology",
"domain_detail": "Randomised controlled trial",
"criteria": [
{
"name": "Analytical Dimension",
"score": 85,
"assessment": "Adequate sample size for statistical power"
},
{
"name": "Methodology",
"score": 82,
"assessment": "Well-designed experimental approach"
}
],
"strengths": ["Large sample size", "Controlled variables"],
"limitations": ["Limited follow-up period"],
"created_at": "2026-03-25T00:30:00Z"
},
"provider": "anthropic",
"model": "claude-sonnet-5",
"disclosure": "This verification uses Claude AI (Anthropic) to analyze research."
}
provider / model / disclosure fields: The AI provider and model that actually produced this result (never the requested value โ if a request is re-validated and falls back, these reflect what really ran), plus a plain-language disclosure string for your own transparency/EU AI Act Article 50 obligations. Present on every completed result, including historical ones (defaults to Anthropic/Claude for results predating this field).
confidence field: A holistic assessment of the paper's credibility (0–100), constrained to within ±20 points of the mean of the per-criterion analysis scores. It reflects the paper's methodological rigour, publication context, post-publication record, and weight of subsequent evidence. A server-side guardrail enforces this constraint; if the AI-generated score deviates beyond the ±20 band, it is clamped and the original value is preserved in confidence_original.
domain_category field: Abstracted research domain detected from the paper โ e.g. "Clinical trial methodology", "Qualitative research", "Computational / engineering research". Returned for all tiers. Displayed in the UI for Professional and Enterprise subscribers only.
domain_detail field: Specific methodology type โ e.g. "Randomised controlled trial", "Phenomenological interview study". Returned for Professional and Enterprise tiers only.
framework_applied field: The primary quality assessment framework applied. Returned for Enterprise tier only under contract. Not present in this example response.
criteria_applicability field: Per-criterion applicability map โ values are "standard", "adapted", or "not applicable". Returned for Professional and Enterprise tiers only. Not present in this example response.
GET /v1/verifications
Retrieve a list of all your verification jobs with pagination support.
limit (optional) - Number of results per page (default: 50, max: 100)offset (optional) - Number of results to skip (default: 0){
"verifications": [
{
"id": "job_dc2f5b45-73ac-420e-9eac-a78c2f9a0abe",
"status": "completed",
"confidence": 78,
"provider": "anthropic",
"model": "claude-sonnet-5",
"created_at": "2026-03-25T00:30:00Z"
},
{
"id": "job_e4a1b2c3-d4e5-f6g7-h8i9-j0k1l2m3n4o5",
"status": "processing",
"progress": 45,
"created_at": "2026-03-24T22:15:00Z"
}
],
"total": 14,
"limit": 50,
"offset": 0
}
For Enterprise organisations managing provider access programmatically (provisioning scripts, not a human clicking Settings). Both endpoints require an API key belonging to an owner or admin of the organisation โ membership and role are checked fresh on every call, not cached on the key.
POST /v1/org/key
Set or remove the organisation's shared API key for a given provider. Body: { "provider": "openai", "apiKey": "sk-..." }, or { "provider": "openai", "apiKey": null } to remove. provider defaults to "anthropic". Never returns key material back โ only a hint (last 4 characters).
POST /v1/org/entitlements
Set or clear which providers the whole organisation may select, overriding the tier default for every member. Body: { "providers": ["anthropic", "openai", "mistral"] }, or { "providers": null } to clear and revert every member to their own tier default.
The API uses standard HTTP status codes to indicate success or failure.
Request succeeded
Invalid request parameters or missing required fields
Missing or invalid API key
API key valid but lacks permission for this resource
Requested resource doesn't exist
Rate limit exceeded - wait before retrying
Something went wrong on our end - please try again
Returned only when you send an optional provider/model field โ see Model & Provider Selection. Never returned on the default (no-selection) path.
PROVIDER_NOT_ENTITLED
Your plan doesn't include the requested provider.
MISSING_PROVIDER_KEY
No verified, active key is stored for the requested provider.
MODEL_REQUIRES_BYOK
Selecting a model or provider requires bring-your-own-key billing.
MODEL_TIER_NOT_ELIGIBLE
Your plan doesn't include this specific model.
MODEL_PROVIDER_MISMATCH
The requested model doesn't belong to the requested provider โ send only one, or make sure they agree.
{
"error": "Invalid API key",
"message": "The provided API key is invalid or has been revoked",
"code": "invalid_api_key"
}
POST /v1/compare
Submit two papers for side-by-side methodological comparison. Returns two job IDs immediately — poll each independently using GET /v1/verifications/{id}. Consumes 2 verifications. Requires Professional or Enterprise plan.
{
"content_a": "https://journals.plos.org/plosmedicine/article?id=10.1371/journal.pmed.0020124",
"content_b": "https://journals.plos.org/plosmedicine/article?id=10.1371/journal.pmed.1001743"
}
| Field | Type | Description |
|---|---|---|
| content_a | string · required | First paper. URL or raw text. Minimum 50 characters. Use full article page URL for DOIs. |
| content_b | string · required | Second paper. URL or raw text. Minimum 50 characters. Use full article page URL for DOIs. |
{
"status": "ok",
"job_a": "job_dc2f5b45-73ac-420e-9eac-a78c2f9a0abe",
"job_b": "job_f1a3c921-11bb-4c2d-b711-889900aabbcc",
"poll_a": "/v1/verifications/job_dc2f5b45-73ac-420e-9eac-a78c2f9a0abe",
"poll_b": "/v1/verifications/job_f1a3c921-11bb-4c2d-b711-889900aabbcc",
"verifications_consumed": 2,
"message": "Both papers queued for analysis. Poll each job independently using the poll URLs."
}
Polling: Both jobs run in parallel. Poll poll_a and poll_b independently every 9–15 seconds. Professional tier takes 2–4 minutes per paper.
import requests, time
API_KEY = 'vs_live_your_api_key_here'
BASE = 'https://www.verifyscience.co.uk'
H = {'Authorization': f'Bearer {API_KEY}', 'Content-Type': 'application/json'}
r = requests.post(f'{BASE}/v1/compare', headers=H, json={
'content_a': 'https://journals.plos.org/plosmedicine/article?id=10.1371/journal.pmed.0020124',
'content_b': 'https://journals.plos.org/plosmedicine/article?id=10.1371/journal.pmed.1001743'
})
data = r.json()
job_a, job_b = data['job_a'], data['job_b']
def poll(job_id):
for _ in range(80):
d = requests.get(f'{BASE}/v1/verifications/{job_id}', headers=H).json()
if d.get('status') == 'completed': return d['result']
if d.get('status') == 'failed': raise Exception(d.get('error'))
time.sleep(9)
raise TimeoutError('Job timed out')
result_a, result_b = poll(job_a), poll(job_b)
delta = result_a['confidence'] - result_b['confidence']
print(f"A: {result_a['confidence']} B: {result_b['confidence']} Delta: {delta:+d}")
POST /api/verify-batch
Submit a list of paper URLs for parallel verification. Processing runs in the background โ results are delivered to your registered email address when complete. The response returns a batch_id for status polling.
{
"csv_content": "https://pubmed.ncbi.nlm.nih.gov/123456/\nhttps://www.nature.com/articles/example\nhttps://doi.org/10.1038/s41586-026-00001-1",
"method": "url"
}
csv_content
required ยท string
One URL or DOI per line. Blank lines are ignored. A header row starting with url, doi, or title is automatically detected and skipped.
method
optional ยท string ยท default: "url"
Input type. Accepted values: "url", "text".
{
"batch_id": "batch_550e8400-e29b-41d4-a716-446655440000",
"total_papers": 3,
"status": "queued",
"message": "Batch verification queued successfully",
"poll_url": "/api/verify-batch-status?batch_id=batch_550e8400-e29b-41d4-a716-446655440000",
"estimated_time_minutes": 2
}
{
"status": "error",
"error": {
"code": "BATCH_TOO_LARGE",
"message": "Batch exceeds limit for professional tier",
"details": {
"papers_submitted": 25,
"max_allowed": 20,
"tier": "professional"
},
"next_steps": [
"Reduce to 20 papers or less",
"Enterprise plan supports up to 50 papers per batch"
]
}
}
GET /api/verify-batch-status?batch_id={batch_id}
Poll for real-time batch processing progress. The recommended polling interval is 5 seconds. When should_poll is false, processing is complete and polling should stop.
batch_id
required ยท string
The batch ID returned from the submit endpoint. Format: batch_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
include_details
optional ยท boolean ยท default: false
When true, includes full verification results for all completed papers in the response.
{
"batch_id": "batch_550e8400-e29b-41d4-a716-446655440000",
"status": "processing",
"total_papers": 3,
"completed_papers": 1,
"failed_papers": 0,
"progress_percent": 33,
"success_rate": 100,
"created_at": "2026-04-05T08:30:00Z",
"started_at": "2026-04-05T08:30:05Z",
"estimated_seconds_remaining": 120,
"should_poll": true,
"poll_interval_seconds": 5
}
{
"batch_id": "batch_550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"total_papers": 3,
"completed_papers": 3,
"failed_papers": 0,
"progress_percent": 100,
"success_rate": 100,
"created_at": "2026-04-05T08:30:00Z",
"started_at": "2026-04-05T08:30:05Z",
"completed_at": "2026-04-05T08:32:18Z",
"duration_seconds": 133,
"should_poll": false,
"verifications": [
{
"id": "job_dc2f5b45-73ac-420e-9eac-a78c2f9a0abe",
"batch_index": 0,
"status": "completed",
"result": {
"title": "Effects of Exercise on Cognitive Function in Older Adults",
"confidence": 82,
"summary": "Well-designed RCT with adequate statistical power...",
"analysis": [
{ "criterion": "Analytical Dimension", "score": 85, "explanation": "..." },
{ "criterion": "Assessment Area", "score": 80, "explanation": "..." }
]
},
"completed_at": "2026-04-05T08:31:02Z"
}
]
}
| Plan | Batch API Access | Max papers per batch | Processing | Results delivery |
|---|---|---|---|---|
| Free | โ | โ | โ | โ |
| Starter | โ | โ | โ | โ |
| Professional | โ | 20 | Parallel ยท 5 concurrent | Email + in-app |
| Enterprise | โ | 50 | Parallel ยท 5 concurrent | Email + in-app |
Batch jobs consume your monthly verification quota. A 20-paper batch deducts 20 verifications from your allowance. The background processor runs every 60 seconds and applies a 30-minute timeout to any stalled job.
// Start a verification
const startVerification = async () => {
const response = await fetch('https://www.verifyscience.co.uk/v1/verifications', {
method: 'POST',
headers: {
'Authorization': 'Bearer vs_live_your_api_key_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({
text: 'Your research text here...',
metadata: {
title: 'Study Title',
source: 'Journal Name'
}
})
});
const data = await response.json();
console.log('Verification started:', data.id);
// Poll for results
return pollForResults(data.id);
};
// Check verification status
const pollForResults = async (jobId) => {
const response = await fetch(`https://www.verifyscience.co.uk/v1/verifications/${jobId}`, {
headers: {
'Authorization': 'Bearer vs_live_your_api_key_here'
}
});
const data = await response.json();
if (data.status === 'completed') {
console.log('Verification complete!', data.result);
return data.result;
} else {
console.log('Still processing...', data.progress, '%');
// Wait 2 seconds and check again
await new Promise(resolve => setTimeout(resolve, 2000));
return pollForResults(jobId);
}
};
startVerification();
import requests
import time
API_KEY = 'vs_live_your_api_key_here'
BASE_URL = 'https://www.verifyscience.co.uk'
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json'
}
# Start verification
response = requests.post(
f'{BASE_URL}/v1/verifications',
headers=headers,
json={
'text': 'Your research text here...',
'metadata': {
'title': 'Study Title',
'source': 'Journal Name'
}
}
)
data = response.json()
job_id = data['id']
print(f'Verification started: {id}')
# Poll for results
while True:
response = requests.get(
f'{BASE_URL}/v1/verifications/{id}',
headers=headers
)
data = response.json()
if data['status'] == 'completed':
print('Verification complete!')
print(f"Confidence: {data['result']['confidence']}%")
print(f"Summary: {data['result']['summary']}")
break
else:
print(f"Still processing... {data.get('progress', 0)}%")
time.sleep(2)
const axios = require('axios');
const API_KEY = 'vs_live_your_api_key_here';
const BASE_URL = 'https://www.verifyscience.co.uk';
const client = axios.create({
baseURL: BASE_URL,
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
}
});
async function verifyResearch(text, metadata = {}) {
try {
// Start verification
const { data: startData } = await client.post('/v1/verifications', {
text,
metadata
});
console.log('Verification started:', startData.id);
// Poll for results
while (true) {
const { data: statusData } = await client.get(
`/v1/verifications/${startData.id}`
);
if (statusData.status === 'completed') {
console.log('Verification complete!');
return statusData.result;
}
console.log(`Progress: ${statusData.progress}%`);
await new Promise(resolve => setTimeout(resolve, 2000));
}
} catch (error) {
console.error('Error:', error.response?.data || error.message);
throw error;
}
}
// Example usage
verifyResearch(
'Your research text here...',
{ title: 'Study Title', source: 'Journal Name' }
).then(result => {
console.log('Confidence:', result.confidence);
console.log('Summary:', result.summary);
});
import requests
import time
API_KEY = 'vs_live_your_api_key_here'
BASE_URL = 'https://www.verifyscience.co.uk'
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json'
}
# Submit a batch of paper URLs (Professional: max 20, Enterprise: max 50)
urls = [
'https://pubmed.ncbi.nlm.nih.gov/123456/',
'https://www.nature.com/articles/example',
'https://doi.org/10.1038/s41586-026-00001-1'
]
response = requests.post(
f'{BASE_URL}/api/verify-batch',
headers=headers,
json={
'csv_content': '\n'.join(urls),
'method': 'url'
}
)
data = response.json()
batch_id = data['batch_id']
print(f'Batch submitted: {batch_id}')
print(f'Estimated time: {data["estimated_time_minutes"]} minutes')
# Poll for results every 5 seconds
while True:
response = requests.get(
f'{BASE_URL}/api/verify-batch-status',
headers=headers,
params={
'batch_id': batch_id,
'include_details': 'true'
}
)
status = response.json()
print(f'Progress: {status["progress_percent"]}% '
f'({status["completed_papers"]}/{status["total_papers"]} complete)')
if not status.get('should_poll', True):
print('\nBatch complete!')
for v in status.get('verifications', []):
if v['status'] == 'completed':
title = v['result'].get('title', 'Untitled')
confidence = v['result'].get('confidence', 0)
print(f' [{confidence}%] {title}')
else:
print(f' [FAILED] Paper {v["batch_index"] + 1}: {v.get("error")}')
break
time.sleep(status.get('poll_interval_seconds', 5))
const API_KEY = 'vs_live_your_api_key_here';
const BASE_URL = 'https://www.verifyscience.co.uk';
const headers = {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
};
// Submit batch โ Professional: max 20 URLs, Enterprise: max 50
const urls = [
'https://pubmed.ncbi.nlm.nih.gov/123456/',
'https://www.nature.com/articles/example',
'https://doi.org/10.1038/s41586-026-00001-1'
];
const submitBatch = async () => {
const res = await fetch(`${BASE_URL}/api/verify-batch`, {
method: 'POST',
headers,
body: JSON.stringify({
csv_content: urls.join('\n'),
method: 'url'
})
});
const { batch_id, estimated_time_minutes } = await res.json();
console.log(`Batch submitted: ${batch_id}`);
console.log(`Estimated time: ${estimated_time_minutes} minutes`);
// Poll until complete
while (true) {
const statusRes = await fetch(
`${BASE_URL}/api/verify-batch-status?batch_id=${batch_id}&include_details=true`,
{ headers }
);
const status = await statusRes.json();
console.log(`Progress: ${status.progress_percent}% ` +
`(${status.completed_papers}/${status.total_papers})`);
if (!status.should_poll) {
console.log('Batch complete!');
for (const v of status.verifications || []) {
if (v.status === 'completed') {
console.log(` [${v.result.confidence}%] ${v.result.title}`);
} else {
console.log(` [FAILED] Paper ${v.batch_index + 1}: ${v.error}`);
}
}
break;
}
await new Promise(r => setTimeout(r, (status.poll_interval_seconds || 5) * 1000));
}
};
submitBatch();
Have questions or need assistance integrating the VerifyScience API? We're here to help!