REST API.
Pull dossiers into your own pipeline — Python script, Looker model, n8n flow, custom dashboard. Same tier-window gating as the web app, no extra throttle for paying tiers.
Authentication
Every endpoint requires Authorization: Bearer <token>. Tokens are long-lived (no expiry) and issued one per subscriber from /account → REST API access. Pro, Fund and Enterprise tiers can issue tokens — Starter and Free Preview hit 403.
Tokens look like gs_ followed by 64 hex characters. Treat them like a Stripe secret key — if a token leaks, rotate immediately from /account. We store the raw token (not a hash); same security model as Stripe API keys.
curl https://gemscout.co/api/v1/me \
-H "Authorization: Bearer gs_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"Response envelope
Every JSON response wraps the payload in a uniform shape:
{
"ok": true,
"data": { /* endpoint-specific */ },
"count": 25, // list endpoints only
"branch": "ai_deep_tech",
"limit": 25
}
{
"ok": false,
"error": "Token does not match any subscriber."
}All endpoints respond with Content-Type: application/json; charset=utf-8 and CORS open to * (auth is bearer-only).
Endpoints
Health-check for your token. Returns the calling subscriber's uid + tier.
{
"ok": true,
"data": {
"uid": "ToxCl6Lq...",
"tier": "pro",
"token_suffix": "f1a7ec8a"
}
}Paginated list of delivered candidates (public-safe shape).
| branch | string, optional | One of ai_deep_tech, open_source, bootstrapped_oss, fintech, b2b_saas, construct_tech. |
| limit | integer, default 25, max 100 | Hard-capped at 100. |
curl "https://gemscout.co/api/v1/candidates?branch=ai_deep_tech&limit=10" \
-H "Authorization: Bearer gs_..."
{
"ok": true,
"data": [
{
"id": "abc123",
"name": "Example AI",
"domain": "example.com",
"summary": "One-sentence neutral description.",
"branch": "ai_deep_tech",
"github_url": "https://github.com/example/repo",
"x_handle": "exampleai",
"linkedin_url": null,
"delivered_at_ms": 1748000000000,
"github_stars": 421
}
],
"count": 1,
"branch": "ai_deep_tech",
"limit": 10
}Tier-window gating is applied — a Pro caller sees candidates whose T+24h release has elapsed; Fund and Enterprise see T+0.
Full PublicCandidateDetail for a single candidate: founders array, signed 24h PDF URL, selected enrichment fields. 404 when the candidate doesn't exist or isn't released for your tier yet (we don't reveal which).
{
"ok": true,
"data": {
"id": "abc123",
"name": "Example AI",
"domain": "example.com",
"summary": "...",
"branch": "ai_deep_tech",
"delivered_at_ms": 1748000000000,
"github_url": "...",
"x_handle": "...",
"linkedin_url": "...",
"github_stars": 421,
"founders": [
{
"name": "Jane Doe",
"linkedin_url": "...",
"x_handle": "janedoe",
"github_handle": "janedoe",
"personal_site": "...",
"background_summary": "Ex-..."
}
],
"pdf_external_url": "https://storage.googleapis.com/...?X-Goog-Signature=...",
"enrichment_view": {
"wayback_first_snapshot": "2024-08-12",
"wayback_snapshot_count": 47,
"github_stars": 421,
"github_recent_commit_count_90d": 184,
"github_unique_contributors_90d": 7,
"tech_stack_hints": ["typescript", "postgres", "edge-runtime"]
}
}
}pdf_external_url is a signed Cloud Storage URL valid for 24 hours. Re-fetch the candidate to refresh it. Internal-only fields (composite scores, scout_angle, outreach data) are never included.
Issue / rotate / revoke your API token. Authed via Firebase ID token (not the api_token itself — so it works when the token is empty or compromised). Most callers use the UI on /account instead of hitting this directly.
POST returns a fresh token in the response body exactly once — we don't store the plaintext after issuing so reloading /account won't show it again.
Language examples
curl
curl https://gemscout.co/api/v1/candidates?branch=ai_deep_tech \
-H "Authorization: Bearer $GEMSCOUT_TOKEN"Python (requests)
import os, requests
r = requests.get(
"https://gemscout.co/api/v1/candidates",
params={"branch": "ai_deep_tech", "limit": 25},
headers={"Authorization": f"Bearer {os.environ['GEMSCOUT_TOKEN']}"},
timeout=10,
)
r.raise_for_status()
for c in r.json()["data"]:
print(c["name"], "·", c.get("domain") or "—")JavaScript (fetch)
const r = await fetch(
"https://gemscout.co/api/v1/candidates?branch=ai_deep_tech",
{ headers: { Authorization: `Bearer ${process.env.GEMSCOUT_TOKEN}` } },
);
const { ok, data, error } = await r.json();
if (!ok) throw new Error(error);
for (const c of data) console.log(c.name, "·", c.domain ?? "—");Go
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
)
type Resp struct {
Ok bool `json:"ok"`
Data []map[string]any `json:"data"`
Error string `json:"error,omitempty"`
}
func main() {
req, _ := http.NewRequest("GET",
"https://gemscout.co/api/v1/candidates?branch=ai_deep_tech", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("GEMSCOUT_TOKEN"))
res, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer res.Body.Close()
var out Resp
json.NewDecoder(res.Body).Decode(&out)
if !out.Ok { panic(out.Error) }
for _, c := range out.Data {
fmt.Println(c["name"], "·", c["domain"])
}
}Ruby (net/http)
require "net/http"
require "json"
uri = URI("https://gemscout.co/api/v1/candidates?branch=ai_deep_tech")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{ENV['GEMSCOUT_TOKEN']}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
body = JSON.parse(res.body)
raise body["error"] unless body["ok"]
body["data"].each { |c| puts "#{c['name']} · #{c['domain'] || '—'}" }Errors
| 400 | Bad request — invalid query param (e.g. limit=abc). |
| 401 | Missing / malformed / unknown bearer token. |
| 403 | Token belongs to a tier that doesn't include API access (Starter / Free). |
| 404 | Candidate doesn't exist OR isn't released for your tier yet. |
| 500 | Server error. Retry with backoff; if persistent, email partners@arcada.team. |
Rate limits
No hard rate limit today. Fair-use: we expect <1 RPS sustained per subscriber; spikes are fine, but bulk-scraping the corpus is not — use the paginated /candidates list and cache responses. We’ll reach out before throttling.