API reference
A public, CORS-open REST surface under /api/v1, plus a GraphQL endpoint. No key required. Reads are licensed CC-BY-4.0.
Response envelope
Every REST response wraps its payload the same way:
{
"data": { /* the payload, shape depends on the endpoint */ },
"meta": {
"source": "https://www.vioscale.ai",
"license": "CC-BY-4.0",
"generatedAt": "2026-07-06T00:00:00.000Z",
"disclaimer": "Independent, evidence-based. Every fact carries provenance."
}
}Errors return { "error": "…" } with the appropriate status (e.g. 404 for an unknown slug).
Endpoints
| Method | Path | data | Description |
|---|---|---|---|
| GET | /api/v1 | ApiIndex | Self-describing index of every endpoint. |
| GET | /api/v1/software | EntityListItem[] | List entities. Query: category, limit, sort=score|name, intent, weights. |
| GET | /api/v1/software/{slug} | EntityView | One entity with all provenanced facts (canonical balanced score). 404 if unknown. |
| GET | /api/v1/categories | CategoryView[] | List all categories (tree). |
| GET | /api/v1/categories/{slug} | CategoryView | One category with its ranked entities. Query: intent, weights. |
| GET | /api/v1/compare | ComparisonView | Aligned comparison. Query: tools=a,b,c (required), intent, weights. |
| GET | /api/v1/search | EntityListItem[] | Fuzzy search. Query: q (required), limit, intent, weights. |
| GET | /api/v1/alternatives/{slug} | EntityListItem[] | Same-category alternatives. Query: limit, intent, weights. |
| GET | /api/v1/signals/{slug} | SignalBreakdownView | The signal breakdown behind an entity's score. |
| GET | /api/v1/intents | IntentCatalogue | Catalogue of ranking profiles + signal glossary (for intent/weights). |
Intent-conditioned ranking
Every list and ranking endpoint (and the same GraphQL queries) accepts optional re-weighting. The facts and signals never change, only the weight vector does. The balanced default is the canonical, citeable score; pass intent (a named profile) or weights (an explicit override) to re-rank. The resolved profile and normalised weights come back in meta, so you can verify the ranking. Each signal is capped at 0.5 of the total, so no single signal can dominate. See the full catalogue at /api/v1/intents.
# named profile
curl "https://www.vioscale.ai/api/v1/categories/ci-cd?intent=most-secure"
# explicit weights (signal:number, comma-separated)
curl "https://www.vioscale.ai/api/v1/search?q=orm&weights=package_downloads:0.5,github_activity:0.5"
# GraphQL takes the same args
curl -X POST https://www.vioscale.ai/api/graphql -H 'content-type: application/json' \
-d '{"query":"{ compare(tools:[\"prisma\",\"drizzle-orm\"], intent:\"most-active\"){ leader } }"}'
# the response meta echoes the applied ranking:
# "meta": { "profile": "most-secure", "weights": { "security_posture": 0.48, ... } }Content negotiation
You don't have to use /api/v1 at all. A canonical page URL responds to your Accept header:
curl -H "Accept: application/json" https://www.vioscale.ai/software/nextjs # → JSON
curl -H "Accept: text/markdown" https://www.vioscale.ai/software/nextjs # → Markdown
curl https://www.vioscale.ai/software/nextjs.md # → Markdown (suffix)GraphQL
POST /api/graphql: open GraphiQL in a browser at the same URL. Schema:
type Query {
software(slug: String!): Entity
softwareList(category: String, limit: Int): [Entity!]!
categories: [Category!]!
category(slug: String!): Category
compare(tools: [String!]!): Comparison!
search(q: String!, limit: Int): [Entity!]!
alternatives(slug: String!, limit: Int): [Entity!]!
}curl https://www.vioscale.ai/api/graphql \
-H 'Content-Type: application/json' \
-d '{"query":"{ compare(tools:[\"prisma\",\"drizzle-orm\"]){ leader entities{ name score{ composite } } } }"}'