API
Reference
Overview MCP GitHub Action Dashboard
The key is a secret. Do not call this API from a page you publish, a mobile app you ship, or a public repository. A visitor can read the key out of the page and spend your quota. Server-side code, a GitHub Actions secret, and an MCP client config on your machine are the places it belongs. The dashboard stores a key in localStorage on that browser only.
Request
GET /v1/preview?url=<absolute url>&platforms=x,slack&source=api
POST /v1/preview with a JSON body accepts the same fields. platforms is optional (all 11 apps when omitted). source is api, mcp, or action. It only changes which counter we increment. The default is api.
Header: Authorization: Bearer cs_live_…. The token is at least 32 characters after cs_live_.
GET responses send Access-Control-Allow-Origin: *. That does not make a browser call safe. The key is still visible to anyone who can read the request.
curl
curl -sS "https://cardscope.dev/v1/preview?url=https://example.com&source=api" \
-H "Authorization: Bearer cs_live_…"
JavaScript
const res = await fetch(
"https://cardscope.dev/v1/preview?url=" + encodeURIComponent("https://example.com"),
{ headers: { Authorization: `Bearer ${process.env.CARDSCOPE_API_KEY}` } },
);
const preview = await res.json();
Python
import os, requests
res = requests.get(
"https://cardscope.dev/v1/preview",
params={"url": "https://example.com", "source": "api"},
headers={"Authorization": f"Bearer {os.environ['CARDSCOPE_API_KEY']}"},
timeout=20,
)
preview = res.json()
Response
The TypeScript shape is LinkPreview in src/lib/preview.ts.
url,finalUrl,status(HTTP status of the page),fetchedAttags.og,tags.twitter,tags.meta(title,description,canonical)image:url,reachable,width,height,bytes,contentTypeplatforms[]:id,name,title,description,image,layout(large,small, ornone),truncated(titleanddescriptionbooleans),usedFallbacks,warnings,sources(the doc URLs cited in the checker; empty when we don’t cite one)issues[]: checklist items withid,severity,title,fix,platformsusage:plan(free,starter,pro),used,limit,resetsAt
Errors
400— the URL is missing, not http(s), or a private or reserved address401— missing or invalid key402—quota_exceeded, withupgradeUrlandusage422— the fetch failed.messageis the reason. The call still counts.429— more than 5 requests in the same second for that key
Quotas
Free 300, Starter 10,000, Pro 100,000 previews each UTC month. The counter key is api:{keyId}:{YYYY-MM}, so the next month starts at zero. A rejected URL (400) and a rate-limited call (429) do not count. A fetch we attempt does, including a 422.
Keys
POST /api/keys with {"email":"you@example.com"} emails a one-time link. GET /api/keys/claim?token=… shows the key once. GET /api/keys/me returns the plan and this month’s usage. POST /api/keys/rotate returns a new key and revokes the one you sent. POST /api/keys/upgrade with {"plan":"starter"} or "pro" returns a Stripe Checkout URL. POST /api/keys/portal returns a billing portal URL after the first payment.