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/v1All requests must use HTTPS. Responses are JSON, and every response carries a success boolean; payloads are nested under data.
Getting an API key
- Sign in to CitePep.
- Go to Settings in your panel (contributor or publisher).
- Under API keys, give the key a name and select Create key.
- 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_hereor as a bearer token, which most HTTP clients and SDKs use by default:
Authorization: Bearer cp_live_your_key_hereA 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/postThe 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 /pagesPuts 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=50Every 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 /requestsAsk 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/decideRead 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:
| Event | Fires when |
|---|---|
contribution.accepted | You accepted work on a page. Its public trust record changed, so refresh any cached attribution block. |
submission.received | A contributor responded to one of your requests. |
page.registered | A 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=1Filter 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=1Get 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-requestsThe 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.
| Status | Meaning |
|---|---|
400 | The request was malformed. |
401 | The API key is missing, invalid, revoked or expired. |
403 | The key is valid but not allowed to do this. |
404 | No such contributor, publisher or page. |
429 | Too many requests. Slow down and retry. |
500 | Something 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.
| Endpoint | Does |
|---|---|
GET /account/api-keys | Lists your keys. Secrets are never returned, only the prefix. |
POST /account/api-keys | Creates a key. Accepts name and optional expires_in_days. Returns the secret once. |
DELETE /account/api-keys/{id} | Revokes a key immediately. |
GET /account/webhooks | Lists your webhooks and their delivery health. |
POST /account/webhooks | Registers 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.