The Civly Intelligence API is JSON over HTTPS. Everything on this page is public, and the only thing you need from us is an API key. Keys are issued by hand to each partner and carry the products you have agreed with Civly.

Products

Each product is a small set of endpoints under the base URL. Your key lists the products it may call, and a call to any other product returns 403.

ProductWhat it doesHow it answersKey access
Research reportsA full sourced research document on a candidate or public figure, plus the raw research data behind it.Job: submit, then pollbig_book
Social media analysisOne analysis across up to fourteen social platforms and web archives, with a per-platform summary and a Word document of the whole run.Job: submit, then pollsocial_analysis
Bulk data accessRead-only access to Civly's full research database as a Parquet export you query with DuckDB or SQL.Instant responsedata_access
Court opinion searchFull-text search over Civly's copy of U.S. federal and state court opinions, and the full text of any one.Instant responseopinion_search
Epstein corpus screeningScreen a person and their associates against the DOJ and House Oversight Epstein document release and a curated watchlist.Instant responseepstein_screening
Adverse screeningScreen a person against federal and international exclusion, enforcement, debarment and sanctions lists, and the DOJ press-release archive.Instant responseadverse_screening
Municipal meeting searchSearch speaker-attributed transcripts of city council and county board meetings, with a link that plays each moment.Instant responsemeeting_search
Clip feedPull the clips in your own Clip Library into your dashboards or warehouse: one flat row per clip, with lasting play and thumbnail links that need no login.Instant responseclip_feed
NIL deal pricingFair-market-value estimates for college athlete NIL deals, priced line by line against published, dated rates.Instant responsenil_pricing

Get access

  1. Request a key with the access form. Tell us what you are building and which products you need. You can also send the same request from code with POST /access-request.
  2. We review it. A person at Civly reads every request; there is no self-serve signup.
  3. You receive your key over a secure channel, never by plain email, along with a signing secret if you use webhooks. The key starts with cvly_pk_ and is shown once, so store it in a secrets manager when it arrives.

Each key carries its own access level: which products it may call, a monthly submission quota for research reports and another for social media analysis, and a cap on how many reports can run at once. Keys work against the production API at the base URL above.

Authentication

Send your key on every request, in either of these headers:

Authorization: Bearer cvly_pk_your_key
X-API-Key: cvly_pk_your_key

The one exception is the clip feed’s play and thumbnail links, which are signed for a single clip and need no key; see Clip feed. A missing, invalid or revoked key returns 401:

{"detail": "Partner API key required. Provide via Authorization: Bearer <key> or X-API-Key header."}

Keep keys on your server. Never put one in browser code, a mobile app or source control. If a key leaks, tell us and we revoke it; a revoked key fails on its very next request. Each key only ever sees its own company’s jobs.

Making requests

Request and response bodies are JSON, so send Content-Type: application/json with every POST. Dates are YYYY-MM-DD and timestamps are ISO 8601 in UTC.

The attestation field

Every endpoint that researches a named person requires this exact field in the body:

"attestation": "subject-is-candidate-or-public-figure"

Sending it affirms that the subject is a candidate for public office or a public figure and that the research is for a lawful purpose. A request without it is rejected with 422, and every submission is recorded in an audit log. The endpoint reference marks it as required wherever it applies. Searches of public records (court opinions, meeting transcripts) and NIL pricing do not take it.

Idempotency keys

Research reports and social media analysis are slow, and they are billed when they start. Send an Idempotency-Key header with a value you choose once per logical submission, such as your own order id:

Idempotency-Key: order-7f3a

If a request times out or your process restarts, retry with the same value and you get the original job back, marked "idempotent_replay": true, instead of starting and paying for a second one. The value is scoped to the product, so the same string on /reports and /social-analysis names two different submissions. Use a new value for a genuinely new submission, including a resubmission after a failure.

Long-running jobs

Two products run as jobs: research reports and social media analysis. Every other endpoint answers in the same request.

  1. Submit. POST /reports or POST /social-analysis returns 202 with a job_id within a couple of seconds. Store it: it is your receipt, and it is what invoices reference.
  2. Poll. Check GET /reports/{job_id} or GET /social-analysis/{group_id} every 30 to 60 seconds, and no faster. A report’s status is one of queued, running, completed or failed. For reports, a webhook can replace polling.
  3. Fetch the result. GET /reports/{job_id}/result or GET /social-analysis/{group_id}/results.

A research report takes about 5 to 10 minutes. Its raw research data (raw_data_url) is ready the moment the job completes. The finished document goes through a human quality review first: until an analyst releases it, document_url is null and document_status reads in_review. Review runs on a human timescale, hours rather than seconds, so check back occasionally rather than polling hard. Download links are presigned and last an hour; call the result endpoint again for a fresh one.

Social media analysis usually finishes in a few minutes. Each platform runs on its own, so a run where one platform failed and the rest succeeded is normal: read the per-platform statuses. Once the run is done, the results also carry a link to a Word document covering the whole run.

Two 409 responses are part of the normal flow. Asking for a report result before the report completes returns 409; keep polling. Submitting a subject that already has a report running for your company also returns 409, naming the running job_id; wait for that one to finish.

Webhooks

Give us an HTTPS URL when you request access, or any time after, and Civly sends a signed POST to it when one of your reports finishes:

{
  "event": "report.completed",
  "delivery_id": "d6c1a0f2-...",
  "product": "big_book",
  "job_id": "1234",
  "status": "completed",
  "timestamp": "2026-10-02T14:12:40Z"
}

The events are report.completed and report.failed. The body never contains report content: fetch it from the result endpoint with your key. report.completed means generation finished, so the raw data is ready but the document may still be in review.

Every delivery carries an X-Civly-Signature header of the form t=<unix timestamp>,v1=<hex>: an HMAC-SHA256 of the timestamp, a period and the raw request body, keyed with your webhook secret. Check it before trusting the body:

import hashlib
import hmac
import time


def verify(secret: str, header: str, body: bytes) -> bool:
    """True if X-Civly-Signature matches the raw request body and is under five minutes old."""
    try:
        parts = dict(item.split("=", 1) for item in header.split(","))
        ts, sig = parts["t"], parts["v1"]
    except (KeyError, ValueError):
        return False
    if abs(time.time() - int(ts)) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{ts}.".encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)

Respond with any 2xx within 10 seconds, and do the real work after you have answered. A failed delivery is retried after 1, 5, 15, 30 and 60 minutes, then dropped, so treat polling as the fallback. The same event can arrive twice; use delivery_id to discard repeats.

Pagination

Most responses are a complete object, so there is nothing to page through. Three endpoints return lists in pages:

Both searches rank results best first and return up to max_results (1 to 50, default 15) per call.

For work across whole datasets, use bulk data access instead: your own Parquet export, queried with DuckDB or SQL, with no per-call limits.

Clip feed

The clip feed brings the clips in your own Clip Library into your dashboards or data warehouse. Each clip is one flat row with no nested objects, so it maps straight to a CSV row, and its clip_id never changes, so use it as the row key. Only your company’s own collections are included.

Pulling only what is new

  1. Call GET /clips with after_id=0 on the first run.
  2. While has_more is true, call again with after_id set to the next_after_id you just received.
  3. Store the last next_after_id. Start the next run from it and you receive only clips added since. When nothing is new, clips is empty and next_after_id repeats your after_id.

A clip enters the feed five minutes after it is created, so a run never sees one that is still being saved. Each clip is sent once: a clip edited later is not sent again. If you need later edits, re-pull from an older after_id now and then and deduplicate on clip_id. The Python examples include a scheduled pull that writes a CSV.

Play and thumbnail links

Every row carries a play_url and a thumbnail_url. They last, they need no key and no login, and you can store them and show them to your own clients. Opening one redirects to a video or image link that expires within an hour, so embed the links as they are and never store where they redirect. A play link opens the cut clip when there is one, and otherwise the full source video at the clip’s moment.

Rate limits and quotas

Your key’s product limits also answer 429, with an error and a message that say which limit you hit:

{
  "detail": {
    "error": "Monthly quota reached",
    "message": "This key has used 20 of 20 big_book submissions this month. Contact Civly to increase your quota.",
    "product": "big_book",
    "quota": 20,
    "used": 20
  }
}

Errors

Errors come back as JSON with a detail field. For a validation error (422), detail lists each problem and the field it is in:

{
  "detail": [
    {"loc": ["body", "attestation"], "msg": "Field required", "type": "missing"}
  ]
}
StatusMeaningWhat to do
401Key missing, invalid or revokedCheck the header is Authorization: Bearer cvly_pk_... or X-API-Key, with no stray whitespace or newline in the key.
402Account out of creditContact Civly. Retrying will not help.
403Product not enabled for this keyContact Civly to add the product to your key.
404Not foundCheck the id. Keys only see their own company’s jobs, so another company’s job id is a 404 too.
409Not ready, or already runningA report result asked for too early, or a second report on a subject already in progress. See Long-running jobs.
422Validation errorMost often a missing or misspelled attestation. The body names the field.
429Rate limit or product limitSee Rate limits and quotas. Honor Retry-After when present.
503Clip still being preparedOnly from a clip play link: the TV clip’s video is still being converted. Open the link again after Retry-After (60 seconds).
5xxServer errorRetry with exponential backoff. If a submission may have gone through, retry with the same Idempotency-Key.

A job that seems stuck usually is not. A report queued for more than about five minutes is waiting on a busy queue and clears on its own. A failed report can be resubmitted with a new Idempotency-Key; if the same subject fails repeatedly, send us the job_id. An expired download link just needs a fresh call to the result endpoint.

Python examples

These use the requests library (pip install requests) and read your key from the CIVLY_API_KEY environment variable. Start with this helper, which every example below uses. It retries rate limits and server errors and raises on anything else.

import os
import time

import requests

BASE = "https://app.civly.ai/api/v1/partner"
SESSION = requests.Session()
SESSION.headers["Authorization"] = f"Bearer {os.environ['CIVLY_API_KEY']}"


def call(method, path, **kwargs):
    """Make one API call and return the parsed JSON.

    Waits out rate limits and retries server errors. Send an Idempotency-Key header
    on submissions, so a retried POST can never start a second job.
    """
    for attempt in range(5):
        resp = SESSION.request(method, BASE + path, timeout=120, **kwargs)
        if resp.status_code == 429 and "Retry-After" in resp.headers:
            time.sleep(int(resp.headers["Retry-After"]))
            continue
        if resp.status_code >= 500:
            time.sleep(2 ** attempt)
            continue
        if resp.status_code >= 400:
            raise RuntimeError(f"{resp.status_code} on {method} {path}: {resp.text}")
        return resp.json()
    resp.raise_for_status()

Screen a list of names and write the results to a CSV

Reads names.csv (one column headed name), screens each name with adverse screening, and writes one row per name with its strongest hit.

import csv

with open("names.csv", newline="") as f:
    names = [row["name"].strip() for row in csv.DictReader(f) if row["name"].strip()]

disclaimer = ""
with open("screening_results.csv", "w", newline="") as f:
    writer = csv.writer(f)
    writer.writerow([
        "name", "matched", "hit_count", "top_list", "top_record_name",
        "name_similarity", "record_link", "doj_press_mentions",
    ])
    for name in names:
        result = call("POST", "/adverse-screening/screen", json={
            "subject_name": name,
            "attestation": "subject-is-candidate-or-public-figure",
        })
        disclaimer = result["disclaimer"]
        for row in result["results"]:
            top = max(row["hits"], key=lambda hit: hit["confidence"], default=None)
            writer.writerow([
                row["name"],
                row["matched"],
                len(row["hits"]),
                top["source_label"] if top else "",
                top["full_name"] if top else "",
                top["confidence"] if top else "",
                top["detail_url"] if top else "",
                row["doj_mention_count"],
            ])

# A hit is a possible same-name match, not proof. Show this with any match you surface.
print(disclaimer)

Run a research report from start to finish

import time

job = call(
    "POST",
    "/reports",
    headers={"Idempotency-Key": "order-1042"},  # one value per submission, e.g. your order id
    json={
        "subject_name": "Jane Example",
        "subject_type": "candidate",
        "office": "US Senate",
        "state": "OH",
        "attestation": "subject-is-candidate-or-public-figure",
    },
)
job_id = job["job_id"]  # store this: it is your receipt

while True:
    status = call("GET", f"/reports/{job_id}")
    if status["status"] in ("completed", "failed"):
        break
    print(status.get("progress") or status["status"])
    time.sleep(45)  # every 30 to 60 seconds, no faster

if status["status"] == "failed":
    raise SystemExit(f"Report {job_id} failed: {status.get('error_message')}")

result = call("GET", f"/reports/{job_id}/result")

# Presigned links: plain downloads, no API key, valid for an hour.
raw = requests.get(result["raw_data_url"], timeout=300)
raw.raise_for_status()
with open(f"report-{job_id}-data.json", "wb") as f:
    f.write(raw.content)

if result["document_status"] == "available":
    print("Document:", result["document_url"])
else:
    print("The document is in quality review. Call the result endpoint again later.")

Page through meeting moments into a CSV

import csv

search = {"query": "\"short-term rentals\"", "state": "CA", "max_results": 50}
moments, offset = [], 0
while True:
    page = call("POST", "/meetings/search", json={**search, "offset": offset})
    moments.extend(page["results"])
    offset += len(page["results"])
    if not page["results"] or offset >= page["total_results"]:
        break

columns = [
    "event_date", "municipality", "state", "body_name", "speaker_name",
    "timestamp", "quote", "clip_url", "citation",
]
with open("meeting_moments.csv", "w", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=columns, extrasaction="ignore")
    writer.writeheader()
    writer.writerows(moments)

print(f"Wrote {len(moments)} moments")

Pull new clips into a CSV on a schedule

Run it from cron or any scheduler. Each run picks up where the last one stopped and writes only the clips added since.

import csv
import json
from pathlib import Path

STATE = Path("civly_clips_state.json")  # where the last run stopped
after_id = json.loads(STATE.read_text())["after_id"] if STATE.exists() else 0

rows = []
for _ in range(50):  # at most 25,000 clips a run; a bigger backlog finishes over the next runs
    page = call("GET", "/clips", params={"after_id": after_id, "limit": 500})
    rows += page["clips"]
    after_id = page["next_after_id"]
    if not page["has_more"]:
        break

if rows:
    with open("civly_clips.csv", "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=list(rows[0]))
        writer.writeheader()
        writer.writerows(rows)

STATE.write_text(json.dumps({"after_id": after_id}))  # save only after the CSV is written
print(f"{len(rows)} new clips")

Receive a webhook

A minimal Flask receiver using the verify function from Webhooks:

import os

from flask import Flask, abort, request

app = Flask(__name__)


@app.post("/civly-webhook")
def civly_webhook():
    signature = request.headers.get("X-Civly-Signature", "")
    if not verify(os.environ["CIVLY_WEBHOOK_SECRET"], signature, request.get_data()):
        abort(400)
    event = request.get_json()
    # Answer within 10 seconds: hand the event to a queue, then fetch the
    # result with call("GET", f"/reports/{event['job_id']}/result") from there.
    print(event["event"], event["job_id"], event["delivery_id"])
    return "", 204

Endpoint reference

Every partner endpoint, with its parameters, request body, response fields and an example of each. This section is rebuilt from the API’s own OpenAPI spec, so it lists exactly what the API accepts today. Paths are relative to the base URL.

Research reports

A full sourced research document on a candidate or public figure, plus the raw research data behind it. Your key needs the big_book product.

POST/reports

Submit a subject for a full research report

Start a full research report (Big Book) for a candidate or public figure.

Requires the attestation field. Generation runs asynchronously for 5-10 minutes; poll GET /reports/{job_id} or register a completion webhook, then fetch the document from GET /reports/{job_id}/result.

Headers
Idempotency-Keystring or null

Optional client-chosen key; retries with the same key return the original job.

Request body
subject_namestringrequired

Name of the research subject. 2–255 characters.

subject_typestring

One of candidate, donor, organization. Default "candidate".

officestring or null

Office held or sought (e.g. 'US Senate'). Up to 255 characters.

districtstring or null

Up to 255 characters.

citystring or null

Up to 255 characters.

statestring or null

State (2-letter code or full name). Up to 50 characters.

partystring or null

Up to 50 characters.

contextstring or null

Free-text research guidance. Up to 5,000 characters.

date_range_startdate or null
date_range_enddate or null
twitter_handlearray of strings or null
youtube_handlearray of strings or null
instagram_handlearray of strings or null
tiktok_handlearray of strings or null
bluesky_handlearray of strings or null
facebook_handlearray of strings or null
substack_handlearray of strings or null
truthsocial_handlearray of strings or null
fec_committee_namestring or null

Up to 255 characters.

legislator_namestring or null

Up to 255 characters.

attestationstringrequired

Required safety attestation. Must be the exact string 'subject-is-candidate-or-public-figure', affirming the subject is a candidate for public office or a public figure and the research is lawful.

Example request
curl -X POST "https://app.civly.ai/api/v1/partner/reports" \
  -H "Authorization: Bearer $CIVLY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-7f3a" \
  -d '{
    "subject_name": "Jane Example",
    "subject_type": "candidate",
    "office": "US Senate",
    "state": "OH",
    "party": "Republican",
    "twitter_handle": [
      "janeexample"
    ],
    "attestation": "subject-is-candidate-or-public-figure"
  }'
Response 202
4 response fields
job_idstringrequired
productstringrequired

One of big_book, social_analysis.

statusstringrequired

One of queued, running, completed, failed.

idempotent_replayboolean

True when this response replays a previously created job (Idempotency-Key hit). Default false.

Example response

{
  "job_id": "1234",
  "product": "big_book",
  "status": "running",
  "idempotent_replay": false
}

GET/reports/{job_id}

Get report status

Poll generation progress. Statuses: queued, running, completed, failed.

Path parameters
job_idintegerrequired
Example request
curl "https://app.civly.ai/api/v1/partner/reports/1234" \
  -H "Authorization: Bearer $CIVLY_API_KEY"
Response 200
4 response fields
job_idstringrequired
statusstringrequired

One of queued, running, completed, failed.

progressstring or null

Human-readable progress message.

error_messagestring or null

Example response

{
  "job_id": "1234",
  "status": "running",
  "progress": "Generating content... (12/45)",
  "error_message": null
}

GET/reports/{job_id}/result

Get report download links

Fetch short-lived download links for a completed report. Returns 409 if the report is not finished yet - keep polling the status endpoint.

raw_data_url is ready as soon as the report completes. The polished document clears a human quality review first: document_url stays null and document_status reads "in_review" until it is released.

Path parameters
job_idintegerrequired
Example request
curl "https://app.civly.ai/api/v1/partner/reports/1234/result" \
  -H "Authorization: Bearer $CIVLY_API_KEY"
Response 200
6 response fields
job_idstringrequired
statusstringrequired

One of queued, running, completed, failed.

document_statusstring

Lifecycle of the polished document: 'in_review' while it is being prepared and quality-reviewed, 'available' once document_url is live, 'unavailable' when no document will be produced for this report. One of available, in_review, unavailable. Default "in_review".

document_urlstring or null

Short-lived presigned URL for the finished document; null until document_status is 'available'.

raw_data_urlstring or null

Short-lived presigned URL for the raw data appendix.

expires_in_secondsinteger

Lifetime of the presigned URLs. Default 3600.

Example response

{
  "job_id": "1234",
  "status": "completed",
  "document_status": "in_review",
  "document_url": null,
  "raw_data_url": "https://storage.example/reports/1234/raw.json?signature=...",
  "expires_in_seconds": 3600
}

Social media analysis

One analysis across up to fourteen social platforms and web archives, with a per-platform summary and a Word document of the whole run. Your key needs the social_analysis product.

POST/social-analysis

Submit social handles for multi-platform analysis

Run unified social analysis across the selected platforms. Requires the attestation field. The 202 returns as soon as the job is accepted; per-platform dispatch and billing run right after the response, so platforms_queued means accepted-for-dispatch - poll the status endpoint for live per-platform state.

Headers
Idempotency-Keystring or null

Optional client-chosen key; retries with the same key return the original job.

Request body
person_namestringrequired
platformsarray of strings

Platforms to analyze. Omit to run the full default set (twitter, youtube, instagram, tiktok, reddit, substack, bluesky, facebook, truthsocial, rumble, twitch, linkedin, archive). Billing is per platform queued, so omitting this field bills for the entire default set.

search_modestring or null

Default "account".

search_querystring or null
objectivestring or null
start_datedate or null
end_datedate or null
screen_for_concernsboolean

Default false.

twitterobject or null
4 fields inside twitter
usernamestring or null
max_tweetsinteger

Default 20.

include_retweetsboolean

Default false.

include_repliesboolean

Default false.

youtubeobject or null
6 fields inside youtube
youtube_handlestring or null
max_videosinteger

Default 10.

auto_transcribeboolean

Default true.

auto_transcribe_countinteger

Default 10.

additional_contextstring or null
date_presetstring or null
instagramobject or null
4 fields inside instagram
usernamestring or null
max_reelsinteger

Default 10.

auto_transcribeboolean

Default true.

auto_transcribe_countinteger

Default 10.

redditobject or null
5 fields inside reddit
topicstring or null
subredditsarray of strings or null
highlight_keywordsarray of strings or null
upvote_thresholdinteger

Default 10.

max_postsinteger

Default 20.

tiktokobject or null
8 fields inside tiktok
usernamestring or null
max_videosinteger

Default 10.

include_transcriptsboolean

Default true.

include_liked_videosboolean

Default true.

max_liked_videosinteger

Default 10.

include_repostsboolean

Default false.

max_repostsinteger or null

From 1 to 10,000.

additional_contextstring or null
substackobject or null
6 fields inside substack
analysis_modestring

Default "topic".

author_urlstring or null
topic_keywordstring or null
max_postsinteger

Default 10.

max_authorsinteger

Default 10.

research_objectivestring or null
blueskyobject or null
2 fields inside bluesky
usernamestring or null
max_postsinteger

Default 20.

instagram_imagesobject or null
3 fields inside instagram_images
usernamestring or null
max_postsinteger

Default 50.

additional_contextstring or null
youtube_imagesobject or null
5 fields inside youtube_images
youtube_handlestring or null
max_videosinteger

Default 25.

frames_per_videointeger

Default 6.

additional_contextstring or null
date_presetstring or null
facebookobject or null
3 fields inside facebook
usernamestring or null
usernamesarray of strings or null
max_postsinteger

Default 20.

truthsocialobject or null
2 fields inside truthsocial
usernamestring or null
max_postsinteger

Default 20.

mastodonobject or null
2 fields inside mastodon
usernamestring or null
max_postsinteger

Default 20.

telegramobject or null
2 fields inside telegram
usernamestring or null
max_postsinteger

Default 20.

rumbleobject or null
2 fields inside rumble
usernamestring or null
max_postsinteger

Default 20.

twitchobject or null
3 fields inside twitch
usernamestring or null
max_postsinteger

Default 20.

transcribe_max_minutesinteger or null
snapchatobject or null
2 fields inside snapchat
usernamestring or null
max_postsinteger

Default 0.

threadsobject or null
2 fields inside threads
usernamestring or null
max_postsinteger

Default 500.

linkedinobject or null
1 field inside linkedin
usernamestring or null
political_emailsobject or null
1 field inside political_emails
max_postsinteger

Default 50.

archiveobject or null
2 fields inside archive
handlesarray of strings or null
max_postsinteger

Default 25.

venmoobject or null
1 field inside venmo
usernamestring or null
attestationstringrequired

Required safety attestation. Must be the exact string 'subject-is-candidate-or-public-figure', affirming the subject is a candidate for public office or a public figure and the research is lawful.

Example request
curl -X POST "https://app.civly.ai/api/v1/partner/social-analysis" \
  -H "Authorization: Bearer $CIVLY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: social-7f3a" \
  -d '{
    "person_name": "Jane Example",
    "platforms": [
      "twitter",
      "youtube",
      "reddit"
    ],
    "twitter": {
      "username": "janeexample",
      "max_tweets": 50
    },
    "attestation": "subject-is-candidate-or-public-figure"
  }'
Response 202
6 response fields
job_idstringrequired

Analysis group id - use for status/results polling.

productstring

Default "social_analysis".

statusstringrequired

One of queued, running, completed, failed.

platforms_queuedarray of strings
platforms_skippedarray of strings
idempotent_replayboolean

Default false.

Example response

{
  "job_id": "6f1c2a9e-3b7d-4c51-9a0e-2d8f4b7c1e55",
  "product": "social_analysis",
  "status": "queued",
  "platforms_queued": [
    "twitter",
    "youtube",
    "reddit"
  ],
  "platforms_skipped": [],
  "idempotent_replay": false
}

GET/social-analysis/{group_id}

Get analysis status

Poll normalized per-platform status (pending, in_progress, completed, failed).

Path parameters
group_idstringrequired
Example request
curl "https://app.civly.ai/api/v1/partner/social-analysis/6f1c2a9e-3b7d-4c51-9a0e-2d8f4b7c1e55" \
  -H "Authorization: Bearer $CIVLY_API_KEY"
Response 200
8 response fields
group_idstringrequired
person_namestringrequired
overall_statusstringrequired
total_platformsintegerrequired
completed_countintegerrequired
failed_countintegerrequired
in_progress_countintegerrequired
platformsarray of objectsrequired
4 fields inside platforms
platformstringrequired
analysis_idintegerrequired
statusstringrequired
error_messagestring or null

Example response

{
  "group_id": "6f1c2a9e-3b7d-4c51-9a0e-2d8f4b7c1e55",
  "person_name": "Jane Example",
  "overall_status": "in_progress",
  "total_platforms": 3,
  "completed_count": 2,
  "failed_count": 0,
  "in_progress_count": 1,
  "platforms": [
    {
      "platform": "twitter",
      "analysis_id": 9101,
      "status": "completed",
      "error_message": null
    },
    {
      "platform": "youtube",
      "analysis_id": 9102,
      "status": "completed",
      "error_message": null
    },
    {
      "platform": "reddit",
      "analysis_id": 9103,
      "status": "in_progress",
      "error_message": null
    }
  ]
}

GET/social-analysis/{group_id}/results

Get analysis results

Per-platform summaries for all completed platforms in the analysis.

Path parameters
group_idstringrequired
Example request
curl "https://app.civly.ai/api/v1/partner/social-analysis/6f1c2a9e-3b7d-4c51-9a0e-2d8f4b7c1e55/results" \
  -H "Authorization: Bearer $CIVLY_API_KEY"
Response 200
9 response fields
group_idstringrequired
person_namestringrequired
overall_statusstringrequired
platformsarray of objectsrequired
19 fields inside platforms
platformstringrequired
analysis_idintegerrequired
statusstringrequired
summarystring or null
risk_levelstring or null
key_topicsarray of objects or null
posts_fetchedinteger or null
uploads_fetchedinteger or null
reposts_fetchedinteger or null
reposts_requestedboolean or null
reposts_completeboolean or null
error_messagestring or null
detail_urlstringrequired
content_itemsarray of objects or null
handle_usedstring or null
handle_sourcestring or null
discovery_confidencestring or null
identity_matchstring or null
review_itemsarray of objects or null
created_atdatetimerequired
cross_platform_summarystring or null
combined_themesarray of objects or null
document_urlstring or null
document_url_expires_in_secondsinteger or null

Example response

{
  "group_id": "6f1c2a9e-3b7d-4c51-9a0e-2d8f4b7c1e55",
  "person_name": "Jane Example",
  "overall_status": "completed",
  "created_at": "2026-10-02T14:05:11Z",
  "platforms": [
    {
      "platform": "twitter",
      "analysis_id": 9101,
      "status": "completed",
      "summary": "Posts center on manufacturing jobs and a state budget fight...",
      "risk_level": "low",
      "key_topics": [
        {
          "topic": "manufacturing",
          "count": 14
        }
      ],
      "posts_fetched": 50,
      "handle_used": "janeexample",
      "handle_source": "supplied",
      "detail_url": "https://app.civly.ai/social-analysis/9101"
    }
  ],
  "cross_platform_summary": "Consistent message across platforms...",
  "document_url": "https://storage.example/social/6f1c2a9e.docx?signature=...",
  "document_url_expires_in_seconds": 3600
}

Bulk data access

Read-only access to Civly's full research database as a Parquet export you query with DuckDB or SQL. Your key needs the data_access product.

GET/data/dictionary

Data dictionary for the bulk Parquet export

The data dictionary for your read-only Parquet export: one entry per dataset with a plain description, where it comes from, and the exact exported columns. Requires the data_access product.

Example request
curl "https://app.civly.ai/api/v1/partner/data/dictionary" \
  -H "Authorization: Bearer $CIVLY_API_KEY"
Response 200

A JSON object; the example shows its shape.

Example response

{
  "schema_version": "...",
  "generated_at": "2026-10-02T06:00:00+00:00",
  "description": "Partner-facing data dictionary for the Civly Parquet export...",
  "dataset_count": 1,
  "datasets": [
    {
      "table": "...",
      "description": "...",
      "source_type_label": "...",
      "expected_scale": "...",
      "columns": [
        "..."
      ],
      "column_count": 1
    }
  ]
}

GET/data/sources

Source catalog behind the bulk export

The catalog of data sources behind the bulk export, grouped by category with a plain description and rough scale for each. Built from Civly's source registry, so it is always current and never exposes internal table or pipeline names. Requires the data_access product.

Example request
curl "https://app.civly.ai/api/v1/partner/data/sources" \
  -H "Authorization: Bearer $CIVLY_API_KEY"
Response 200

A JSON object; the example shows its shape.

Example response

{
  "categories": [
    {
      "category": "...",
      "sources": [
        {
          "name": "...",
          "description": "...",
          "scale": "..."
        }
      ]
    }
  ],
  "note": "Read-only bulk access; query the parquet export with DuckDB."
}

GET/data/export

Your read-only Parquet export location

Where your read-only Parquet export lives, for querying directly with DuckDB/SQL. Returns 409 until Civly has provisioned your export bucket. Requires the data_access product.

Example request
curl "https://app.civly.ai/api/v1/partner/data/export" \
  -H "Authorization: Bearer $CIVLY_API_KEY"
Response 200
3 response fields
export_locationstringrequired

The partner's Parquet export location (s3://bucket/prefix path or a long-lived URL).

formatstring

Default "parquet".

accessstring

Default "read-only".

Example response

{
  "export_location": "s3://civly-partner-exports/your-company/",
  "format": "parquet",
  "access": "read-only"
}

Court opinion search

Full-text search over Civly's copy of U.S. federal and state court opinions, and the full text of any one. Your key needs the opinion_search product.

POST/opinions/search

Full-text search U.S. court opinions

Search Civly's copy of U.S. court opinions and get ranked results with snippets. Filter by court, judge, precedential status, date range, and minimum citation count. Requires the opinion_search product.

full_text_available is False in environments where only case-name/judge metadata is searchable; the search never silently pretends to have read the opinion body.

Request body
querystringrequired

Words or phrase to find in opinion body text. 2–500 characters.

max_resultsinteger

How many ranked results to return (1-50). From 1 to 50. Default 15.

court_idsarray of strings or null

Restrict to court ids, e.g. ['scotus', 'ca9'].

judgestring or null

Filter by judge name. Up to 200 characters.

precedential_statusstring or null

Filter by precedential status, e.g. 'Published'. Up to 50 characters.

date_fromstring or null

Only opinions filed on or after this date (YYYY-MM-DD).

date_tostring or null

Only opinions filed on or before this date (YYYY-MM-DD).

min_citationsinteger or null

Only opinions cited at least this many times. At least 0.

Example request
curl -X POST "https://app.civly.ai/api/v1/partner/opinions/search" \
  -H "Authorization: Bearer $CIVLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "qualified immunity excessive force",
    "max_results": 10,
    "court_ids": [
      "ca9",
      "scotus"
    ],
    "date_from": "2010-01-01",
    "date_to": "2020-12-31",
    "min_citations": 5
  }'
Response 200
4 response fields
querystring

Default "".

resultsarray of objects
12 fields inside results
opinion_idinteger or null

Stable id; pass to GET /opinions/{opinion_id}.

case_namestring or null
court_idstring or null

Court id, e.g. 'scotus'.

court_namestring or null
court_jurisdictionstring or null
judgesstring or null
date_filedstring or null

Date the opinion was filed (YYYY-MM-DD).

precedential_statusstring or null
citation_countinteger or null

How many later opinions cite this one.

docket_numberstring or null
source_urlstring or null

Public web page for this opinion.

snippetstring

Text excerpt around the first query-term match. Default "".

total_resultsinteger

Default 0.

full_text_availableboolean

True when results were ranked over full opinion text; False when only case name and judges metadata was searched. Default false.

Example response

{
  "query": "qualified immunity excessive force",
  "total_results": 10,
  "full_text_available": true,
  "results": [
    {
      "opinion_id": 812345,
      "case_name": "Doe v. City of Example",
      "court_id": "ca9",
      "court_name": "Court of Appeals for the Ninth Circuit",
      "court_jurisdiction": "F",
      "judges": "...",
      "date_filed": "2014-06-02",
      "precedential_status": "Published",
      "citation_count": 42,
      "docket_number": "12-56789",
      "source_url": "https://...",
      "snippet": "...the doctrine of qualified immunity does not shield..."
    }
  ]
}

GET/opinions/{opinion_id}

Fetch one full opinion by id

Fetch one opinion's full body text plus case metadata, by an opinion_id from a search result. Very long opinions are truncated to a fixed size cap, with truncated=True set on the response. Returns 404 if the id is not in Civly's copy. Requires the opinion_search product.

Path parameters
opinion_idintegerrequired
Example request
curl "https://app.civly.ai/api/v1/partner/opinions/812345" \
  -H "Authorization: Bearer $CIVLY_API_KEY"
Response 200
14 response fields
opinion_idinteger or null

Stable id; pass to GET /opinions/{opinion_id}.

case_namestring or null
court_idstring or null

Court id, e.g. 'scotus'.

court_namestring or null
court_jurisdictionstring or null
judgesstring or null
date_filedstring or null

Date the opinion was filed (YYYY-MM-DD).

precedential_statusstring or null
citation_countinteger or null

How many later opinions cite this one.

docket_numberstring or null
source_urlstring or null

Public web page for this opinion.

snippetstring

Text excerpt around the first query-term match. Default "".

opinion_textstring

Full opinion body text; may be truncated (see 'truncated'). Default "".

truncatedboolean

True when opinion_text was cut to the response size cap. Default false.

Example response

{
  "opinion_id": 812345,
  "case_name": "Doe v. City of Example",
  "court_id": "ca9",
  "date_filed": "2014-06-02",
  "citation_count": 42,
  "source_url": "https://...",
  "opinion_text": "Full opinion body text...",
  "truncated": false
}

Epstein corpus screening

Screen a person and their associates against the DOJ and House Oversight Epstein document release and a curated watchlist. Your key needs the epstein_screening product.

POST/epstein-screening/screen

Screen a person (and optional associates) against the Epstein corpus

Screen subject_name (plus any additional_names) against the Epstein document corpus and curated associate watchlist. Returns, per name, a cited match or an explicit no-match.

Every match is a POTENTIAL same-name reference that MUST be identity-verified before it is asserted; presence in these records is not evidence of wrongdoing, and presenting a hit as fact is defamatory. Always surface the returned disclaimer with any result. Requires the epstein_screening product.

Request body
subject_namestringrequired

Full name of the person to screen. 2–200 characters.

additional_namesarray of strings or null

Optional associated names to screen alongside the subject (business associates, family, etc.). Up to 40 items.

use_corpusboolean

Search the full corpus + watchlist (True) or only the curated watchlist (False). Default true.

attestationstringrequired

Required affirmation (the standard attestation token) that the subject is a candidate or public figure and the screening is for lawful political research.

Example request
curl -X POST "https://app.civly.ai/api/v1/partner/epstein-screening/screen" \
  -H "Authorization: Bearer $CIVLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subject_name": "Jane Q. Public",
    "additional_names": [
      "Acme Holdings LLC",
      "John Public"
    ],
    "use_corpus": true,
    "attestation": "subject-is-candidate-or-public-figure"
  }'
Response 200
3 response fields
resultsarray of objectsrequired
9 fields inside results
namestringrequired
matchedbooleanrequired
actionstringrequired

'flag' (assert-worthy hit), 'review' (weak, verify), or 'pass' (clean).

confidencenumberrequired
tierstringrequired

'watchlist', 'corpus_keyword', or 'none'.

doc_typesarray of strings
citationsarray of objects
3 fields inside citations
labelstringrequired
doc_typestring or null
doc_idstring or null

EFTA document id where available.

connectionsarray of strings
notestring

Default "".

corpus_document_countintegerrequired
disclaimerstringrequired

Example response

{
  "results": [
    {
      "name": "Jane Q. Public",
      "matched": false,
      "action": "pass",
      "confidence": 0.0,
      "tier": "none",
      "doc_types": [],
      "citations": [],
      "connections": [],
      "note": "No matches across the 383,000-document Epstein corpus."
    }
  ],
  "corpus_document_count": 383000,
  "disclaimer": "..."
}

Adverse screening

Screen a person against federal and international exclusion, enforcement, debarment and sanctions lists, and the DOJ press-release archive. Your key needs the adverse_screening product.

POST/adverse-screening/screen

Screen a person (and optional associates) against free federal / international bad-actor lists

Screen subject_name (plus any additional_names) against the warehoused adverse screening lists and the DOJ press-release archive. Returns, per name, cited hits or an explicit no-match, plus a sources_checked list with per-list freshness dates.

Every hit is a POTENTIAL same-name match that MUST be identity-verified before it is asserted; presence on a list is not evidence of wrongdoing by the person you screened, and presenting a hit as fact is defamatory. Always surface the returned disclaimer with any result. Requires the adverse_screening product.

Request body
subject_namestringrequired

Full name of the person to screen. 2–200 characters.

additional_namesarray of strings or null

Optional associated names to screen alongside the subject (employers, orgs, family). Up to 40 items.

attestationstringrequired

Required affirmation (the standard attestation token) that the subject is a candidate or public figure and the screening is for lawful political research.

Example request
curl -X POST "https://app.civly.ai/api/v1/partner/adverse-screening/screen" \
  -H "Authorization: Bearer $CIVLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subject_name": "Jane Q. Public",
    "additional_names": [
      "Acme Holdings LLC"
    ],
    "attestation": "subject-is-candidate-or-public-figure"
  }'
Response 200
3 response fields
resultsarray of objectsrequired
6 fields inside results
namestringrequired
matchedbooleanrequired
hitsarray of objects
18 fields inside hits
sourcestringrequired

List slug, e.g. 'hhs_oig_leie', 'fec_enforcement'.

source_labelstringrequired

Plain-English list name.

record_typestringrequired
categorystring or null
full_namestringrequired

The list entry's name (may be an alias row).

entity_typestringrequired

'individual' or 'entity'.

confidencenumberrequired

Name-similarity 0.0-1.0; identity is NOT verified.

org_namestring or null
case_idstring or null
detail_urlstring or null

Citation link for the record.

summarystring or null
dobstring or null

Date of birth where the list publishes one (verify identity).

dob_textstring or null
npistring or null
statestring or null
action_datestring or null
end_datestring or null
is_aliasboolean

Default false.

doj_mentionsarray of objects
4 fields inside doj_mentions
titlestringrequired
urlstring or null
pr_datestring or null
componentsarray of strings
doj_mention_countinteger

Default 0.

notestring

Default "".

sources_checkedarray of objects
6 fields inside sources_checked
sourcestringrequired
labelstringrequired
record_countinteger or null
as_ofstring or null
last_success_atstring or null
is_staleboolean

Default false.

disclaimerstringrequired

Example response

{
  "results": [
    {
      "name": "Jane Q. Public",
      "matched": false,
      "hits": [],
      "doj_mentions": [],
      "doj_mention_count": 0,
      "note": "No matches across the adverse screening lists or the DOJ press-release archive."
    }
  ],
  "sources_checked": [
    {
      "source": "hhs_oig_leie",
      "label": "HHS OIG healthcare exclusions (LEIE)",
      "record_count": 80000,
      "as_of": "2026-10-01T05:15:00+00:00",
      "last_success_at": "2026-10-01T05:15:00+00:00",
      "is_stale": false
    }
  ],
  "disclaimer": "..."
}

Municipal meeting search

Search speaker-attributed transcripts of city council and county board meetings, with a link that plays each moment. Your key needs the meeting_search product.

POST/meetings/search

Search municipal meeting transcripts for attributed moments

Search Civly's diarized municipal-meeting archive by keyword and/or speaker, with meeting-level filters (state, municipality, governing body, date range). Returns ranked moments; each carries a verbatim quote, the resolved speaker name, the meeting and date, a timestamp, and a clip link that plays the exact moment. Requires the meeting_search product.

Only moments with a resolved real speaker name are returned — a keyword hit spoken by an unresolved diarization label is never surfaced. Quotes are auto-transcribed: verify against the linked source video before asserting.

Request body
querystring or null

Keyword or phrase to find in what was said (websearch syntax; "quoted phrases" supported). 2–500 characters.

speakerstring or null

Speaker name, partial match (e.g. a council member). Up to 200 characters.

statestring or null

Two-letter state, e.g. 'CA'. 2–2 characters.

municipalitystring or null

Municipality name, partial match. Up to 200 characters.

body_namestring or null

Governing body, partial match. Up to 200 characters.

date_fromdate or null

Only meetings on or after this date (YYYY-MM-DD).

date_todate or null

Only meetings on or before this date (YYYY-MM-DD).

max_resultsinteger

How many ranked moments to return (1-50). From 1 to 50. Default 15.

offsetinteger

Result offset for paging through more than max_results moments. At least 0. Default 0.

Example request
curl -X POST "https://app.civly.ai/api/v1/partner/meetings/search" \
  -H "Authorization: Bearer $CIVLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "\"rezoning\" affordable housing",
    "state": "CA",
    "municipality": "Antioch",
    "date_from": "2023-01-01",
    "max_results": 10,
    "offset": 0
  }'
Response 200
4 response fields
querystring or null
speakerstring or null
resultsarray of objects
11 fields inside results
quotestringrequired

Verbatim passage as spoken (auto-transcribed; verify against the video).

speaker_namestringrequired

Resolved real speaker name; moments without one are never returned.

municipalitystring or null
statestring or null
body_namestring or null

Governing body, e.g. 'City Council'.

event_datedate or null

Date of the meeting (YYYY-MM-DD).

timestampstringrequired

Human-readable offset into the meeting video, e.g. '1:35:07'.

start_secondsintegerrequired

Offset into the meeting video in whole seconds.

source_urlstring or null

Public meeting video the moment is drawn from.

clip_urlstring or null

Link that opens the meeting video at this exact moment (deep-link).

citationstringrequired

Ready-to-quote citation, e.g. '[Antioch City Council, 12/19/23 @ 1:35:07]'.

total_resultsinteger

Default 0.

Example response

{
  "query": "\"rezoning\" affordable housing",
  "speaker": null,
  "total_results": 12,
  "results": [
    {
      "quote": "I move that we approve the rezoning for the affordable housing project on L Street.",
      "speaker_name": "Lamar Thorpe",
      "municipality": "Antioch",
      "state": "CA",
      "body_name": "City Council",
      "event_date": "2023-12-19",
      "timestamp": "1:35:07",
      "start_seconds": 5707,
      "source_url": "https://.../antioch-council-2023-12-19.mp4",
      "clip_url": "https://.../antioch-council-2023-12-19.mp4#t=5707",
      "citation": "[Antioch City Council, 12/19/23 @ 1:35:07]"
    }
  ]
}

Clip feed

Pull the clips in your own Clip Library into your dashboards or warehouse: one flat row per clip, with lasting play and thumbnail links that need no login. Your key needs the clip_feed product.

GET/clips

List your clips, oldest first, for a dashboard feed

Page through your company's clips in clip_id order. Store next_after_id after each run and send it as after_id next time to get only clips added since. Clips appear five minutes after they are created. Requires the clip_feed product.

Query parameters
after_idinteger

Return clips with clip_id above this; 0 starts at the start. At least 0. Default 0.

limitinteger

Clips per page (max 500). From 1 to 500. Default 100.

collectionstring or null

Only this collection (collection_slug).

Example request
curl "https://app.civly.ai/api/v1/partner/clips?after_id=18233&limit=500" \
  -H "Authorization: Bearer $CIVLY_API_KEY"
Response 200
3 response fields
clipsarray of objects
21 fields inside clips
clip_idintegerrequired

Stable Civly clip id; use it as the row key.

collectionstring or null

Collection name (usually one per client).

collection_slugstring or null

Collection identifier for the collection filter.

titlestring or null
quotestring or null

What was said (auto-transcribed; verify against the video).

speakerstring or null
categorystring or null
severityinteger or null
reasonstring or null

One-line reason the clip was flagged, when AI-found.

showstring or null

Program or show the clip aired on.

channelstring or null

Channel or station of the source video.

platformstring or null
air_datedate or null

Date the clip aired (YYYY-MM-DD).

start_secondsnumber or null

Clip start within the source video.

end_secondsnumber or null

Clip end within the source video.

duration_secondsnumber or null
tagsstring or null

Comma-separated tags.

source_urlstring or null

Original public video, when there is one.

play_urlstring or null

Lasting link that plays the clip; no login. Null when the clip has no video.

thumbnail_urlstring or null

Lasting link to a still image, when there is one.

created_atdatetimerequired
has_moreboolean

Default false.

next_after_idinteger or null

Highest clip_id on this page; store it and send it as after_id next time.

Example response

{
  "clips": [
    {
      "clip_id": 18234,
      "collection": "Acme for Congress",
      "collection_slug": "acme-for-congress",
      "title": "Smith on the gas tax",
      "quote": "We are not going to raise the gas tax. Period.",
      "speaker": "Jane Smith",
      "category": "taxes",
      "severity": 3,
      "reason": "Contradicts her 2024 vote on HB 112",
      "show": "Evening News",
      "channel": "WXYZ",
      "platform": "tv",
      "air_date": "2026-10-01",
      "start_seconds": 1312.4,
      "end_seconds": 1341.0,
      "duration_seconds": 28.6,
      "tags": "gas-tax, debate",
      "source_url": "https://...",
      "play_url": "https://app.civly.ai/api/v1/partner/clips/18234/play?sig=...",
      "thumbnail_url": "https://app.civly.ai/api/v1/partner/clips/18234/thumbnail?sig=...",
      "created_at": "2026-10-01T22:14:09Z"
    }
  ],
  "has_more": true,
  "next_after_id": 18234
}

GET/clips/{clip_id}/play

Play a clip (signed link from the clip feed; no API key)

Redirects to the clip's video: the cut clip when there is one, otherwise the source video opened at the clip's moment. A TV clip whose video is still being converted answers 503 with Retry-After.

Path parameters
clip_idintegerrequired
Query parameters
sigstringrequired
Example request
curl -sI "https://app.civly.ai/api/v1/partner/clips/18234/play?sig=..."
Response 302
HTTP/2 302
location: https://...  (the clip's video, valid for an hour)
cache-control: private, max-age=300

GET/clips/{clip_id}/thumbnail

Clip still image (signed link from the clip feed; no API key)

Redirects to the clip's own frame, or to its source video's thumbnail.

Path parameters
clip_idintegerrequired
Query parameters
sigstringrequired
Example request
curl -sI "https://app.civly.ai/api/v1/partner/clips/18234/thumbnail?sig=..."
Response 302
HTTP/2 302
location: https://...  (the clip's still image, valid for an hour)
cache-control: private, max-age=300

NIL deal pricing

Fair-market-value estimates for college athlete NIL deals, priced line by line against published, dated rates. Your key needs the nil_pricing product.

POST/nil-pricing/price

Price a proposed NIL deal line by line against published market rates

Price each deliverable in a deal against a published rate for that kind of work, adjusted for this athlete, and total the result.

Every priced line returns the rate used, the benchmark it came from with its source URL and as_of date, the reasoning for where this athlete sits inside the published range, and the extension. Deliverables with no sourced rate return priced: false with the reason rather than an estimate. The quote is stored and replayable by its quote_ref. Requires the nil_pricing product.

Request body
athleteobjectrequired
3 fields inside athlete
athlete_idinteger or null
seasoninteger or null

From 2,015 to 2,100.

followersmap of integer or null

Platform -> follower count, when Civly does not hold the athlete.

deliverablesarray of objectsrequired

Up to 50 items.

10 fields inside deliverables
deliverable_typestringrequired

social_post, appearance, likeness_usage, tv_spot, ...

quantityinteger

From 1 to 10,000. Default 1.

platformstring or null

instagram, tiktok, x, youtube.

formatstring or null

static, video, story, reel.

geographystring or null

local, regional, national.

term_monthsinteger or null

From 1 to 120.

duration_minutesinteger or null

From 1 to 1,440.

exclusivityboolean

Default false.

audienceinteger or null

From 1 to 1,000,000,000.

descriptionstring or null

Up to 1,000 characters.

payor_namestring or null

Up to 300 characters.

payor_typestring or null

collective, business, associated_entity, unknown.

Example request
curl -X POST "https://app.civly.ai/api/v1/partner/nil-pricing/price" \
  -H "Authorization: Bearer $CIVLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "athlete": {
      "athlete_id": 8812,
      "season": 2025
    },
    "payor_name": "A regional bank",
    "payor_type": "business",
    "deliverables": [
      {
        "deliverable_type": "social_post",
        "quantity": 4,
        "platform": "instagram",
        "format": "static"
      },
      {
        "deliverable_type": "appearance",
        "quantity": 2,
        "duration_minutes": 120
      },
      {
        "deliverable_type": "likeness_usage",
        "geography": "regional",
        "term_months": 12
      }
    ]
  }'
Response 200
14 response fields
quote_refstringrequired
athleteobjectrequired
29 fields inside athlete
athlete_idinteger or null
full_namestring or null
sportstring or null
seasoninteger or null
position_abbrstring or null
program_namestring or null
conference_namestring or null
program_supplied_by_callerboolean

Default false.

followersmap of integer
engagement_ratemap of number
followers_supplied_by_callerboolean

Default false.

reach_captured_ondatetime or null
production_statstring or null
production_valuenumber or null
production_percentilenumber or null
production_peer_countinteger

Default 0.

production_seasoninteger or null
market_namestring or null
market_populationinteger or null
market_gdp_thousandsinteger or null
market_pricedboolean

Default false.

market_not_priced_reasonstring or null
program_revenue_usdinteger or null
program_revenue_percentilenumber or null
program_revenue_peer_countinteger

Default 0.

program_revenue_peer_groupstring or null
profile_tierstring or null
profile_tier_reasonstring or null
unresolvedarray of strings
linesarray of objectsrequired
26 fields inside lines
deliverable_typestringrequired
platformstring or null
formatstring or null
quantityinteger

Default 1.

geographystring or null
term_monthsinteger or null
exclusivityboolean

Default false.

descriptionstring or null
pricedbooleanrequired
unpriced_reasonstring or null
coverage_gapboolean

Default false.

rate_lownumber or null
rate_highnumber or null
rate_basisstring or null
rate_open_endedboolean

Default false.

rate_floor_boundboolean

Default false.

benchmark_idinteger or null
benchmark_rate_lownumber or null
benchmark_rate_highnumber or null
benchmark_tierstring or null
extended_lownumber or null
extended_highnumber or null
placement_reasonstring or null
citationsarray of objects
6 fields inside citations
namestringrequired
urlstring or null
as_ofstring or null
rolestringrequired
figurestring or null
notestring or null
max_per_seasoninteger or null
exceeds_capboolean

Default false.

total_lownumberrequired
total_highnumberrequired
total_high_openboolean

Default false.

unpriced_line_countinteger

Default 0.

priced_atdatetimerequired
engine_versionstring

Default "1.0.0".

rates_as_ofdate or null
benchmark_checkobject or null
10 fields inside benchmark_check
archetypestringrequired
projection_periodstringrequired
commercial_nil_usdintegerrequired
excluded_roster_pay_usdintegerrequired
deal_share_low_pctnumberrequired
deal_share_high_pctnumberrequired
basisstringrequired
source_namestringrequired
source_urlstring or null
as_ofdaterequired
clearinghouseobject or null
16 fields inside clearinghouse
period_keystringrequired
period_labelstringrequired
cleared_countintegerrequired
cleared_total_usdintegerrequired
denied_countintegerrequired
denied_total_usdintegerrequired
approved_mean_usdintegerrequired
denied_mean_usdintegerrequired
review_threshold_usdintegerrequired
bandstringrequired
band_reasonstringrequired
caveatstringrequired
source_namestringrequired
source_urlstringrequired
as_ofdaterequired
reconciliation_notestring or null
athlete_yearobject or null
8 fields inside athlete_year
commercial_lownumber or null
commercial_highnumber or null
school_lownumber or null
school_highnumber or null
total_lownumber or null
total_highnumber or null
deal_share_of_commercial_pctnumber or null
notestringrequired
disclaimerstring

Example response

{
  "quote_ref": "nilq_...",
  "athlete": {
    "athlete_id": 8812,
    "sport": "football",
    "season": 2025,
    "followers": {
      "instagram": 28000
    }
  },
  "lines": [
    {
      "deliverable_type": "social_post",
      "quantity": 4,
      "priced": true,
      "rate_low": 280.0,
      "rate_high": 500.0,
      "rate_basis": "per_unit",
      "extended_low": 1120.0,
      "extended_high": 2000.0,
      "placement_reason": "28,000 followers. The per-follower anchor puts a post at 280...",
      "citations": [
        {
          "name": "...",
          "url": "https://...",
          "as_of": "2026-08-02",
          "role": "anchor"
        }
      ]
    }
  ],
  "total_low": 8120.0,
  "total_high": 17300.0,
  "total_high_open": false,
  "unpriced_line_count": 0,
  "priced_at": "2026-10-02T14:05:11Z",
  "rates_as_of": "2026-08-02",
  "disclaimer": "..."
}

POST/nil-pricing/reverse

Show the work a target budget would require, and whether that volume is credible

Given a target budget, return the package of work that reaches it, with a warning on every deliverable whose volume exceeds a credible per-season maximum.

feasible, credible_ceiling_low/_high and shortfall_multiple quantify those warnings: they say what the athlete's whole season of inventory supports and by what multiple the target overshoots it. A package returned with feasible: false reaches the number only at volumes this athlete cannot credibly deliver, and presenting its total without the warnings misrepresents the answer. Requires the nil_pricing product.

Request body
athleteobjectrequired
3 fields inside athlete
athlete_idinteger or null
seasoninteger or null

From 2,015 to 2,100.

followersmap of integer or null

Platform -> follower count, when Civly does not hold the athlete.

target_usdnumberrequired

At most 100,000,000.

deliverable_mixarray of strings or null

Up to 11 items.

geographystring or null

Default "regional".

Example request
curl -X POST "https://app.civly.ai/api/v1/partner/nil-pricing/reverse" \
  -H "Authorization: Bearer $CIVLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "athlete": {
      "athlete_id": 8812
    },
    "target_usd": 200000,
    "geography": "regional",
    "deliverable_mix": [
      "social_post",
      "appearance",
      "likeness_usage"
    ]
  }'
Response 200
17 response fields
quote_refstringrequired
target_usdnumberrequired
package_total_usdnumberrequired
linesarray of objectsrequired
26 fields inside lines
deliverable_typestringrequired
platformstring or null
formatstring or null
quantityinteger

Default 1.

geographystring or null
term_monthsinteger or null
exclusivityboolean

Default false.

descriptionstring or null
pricedbooleanrequired
unpriced_reasonstring or null
coverage_gapboolean

Default false.

rate_lownumber or null
rate_highnumber or null
rate_basisstring or null
rate_open_endedboolean

Default false.

rate_floor_boundboolean

Default false.

benchmark_idinteger or null
benchmark_rate_lownumber or null
benchmark_rate_highnumber or null
benchmark_tierstring or null
extended_lownumber or null
extended_highnumber or null
placement_reasonstring or null
citationsarray of objects
6 fields inside citations
namestringrequired
urlstring or null
as_ofstring or null
rolestringrequired
figurestring or null
notestring or null
max_per_seasoninteger or null
exceeds_capboolean

Default false.

warningsarray of strings
feasiblebooleanrequired
credible_ceiling_lownumberrequired
credible_ceiling_highnumberrequired
shortfall_multiplenumber or null
volume_constraintsarray of objects
9 fields inside volume_constraints
deliverable_typestringrequired
platformstring or null
max_per_seasoninteger or null
required_for_targetintegerrequired
exceedsbooleanrequired
rationalestring or null
source_kindstring or null
source_namestring or null
source_urlstring or null
package_total_lownumberrequired
package_total_highnumberrequired
athleteobjectrequired
29 fields inside athlete
athlete_idinteger or null
full_namestring or null
sportstring or null
seasoninteger or null
position_abbrstring or null
program_namestring or null
conference_namestring or null
program_supplied_by_callerboolean

Default false.

followersmap of integer
engagement_ratemap of number
followers_supplied_by_callerboolean

Default false.

reach_captured_ondatetime or null
production_statstring or null
production_valuenumber or null
production_percentilenumber or null
production_peer_countinteger

Default 0.

production_seasoninteger or null
market_namestring or null
market_populationinteger or null
market_gdp_thousandsinteger or null
market_pricedboolean

Default false.

market_not_priced_reasonstring or null
program_revenue_usdinteger or null
program_revenue_percentilenumber or null
program_revenue_peer_countinteger

Default 0.

program_revenue_peer_groupstring or null
profile_tierstring or null
profile_tier_reasonstring or null
unresolvedarray of strings
priced_atdatetimerequired
engine_versionstring

Default "1.0.0".

clearinghouseobject or null
16 fields inside clearinghouse
period_keystringrequired
period_labelstringrequired
cleared_countintegerrequired
cleared_total_usdintegerrequired
denied_countintegerrequired
denied_total_usdintegerrequired
approved_mean_usdintegerrequired
denied_mean_usdintegerrequired
review_threshold_usdintegerrequired
bandstringrequired
band_reasonstringrequired
caveatstringrequired
source_namestringrequired
source_urlstringrequired
as_ofdaterequired
reconciliation_notestring or null
disclaimerstring

Example response

{
  "quote_ref": "nilq_...",
  "target_usd": 200000.0,
  "package_total_usd": 202960.0,
  "package_total_low": 190400.0,
  "package_total_high": 215520.0,
  "feasible": false,
  "credible_ceiling_low": 13960.0,
  "credible_ceiling_high": 28800.0,
  "shortfall_multiple": 6.94,
  "lines": [
    {
      "deliverable_type": "social_post",
      "quantity": 104,
      "priced": true,
      "max_per_season": 7,
      "exceeds_cap": true
    }
  ],
  "warnings": [
    "No package at credible volume reaches 200,000..."
  ],
  "athlete": {
    "athlete_id": 8812
  },
  "priced_at": "2026-10-02T14:05:11Z"
}

POST/nil-pricing/annual

Value a full year of an athlete: what a school would pay, and what brands would pay

Value a whole season, in two halves that are reported separately and never blended.

What a school would pay (roster_pay) is a published annual pay range for the athlete's position, from a survey of 20+ college general managers and agents, with the athlete placed inside it by production percentile against peers at that position. Audience is not an input: schools pay to win games. Read placement_reason for the published range, the athlete's rank and the arithmetic between them.

What brands would pay (annual_low/annual_high) prices every kind of commercial work at a published rate and multiplies by how much of it a season holds.

Built bottom up: each line is a published rate for that work, times a per-season count for the athlete's sport. Nothing is a share of any published annual valuation. The social-post and appearance counts are derived from transaction data covering 125,000+ student-athletes; a line whose count is still ours reports cap_source_kind: "civly_assumption", and assumption_backed_usd totals how much of the figure rests on those.

excluded_work names every kind of work left out and why, and activity_count_check sets the package against published annual activity counts for the sport.

annual_low/annual_high are null when the year would be short a line Civly could not price, either because no sourced rate covers the athlete or because no sourced count says how much of that work a season holds. A total missing a line reads as complete and is not, and it errs in one direction only: the athlete looks cheaper, so any ratio built on the figure rises. annual_unavailable_reason says which lines are missing and incomplete_lines names them. Do not sum the lines to recover a total. A line unpriced because an input about the athlete was missing does not withhold the year, and reports coverage_gap: false. Requires the nil_pricing product.

Request body
athleteobjectrequired
3 fields inside athlete
athlete_idinteger or null
seasoninteger or null

From 2,015 to 2,100.

followersmap of integer or null

Platform -> follower count, when Civly does not hold the athlete.

seasoninteger or null

From 2,015 to 2,100.

geographystring or null

Default "regional".

committedmap of integer or null

deliverable_type -> count already signed this season.

Example request
curl -X POST "https://app.civly.ai/api/v1/partner/nil-pricing/annual" \
  -H "Authorization: Bearer $CIVLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "athlete": {
      "athlete_id": 8812
    },
    "season": 2025,
    "geography": "national",
    "committed": {
      "social_post": 4,
      "appearance": 1
    }
  }'
Response 200
29 response fields
quote_refstringrequired
athleteobjectrequired
29 fields inside athlete
athlete_idinteger or null
full_namestring or null
sportstring or null
seasoninteger or null
position_abbrstring or null
program_namestring or null
conference_namestring or null
program_supplied_by_callerboolean

Default false.

followersmap of integer
engagement_ratemap of number
followers_supplied_by_callerboolean

Default false.

reach_captured_ondatetime or null
production_statstring or null
production_valuenumber or null
production_percentilenumber or null
production_peer_countinteger

Default 0.

production_seasoninteger or null
market_namestring or null
market_populationinteger or null
market_gdp_thousandsinteger or null
market_pricedboolean

Default false.

market_not_priced_reasonstring or null
program_revenue_usdinteger or null
program_revenue_percentilenumber or null
program_revenue_peer_countinteger

Default 0.

program_revenue_peer_groupstring or null
profile_tierstring or null
profile_tier_reasonstring or null
unresolvedarray of strings
seasoninteger or null
linesarray of objectsrequired
16 fields inside lines
deliverable_typestringrequired
lineobjectrequired
26 fields inside line
deliverable_typestringrequired
platformstring or null
formatstring or null
quantityinteger

Default 1.

geographystring or null
term_monthsinteger or null
exclusivityboolean

Default false.

descriptionstring or null
pricedbooleanrequired
unpriced_reasonstring or null
coverage_gapboolean

Default false.

rate_lownumber or null
rate_highnumber or null
rate_basisstring or null
rate_open_endedboolean

Default false.

rate_floor_boundboolean

Default false.

benchmark_idinteger or null
benchmark_rate_lownumber or null
benchmark_rate_highnumber or null
benchmark_tierstring or null
extended_lownumber or null
extended_highnumber or null
placement_reasonstring or null
citationsarray of objects
6 fields inside citations
namestringrequired
urlstring or null
as_ofstring or null
rolestringrequired
figurestring or null
notestring or null
max_per_seasoninteger or null
exceeds_capboolean

Default false.

max_per_seasoninteger or null
annual_lownumber or null
annual_highnumber or null
cap_source_kindstring or null
cap_source_namestring or null
cap_source_urlstring or null
cap_sportstring or null
volume_notestring or null
dropped_reasonstring or null
committedinteger or null
remaininginteger or null
remaining_lownumber or null
remaining_highnumber or null
oversoldboolean

Default false.

annual_lownumber or null
annual_highnumber or null
annual_unavailable_reasonstring or null
incomplete_linesarray of strings
annual_high_openboolean

Default false.

assumption_backed_usdnumber

Default 0.0.

unpriced_line_countinteger

Default 0.

excluded_workmap of string
sport_activity_countnumber or null
activity_count_checkstring or null
remaining_lownumber or null
remaining_highnumber or null
oversold_linesarray of strings
total_lownumber or null
total_highnumber or null
total_unavailable_reasonstring or null
total_basisstring or null
archetype_totalobject or null
9 fields inside archetype_total
archetypestringrequired
total_usdintegerrequired
precisionstring

Default "archetype average, not an estimate for this athlete".

periodstringrequired
predates_revenue_sharingboolean

Default false.

basisstringrequired
source_namestringrequired
source_urlstring or null
as_ofdaterequired
position_archetypeobject or null
10 fields inside position_archetype
archetypestringrequired
annual_lowintegerrequired
annual_highintegerrequired
precisionstring
coarse_position_notestring or null
basisstringrequired
caveatstringrequired
source_namestringrequired
source_urlstring or null
as_ofdaterequired
roster_payobject or null
13 fields inside roster_pay
archetypestringrequired
coarse_position_notestring or null
annual_usdintegerrequired
annual_lownumber or null
annual_highnumber or null
placement_reasonstring or null
precisionstring

Default "published position range, placed by on-field production".

componentsstringrequired
caveatstring or null
basisstring or null
source_namestringrequired
source_urlstring or null
as_ofdaterequired
campaign_geographystring or null
priced_atdatetimerequired
engine_versionstring

Default "1.0.0".

rates_as_ofdate or null
disclaimerstring

Example response

{
  "quote_ref": "nilq_...",
  "athlete": {
    "athlete_id": 8812
  },
  "season": 2025,
  "lines": [
    {
      "deliverable_type": "social_post",
      "max_per_season": 7,
      "annual_low": 1960.0,
      "annual_high": 3500.0,
      "cap_source_kind": "derived",
      "line": {
        "deliverable_type": "social_post",
        "priced": true
      }
    }
  ],
  "annual_low": 13960.0,
  "annual_high": 28800.0,
  "assumption_backed_usd": 15000.0,
  "excluded_work": {
    "tv_spot": "no sourced broadcast rate yet..."
  },
  "priced_at": "2026-10-02T14:05:11Z"
}

GET/nil-pricing/rate-card

List every current NIL rate benchmark with its source

Every rate currently in force, with the bracket it applies to, what one unit buys, and the source it was read from. Superseded rows are excluded; a quote issued against an older survey still cites its own figures and replays unchanged.

Rows whose source licence permits citation but not reproduction are listed with their source and their figures withheld. Requires the nil_pricing product.

Example request
curl "https://app.civly.ai/api/v1/partner/nil-pricing/rate-card" \
  -H "Authorization: Bearer $CIVLY_API_KEY"
Response 200
2 response fields
ratesarray of objectsrequired
17 fields inside rates
idintegerrequired
deliverable_typestringrequired
platformstring or null
formatstring or null
basisstringrequired
tier_metricstring or null
tier_lownumber or null
tier_highnumber or null
tier_labelstring or null
rate_lownumber or null
rate_highnumber or null
unit_notestring or null
source_namestringrequired
source_urlstring or null
as_ofdaterequired
redistributionstringrequired
notesstring or null
countintegerrequired

Example response

{
  "count": 1,
  "rates": [
    {
      "id": 3,
      "deliverable_type": "social_post",
      "platform": "instagram",
      "basis": "per_unit",
      "tier_low": 10000,
      "tier_high": 100000,
      "rate_low": 500.0,
      "rate_high": 5000.0,
      "unit_note": "One in-feed post to the athlete's own account...",
      "source_name": "...",
      "source_url": "https://...",
      "as_of": "2026-08-02",
      "redistribution": "..."
    }
  ]
}

GET/nil-pricing/quotes/{quote_ref}

Replay a stored quote exactly as it was issued

Return a previously issued quote verbatim, including the rates and source dates it cited at the time. This does not recompute: the rates it was priced against may have been resurveyed since, and a recomputation would return a different number and prove nothing about the one being asked about. Requires the nil_pricing product.

Path parameters
quote_refstringrequired
Example request
curl "https://app.civly.ai/api/v1/partner/nil-pricing/quotes/nilq_..." \
  -H "Authorization: Bearer $CIVLY_API_KEY"
Response 200

A JSON object; the example shows its shape.

Example response

{
  "quote_ref": "nilq_...",
  "total_low": 8120.0,
  "total_high": 17300.0,
  "lines": [
    "..."
  ]
}

Access requests

Ask Civly for a key from your own code. The form linked under Get access does the same thing.

POST/access-request

Request partner API access

Ask Civly for a partner API key. Every request is reviewed by a human; approved partners receive their key and webhook secret over a secure channel. This endpoint never issues credentials.

Request body
company_namestringrequired

2–200 characters.

contact_namestringrequired

2–200 characters.

emailemailrequired
use_casestringrequired

What you plan to build and research with the API. 20–3,000 characters.

requested_productsarray of strings or null

Products you want: big_book, social_analysis, data_access, opinion_search. Omit for all.

Example request
curl -X POST "https://app.civly.ai/api/v1/partner/access-request" \
  -H "Content-Type: application/json" \
  -d '{
    "company_name": "Example Strategies",
    "contact_name": "Jane Example",
    "email": "[email protected]",
    "use_case": "Screen our donor list each quarter and pull research reports on new candidates.",
    "requested_products": [
      "adverse_screening",
      "big_book"
    ]
  }'
Response 201
3 response fields
receivedboolean

Default true.

request_idintegerrequired
messagestring

Example response

{
  "received": true,
  "request_id": 42,
  "message": "Thanks - Civly reviews every request and will reply by email."
}

OpenAPI and llms.txt

Responsible use

Support

Questions about the API, your key or your limits: matthew@civly.ai. When something goes wrong, include the job_id and the X-Request-ID response header.

Civly’s Privacy Policy and Terms of Use apply to all use of the API. To see what the data behind it covers, visit the Data page.