Getting started

Quickstart

From a key to a useful weekly digest in four requests.

Make your first call

  1. Create a key

    On the Developers page of your workspace, create an API key. You need to be an admin of the workspace, and the plan has to be Grow or above. Choose read unless you intend to change things; read-and-write keys need Scale or Custom.

    The secret is shown once. Put it somewhere your code can read it:

    export VIDRYS_API_KEY="vidrys_sk_…"
  2. List your projects

    Every other call takes a project id, so this is always the first request.

    curl --request GET \
      --url "https://api.vidrys.com/v1/projects" \
      --header "Authorization: Bearer $VIDRYS_API_KEY"
    200 OK
    {
      "projects": [
        {
          "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
          "name": "Acme Invoicing",
          "industry": "Accounting software"
        }
      ]
    }
  3. Read this week's visibility

    One call gives the headline numbers, a row per AI engine and the alerts — the same figures as the dashboard’s front page.

    curl --request GET \
      --url "https://api.vidrys.com/v1/projects/$PROJECT_ID/visibility?range=7d" \
      --header "Authorization: Bearer $VIDRYS_API_KEY"
  4. Find out what you're losing

    List gaps returns the questions you lose, which engines lose them, who wins instead and whether it is getting worse.

    curl --request GET \
      --url "https://api.vidrys.com/v1/projects/$PROJECT_ID/gaps?page_size=20" \
      --header "Authorization: Bearer $VIDRYS_API_KEY"

A useful first loop

Most integrations are a weekly digest. This one reads the numbers, pulls the worst gaps and prints the reasoning behind the worst one — every call is a read, so a read-only key is enough.

digest.py
import os

import requests

BASE = "https://api.vidrys.com/v1"
AUTH = {"Authorization": f"Bearer {os.environ['VIDRYS_API_KEY']}"}


def get(path, **params):
    response = requests.get(f"{BASE}{path}", params=params, headers=AUTH, timeout=30)
    response.raise_for_status()
    return response.json()


project = get("/projects")["projects"][0]
pid = project["id"]

overview = get(f"/projects/{pid}/visibility", range="7d")
for metric in overview["metrics"]:
    print(f"{metric['label']}: {metric['value']} ({metric['caption']})")

gaps = get(f"/projects/{pid}/gaps", page_size=5)["gaps"]
for gap in gaps:
    losing = ", ".join(gap["platforms_losing"])
    print(f"#{gap['prompt_ref']} {gap['prompt_text']} — losing on {losing} to {gap['top_competitor']}")

# Why we lose the worst one: find a result that carries a reasoning card.
worst = gaps[0]
results = get(f"/projects/{pid}/results", prompt_id=worst["prompt_id"])["probes"]
card_result = next(r for r in results if r["reasoning_card_id"])
card = get(f"/projects/{pid}/results/{card_result['id']}/reasoning")
for reason in card["reasons"]:
    print(f"- [{reason['impact']}] {reason['category']}: {reason['detail']}")
Reasoning cards are fetched by result id, not by the card id you see on a gap. Pick a result whose reasoning_card_id isn’t null and pass that result’s id.

Handle the errors that matter

Three of them decide how a well-behaved client behaves:

  • 429 rate_limited — wait the number of seconds in Retry-After and try again. This is the only error worth retrying unchanged.
  • 402 credits_exhausted— a draft this month is impossible; retrying won’t help until the 1st.
  • 403 write_not_allowed— the key is read-only, or the plan doesn’t include writes.

Errors lists every code. Then head to the reference, or point an assistant at the MCP server and skip the plumbing.