Developers · v1

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.

Generate a token →See pricing

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

GET/api/v1/me

Health-check for your token. Returns the calling subscriber's uid + tier.

{
  "ok": true,
  "data": {
    "uid": "ToxCl6Lq...",
    "tier": "pro",
    "token_suffix": "f1a7ec8a"
  }
}
GET/api/v1/candidates

Paginated list of delivered candidates (public-safe shape).

branchstring, optionalOne of ai_deep_tech, open_source, bootstrapped_oss, fintech, b2b_saas, construct_tech.
limitinteger, default 25, max 100Hard-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.

GET/api/v1/candidates/{id}

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.

POST/DELETE/api/v1/token

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

400Bad request — invalid query param (e.g. limit=abc).
401Missing / malformed / unknown bearer token.
403Token belongs to a tier that doesn't include API access (Starter / Free).
404Candidate doesn't exist OR isn't released for your tier yet.
500Server 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.

Questions? Email partners@arcada.team — partner desk handles integration questions, custom endpoints, white-label deals.