API documentation

Read CitePep's trust records from your own system: who authored, reviewed, corrected or verified a page, and the evidence behind it. A simple JSON API over HTTPS.

Quickstart

Create a key in your panel, then check it works:

curl https://api.citepep.com/api/v1/whoami \ -H "X-API-Key: cp_live_your_key_here"

A valid key returns:

{ "success": true, "data": { "authenticated": true, "user_id": 42, "key_id": 7 } }

Base URL

https://api.citepep.com/api/v1

All requests must use HTTPS. Responses are JSON, and every response carries a success boolean; payloads are nested under data.

Getting an API key

  1. Sign in to CitePep.
  2. Go to Settings in your panel (contributor or publisher).
  3. Under API keys, give the key a name and select Create key.
  4. Copy the key immediately.

The key is shown only once. We store only a hash of it, so we cannot show it or recover it again. If you lose a key, revoke it and create a new one.

Keys look like cp_live_<64 hex characters>. Treat a key like a password: keep it server-side, never commit it to a repository, and never ship it in browser or mobile code, where anyone can read it. Revoke a key the moment you suspect it leaked; revocation takes effect immediately.

Authentication

Send your key on every request, either as a dedicated header:

X-API-Key: cp_live_your_key_here

or as a bearer token, which most HTTP clients and SDKs use by default:

Authorization: Bearer cp_live_your_key_here

A key acts as your account. A publisher's key can register that publisher's pages, open requests on them, and accept work, and nothing else. Reading the public reputation graph does not require a key, because that data is already public on the site.

Rate limits

Key-authenticated requests are limited per key, so one integration can never throttle another. The current budget is 10 requests per second sustained, with bursts up to 60. Exceeding it returns 429 with a Retry-After header.

Integrating your system

These are the endpoints an external system actually needs. They address pages by URL, because that is what your CMS has. You never need to know CitePep's internal IDs.

Look up a page's trust record by URL

GET /pages/lookup?url=https://yoursite.com/post

The call most integrations make on every page render: who authored, reviewed or corrected this page, and where the public proof lives. No key required.

curl "https://api.citepep.com/api/v1/pages/lookup?url=https://yoursite.com/post"

If we have no record of the page, this is not an error. You get has_record: false so you can branch on it:

{ "success": true, "data": { "url": "https://yoursite.com/post", "title": "Your post", "has_record": true, "registered": true, "record_url": "https://citepep.com/pages/yoursite/your-post", "publisher": { "slug": "yoursite", "name": "Your Site", "verified": true }, "contributors": { "correction": [ { "username": "jane-doe", "name": "Jane Doe", "level": "expert", "trust_score": 82, "summary": "Removed an unsourced statistic.", "profile_url": "https://citepep.com/co/jane-doe", "proof_url": "https://citepep.com/co/jane-doe/contributions/your-post-correction" } ] } }}

URL matching tolerates a trailing slash, the scheme, and host casing, so the URL your CMS stores does not have to match ours byte for byte.

Every write endpoint takes one object or an array. Send a single item to create one thing, or an array to submit a whole batch in one call (up to 500). This is the same API the CitePep panel itself uses, so anything the panel can do, your system can do.

Register or update pages

POST /pages

Puts pages on the record so they can receive contributions. Upsert by URL: sending a URL we already hold updates it instead of failing, so you can re-send your whole catalogue on every publish without tracking what you sent before.

curl -X POST https://api.citepep.com/api/v1/pages \ -H "X-API-Key: cp_live_your_key_here" \ -H "Content-Type: application/json" \ -d '[ { "url": "https://yoursite.com/a", "title": "Post A", "topic": "health" }, { "url": "https://yoursite.com/b", "title": "Post B", "topic": "science" } ]'

An array always answers with a per-item result, so one bad row never discards the batch:

{ "success": true, "data": { "results": [ { "index": 0, "status": "created", "id": 371, "url": "https://yoursite.com/a" }, { "index": 1, "status": "updated", "id": 372, "url": "https://yoursite.com/b" } ], "summary": { "created": 1, "updated": 1 } }}

Per-item status is one of created, updated, skipped (nothing changed) or failed (with an error). A single object gets a single result back, not an array.

List your pages

GET /pages?page=1&per_page=50

Every page registered to your publication, with its trust record. Use it to reconcile your CMS against CitePep. per_page is capped at 100.

Open review requests

POST /requests

Ask for a review, edit, correction or source. Name the page by url (registering it if it is new) or by article_ref_id. One object or an array.

curl -X POST https://api.citepep.com/api/v1/requests \ -H "X-API-Key: cp_live_your_key_here" \ -H "Content-Type: application/json" \ -d '[ { "url": "https://yoursite.com/a", "request_type": "correction", "title": "Check this page for accuracy", "brief": "Verify the statistics in section 2.", "topic": "health" } ]'

request_type is one of reviewer, fact_checker, editor or correction.

Accept or reject submitted work

POST /submissions/decide

Read what contributors submitted (their evidence: before/after, reason and sources) from GET /publisher-auth/requests, then decide. Accepting mints the public contribution record. One object or an array, so you can clear a queue in one call.

curl -X POST https://api.citepep.com/api/v1/submissions/decide \ -H "X-API-Key: cp_live_your_key_here" \ -H "Content-Type: application/json" \ -d '[ { "submission_id": 9, "decision": "accept", "note": "Good catch, applied." }, { "submission_id": 10, "decision": "reject", "note": "Not supported by the source." } ]'

Webhooks

Rather than polling, register an endpoint and we will call it when something happens on your account.

curl -X POST https://api.citepep.com/api/v1/account/webhooks \ -H "Authorization: Bearer <your session token>" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yoursite.com/hooks/citepep", "events": ["contribution.accepted"] }'

The response contains a signing secret (whsec_...), shown once. Events:

EventFires when
contribution.acceptedYou accepted work on a page. Its public trust record changed, so refresh any cached attribution block.
submission.receivedA contributor responded to one of your requests.
page.registeredA page was added to your publication.

Verifying a webhook

Every delivery carries these headers:

X-CitePep-Event: contribution.acceptedX-CitePep-Timestamp: 1750000000X-CitePep-Signature: sha256=<hmac>

The signature is an HMAC-SHA256 over "<timestamp>.<raw body>" using your signing secret. Verify it before trusting the payload, and reject old timestamps to prevent replay:

const crypto = require("crypto");function verify(req, secret) { const ts = req.headers["x-citepep-timestamp"]; const sig = req.headers["x-citepep-signature"]; const body = req.rawBody; // the RAW bytes, not the parsed JSON const expected = "sha256=" + crypto .createHmac("sha256", secret) .update(ts + "." + body) .digest("hex"); // Constant-time compare, and refuse anything older than five minutes. const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300; return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));}

Respond with any 2xx status. A delivery that keeps failing is retried on subsequent events; an endpoint that fails 20 times in a row is disabled, and you can re-enable it once it is fixed.

Reading the reputation graph

These endpoints are open: they return the same information the public pages already show. A key is not required.

List contributors

GET /contributors?topic=seo&q=jane&page=1

Filter with topic (expertise) and q (name search); paginate with page. The response carries a meta object with total, page and per_page.

curl "https://api.citepep.com/api/v1/contributors?topic=seo"

Returns contributors with their reputation, expertise and latest accepted contribution. Emails are never included in public responses.

Get one contributor

GET /contributors/{username}

The full profile, per-topic authority scores, and history: their accepted contributions, each with the page, the publisher, and the proof record.

List publishers

GET /publishers?page=1

Get one publisher

GET /publishers/{slug}

The publication, its pages on record, and the contributors who worked on them.

Get a page by internal id

GET /articles/{id}

The same record as /pages/lookup, addressed by CitePep's internal id. Prefer lookup by URL unless you already hold the id.

Attribution embed

GET /embed/article/{id}

Returns a ready-to-display attribution block for one of your pages, as { "html": "..." }. Add ?format=js to get a self-injecting script instead, which keeps the block up to date automatically:

<div id="citepep-attribution"></div><script src="https://api.citepep.com/api/v1/embed/article/123?format=js" async></script>

This endpoint is CORS-open and cached for 5 minutes, so you can call it directly from a browser.

Open review requests

GET /review-requests

The public marketplace of open work. Filter with topic and type.

Errors

Errors use standard HTTP status codes and return a JSON body with an error message.

StatusMeaning
400The request was malformed.
401The API key is missing, invalid, revoked or expired.
403The key is valid but not allowed to do this.
404No such contributor, publisher or page.
429Too many requests. Slow down and retry.
500Something broke on our side.

Managing keys programmatically

Keys are managed with your normal session token, not with an API key: you cannot mint a key using a key.

EndpointDoes
GET /account/api-keysLists your keys. Secrets are never returned, only the prefix.
POST /account/api-keysCreates a key. Accepts name and optional expires_in_days. Returns the secret once.
DELETE /account/api-keys/{id}Revokes a key immediately.
GET /account/webhooksLists your webhooks and their delivery health.
POST /account/webhooksRegisters an https endpoint. Returns the signing secret once.
DELETE /account/webhooks/{id}Removes a webhook.

Support

Building something with CitePep? Get in touch and tell us what you need. If an endpoint you want is missing, we would rather hear it than have you work around it.