Documentation 40

Verified quickstarts · 4 min read

Every snippet here has been run.

14 clients, each proved against the live hosts by a script that executes the snippet on this page — the same string, not a copy of it — and fails if the response is not a 2xx or if what it prints is not the olympus-labs data set. Paste one and it works, or this page is wrong and the check says so.

01Install the pinned client
02Paste the snippet
03See olympus-labs come back

01 / What verified means

A script runs these, not a person.

Each entry below carries its install line, the exact code, the requests that code sends, and the values a check asserts in its output. apps/web/scripts/verify-quickstarts.ts imports the same module this page renders, writes each snippet to a file, runs it in a real Python or Node runtime with the client at the version named, and fails the run on a non-zero exit or on missing output. Every client here raises on a non-2xx, so a coverage 404 or a read-only refusal fails the check as loudly as a 500 would.

terminal
pnpm --filter @sandboxapis/web verify:quickstarts

It is not part of CI — it talks to the live hosts over the internet, and a build must not depend on the network. It is run by hand after any change to a renderer, a client pin, or a snippet. One full run is 39 requests.

02 / Keys

Anonymous works. A free key works better.

Every snippet below sends the literal token any-token, because any token is accepted — there is nothing real behind these hosts to leak or break. Anonymous callers get 60 req/hour — one bucket across every host, not one per host — which is enough to paste a quickstart and look around; a free key raises that to 600 req/hour. Each entry names the slot your key goes in.

Get a free key → · Keys & rate limits →

Nothing to install at all: every provider’s page under /docs carries a try-it console — a locked GET, the provider’s own documented first path already filled in, and a Send button that shows you the status, the rate-limit headers and the JSON. Your browser calls the provider host itself, so the request spends the same anonymous 60 req/hour a curl would and no key of ours is involved. On a network that blocks that call — a corporate proxy that strips CORS headers is the usual one — it retries once through sandboxapis.dev on a shared key, and says so in the answer; that fallback is capped at 30 requests an hour per browser, 60 per network and 2,000 an hour across everyone, and over any of them it points you at a key. The console stores nothing about what you typed: pressing Send counts the provider whose page you were on, and nothing else.

03 / GitHub

GitHub, drop-in.

Base URL https://gh.sandboxapis.dev. The endpoint-by-endpoint coverage, the pins and the spec of record are on the GitHub reference page; the narrative quickstart is at /docs/github.

@octokit/rest

22.0.1gh.sandboxapis.dev
install.sh
npm install @octokit/rest@22.0.1
javascript
import { Octokit } from "@octokit/rest";

const octokit = new Octokit({
  baseUrl: "https://gh.sandboxapis.dev",   // the only line that changes
  auth: "any-token",                       // any token is accepted
});

const { data: repo } = await octokit.repos.get({
  owner: "olympus-labs",
  repo: "parthenon",
});
const { data: prs } = await octokit.pulls.list({
  owner: "olympus-labs",
  repo: "parthenon",
  state: "closed",
  per_page: 5,
});

console.log(repo.full_name, "-", repo.description);
console.log(prs.length, "closed PRs, newest: #" + prs[0].number, prs[0].title);
Sends
  • GET /repos/olympus-labs/parthenon
  • GET /repos/olympus-labs/parthenon/pulls?state=closed&per_page=5
Prints
the repository's full path and description, then the five most recently closed pull requests.
Asserted
olympus-labs/parthenonclosed PRs, newest: #
Your key
Replace any-token in auth — it rides GitHub's own Authorization header.

gh CLI

2.83.2gh.sandboxapis.dev
install.sh
brew install gh   # or see cli.github.com
quickstart.sh
export GH_HOST=gh.sandboxapis.dev
export GH_ENTERPRISE_TOKEN=any-token
export GH_CONFIG_DIR=$(mktemp -d)   # keep this out of your real gh config

gh repo view olympus-labs/parthenon \
  --json nameWithOwner,description \
  --template '{{.nameWithOwner}} - {{.description}}{{"\n"}}'

gh pr list --repo olympus-labs/parthenon --state merged --limit 3
Sends
  • POST /api/graphql (gh repo view)
  • POST /api/graphql (gh pr list)
Prints
the repository line, then the three most recently merged pull requests with their head branches.
Asserted
olympus-labs/parthenonMERGED
Your key
Replace any-token in GH_ENTERPRISE_TOKEN — gh treats any non-github.com host as Enterprise Server.

gh addresses a custom host through the Enterprise Server layout (/api/v3, /api/graphql); the GitHub hosts serve that layout as an alias, so no path prefix reaches your code.

04 / GitLab

GitLab, drop-in.

Base URL https://gl.sandboxapis.dev. The endpoint-by-endpoint coverage, the pins and the spec of record are on the GitLab reference page; the narrative quickstart is at /docs/gitlab.

python-gitlab

6.5.0gl.sandboxapis.dev
install.sh
pip install python-gitlab==6.5.0
python
import gitlab

gl = gitlab.Gitlab(
    "https://gl.sandboxapis.dev",   # the only line that changes
    private_token="any-token",      # any token is accepted
)

project = gl.projects.get("olympus-labs/parthenon")
mrs = project.mergerequests.list(state="merged", per_page=5, get_all=False)

print(project.path_with_namespace, "-", project.description)
print(len(mrs), "merged MRs, newest:", mrs[0].title)
Sends
  • GET /api/v4/projects/olympus-labs%2Fparthenon
  • GET /api/v4/projects/{id}/merge_requests?state=merged&per_page=5
Prints
the project's full path and description, then the five most recently merged merge requests.
Asserted
olympus-labs/parthenonmerged MRs, newest:
Your key
Replace any-token in private_token — it rides GitLab's own PRIVATE-TOKEN header.

@gitbeaker/rest

43.8.0gl.sandboxapis.dev
install.sh
npm install @gitbeaker/rest@43.8.0
javascript
import { Gitlab } from "@gitbeaker/rest";

const api = new Gitlab({
  host: "https://gl.sandboxapis.dev",   // the only line that changes
  token: "any-token",                   // any token is accepted
});

const project = await api.Projects.show("olympus-labs/parthenon");
const mrs = await api.MergeRequests.all({
  projectId: project.id,
  state: "merged",
  perPage: 5,
  maxPages: 1,
});

console.log(project.path_with_namespace, "-", project.description);
console.log(mrs.length, "merged MRs, newest:", mrs[0].title);
Sends
  • GET /api/v4/projects/olympus-labs%2Fparthenon
  • GET /api/v4/projects/{id}/merge_requests?state=merged&per_page=5
Prints
the project's full path and description, then the five most recently merged merge requests.
Asserted
olympus-labs/parthenonmerged MRs, newest:
Your key
Replace any-token in token — gitbeaker sends it as PRIVATE-TOKEN.

05 / Bitbucket

Bitbucket, drop-in.

Base URL https://bb.sandboxapis.dev. The endpoint-by-endpoint coverage, the pins and the spec of record are on the Bitbucket reference page; the narrative quickstart is at /docs/bitbucket.

atlassian-python-api

5.0.4bb.sandboxapis.dev
install.sh
pip install atlassian-python-api==5.0.4
python
from atlassian.bitbucket import Cloud

bb = Cloud(
    url="https://bb.sandboxapis.dev",   # the only line that changes
    username="you@example.com",
    password="any-token",               # any app password is accepted
)

repo = bb.workspaces.get("olympus-labs").repositories.get("parthenon")
prs = [pr.title for pr in repo.pullrequests.each()]

print(repo.get_data("full_name"), "on", repo.get_data("mainbranch")["name"])
print(len(prs), "open PRs, newest:", prs[0])
Sends
  • GET /2.0/workspaces/olympus-labs
  • GET /2.0/repositories/olympus-labs/parthenon
  • GET /2.0/repositories/olympus-labs/parthenon/pullrequests/
  • GET /2.0/repositories/olympus-labs/parthenon/pullrequests/{id} (once per row — the client re-reads each)
Prints
the repository's full name and main branch, then every open pull request on it.
Asserted
olympus-labs/parthenon on mainopen PRs, newest:
Your key
Replace any-token in password — the app-password slot Bitbucket Cloud already uses.

06 / Azure DevOps

Azure DevOps, drop-in.

Base URL https://ado.sandboxapis.dev. The endpoint-by-endpoint coverage, the pins and the spec of record are on the Azure DevOps reference page; the narrative quickstart is at /docs/ado.

azure-devops-node-api

17.0.0ado.sandboxapis.dev
install.sh
npm install azure-devops-node-api@17.0.0
javascript
import * as azdev from "azure-devops-node-api";

const connection = new azdev.WebApi(
  "https://ado.sandboxapis.dev/olympus-labs",   // the organization URL
  azdev.getPersonalAccessTokenHandler("any-token"),
);

const core = await connection.getCoreApi();
const projects = await core.getProjects();
console.log("projects:", projects.map((p) => p.name).join(", "));

const git = await connection.getGitApi();
const repos = await git.getRepositories("olympus-labs");
console.log("repos:", repos.map((r) => r.name).sort().join(", "));
Sends
  • OPTIONS /olympus-labs/_apis/Location, then GET /olympus-labs/_apis/resourceAreas (the SDK's own handshake)
  • OPTIONS /olympus-labs/_apis/core, then GET /olympus-labs/_apis/projects
  • OPTIONS /olympus-labs/_apis/git, then GET /olympus-labs/olympus-labs/_apis/git/repositories
Prints
the organization's projects, then the git repositories inside the project.
Asserted
projects: olympus-labsrepos: parthenon
Your key
Replace any-token in getPersonalAccessTokenHandler — the PAT slot, sent as Basic auth.

The SDK resolves every client through the location service before its first real call. That handshake is served, so getCoreApi() and getGitApi() resolve without configuration.

azure-devops

7.1.0b4ado.sandboxapis.dev
install.sh
pip install azure-devops==7.1.0b4
python
from azure.devops.connection import Connection
from msrest.authentication import BasicAuthentication

connection = Connection(
    base_url="https://ado.sandboxapis.dev/olympus-labs",   # the organization URL
    creds=BasicAuthentication("", "any-token"),            # any PAT is accepted
)

core = connection.clients.get_core_client()
projects = core.get_projects()
print("projects:", ", ".join(sorted(p.name for p in projects)))

git = connection.clients.get_git_client()
repos = git.get_repositories("olympus-labs")
print("repos:", ", ".join(sorted(r.name for r in repos)))
Sends
  • OPTIONS /olympus-labs/_apis — the whole location catalogue in one request
  • GET /olympus-labs/_apis/resourceAreas
  • GET /olympus-labs/_apis/projects
  • GET /olympus-labs/olympus-labs/_apis/git/repositories
Prints
the organization's projects, then the git repositories inside the project.
Asserted
projects: olympus-labsrepos: parthenon
Your key
Replace any-token in the second argument to BasicAuthentication — the PAT slot, sent as Basic auth.

Connection.get_client() resolves every client through the location service before its first real call, and this SDK asks for the whole catalogue in ONE request — OPTIONS /olympus-labs/_apis, with no area segment. That form was uncovered until 2026-09-13, which made this client 404 on its first request and raise before any read; it is served now. Four requests to the Node SDK's six: the catalogue is cached, so no per-area call follows.

requests

2.32.5ado.sandboxapis.dev
install.sh
pip install requests==2.32.5
python
import requests

org = "https://ado.sandboxapis.dev/olympus-labs"   # the organization URL

s = requests.Session()
s.auth = ("", "any-token")            # any PAT is accepted
s.params = {"api-version": "7.1"}

repos = s.get(f"{org}/_apis/git/repositories").json()
repo = repos["value"][0]["name"]

prs = s.get(
    f"{org}/olympus-labs/_apis/git/repositories/{repo}/pullrequests",
    params={"searchCriteria.status": "completed"},
).json()

print(repos["count"], "repos:", repo)
print(prs["count"], "completed PRs, newest:", prs["value"][0]["title"])
Sends
  • GET /olympus-labs/_apis/git/repositories?api-version=7.1
  • GET /olympus-labs/olympus-labs/_apis/git/repositories/parthenon/pullrequests?searchCriteria.status=completed&api-version=7.1
Prints
the repository count and name, then the completed pull requests on it.
Asserted
repos: parthenoncompleted PRs, newest:
Your key
Replace any-token in the second half of s.auth — the PAT slot, sent as Basic auth.

The dependency-free path, for a script that does not want the SDK. It skips the location service entirely and addresses the routes directly, which is why it spends two requests where the SDK above spends four — and why it has to pass the project twice (/{organization}/{project}/_apis/…): the project-less pull-request route 404s, exactly as on dev.azure.com.

07 / Jira

Jira, drop-in.

Base URL https://jira.sandboxapis.dev. The endpoint-by-endpoint coverage, the pins and the spec of record are on the Jira reference page; the narrative quickstart is at /docs/jira.

jira

3.8.0jira.sandboxapis.dev
install.sh
pip install jira==3.8.0
python
from jira import JIRA

jira = JIRA(
    server="https://jira.sandboxapis.dev",              # the only line that changes
    basic_auth=("you@example.com", "any-token"),        # any pair is accepted
)

argo = jira.project("ARGO")
boards = jira.boards(projectKeyOrID="ARGO")

print(argo.key, "-", argo.name)
print(len(boards), "board:", boards[0].name, "/ id", boards[0].id)
Sends
  • GET /rest/api/2/serverInfo (the constructor's own handshake)
  • GET /rest/api/2/project/ARGO
  • GET /rest/agile/1.0/board?projectKeyOrId=ARGO&maxResults=50
Prints
the Argonauts project, then the one scrum board that belongs to it.
Asserted
ARGO - Argonauts1 board: ARGO board
Your key
Replace any-token in the second half of basic_auth — the API-token slot Jira Cloud already uses.

jira.search_issues() is NOT in this snippet on purpose: it calls the classic /rest/api/2/search, which Atlassian removed. This host answers it with the real 410 and points at the enhanced-search family, exactly as Jira Cloud does.

08 / Linear

Linear, drop-in.

Base URL https://linear.sandboxapis.dev. The endpoint-by-endpoint coverage, the pins and the spec of record are on the Linear reference page; the narrative quickstart is at /docs/linear.

fetch (Node built-in)

node 22linear.sandboxapis.dev
install.sh
# nothing to install — Node 22 has fetch
javascript
const api = async (query, variables) => {
  const res = await fetch("https://linear.sandboxapis.dev/graphql", {
    method: "POST",
    headers: { "content-type": "application/json", authorization: "any-token" },
    body: JSON.stringify({ query, variables }),
  });
  const { data, errors } = await res.json();
  if (!res.ok || errors) throw new Error(res.status + " " + JSON.stringify(errors));
  return data;
};

const { teams } = await api("{ teams(first: 10) { nodes { key name } } }");
const { issues } = await api(
  `query($key: String!) {
     issues(filter: { team: { key: { eq: $key } } }, first: 3) {
       nodes { identifier title state { name } }
     }
   }`,
  { key: "ARGO" },
);

console.log("teams:", teams.nodes.map((t) => t.key + " " + t.name).join(", "));
for (const i of issues.nodes) console.log(i.identifier, i.title, "[" + i.state.name + "]");
Sends
  • POST /graphql — teams(first: 10)
  • POST /graphql — issues(filter: { team: { key: { eq: "ARGO" } } }, first: 3)
Prints
every team key and name, then three Argonauts issues with their workflow states.
Asserted
ARGO ArgonautsORCL OraclesARGO-
Your key
Replace any-token in the authorization header — Linear takes a bare API key there, no Bearer prefix.

Linear has no REST dialect, so this is the whole client: one endpoint, the real schema subset. An uncovered field returns an explicit coverage error in errors[], never a silent null — which is why this snippet throws on errors rather than reading through them.

09 / Google Antigravity

Google Antigravity, drop-in.

Base URL https://antigravity.sandboxapis.dev. The endpoint-by-endpoint coverage, the pins and the spec of record are on the Google Antigravity reference page; the narrative quickstart is at /docs/antigravity.

google-genai

2.32.0antigravity.sandboxapis.dev
install.sh
pip install google-genai==2.32.0
python
from google import genai

client = genai.Client(
    api_key="any-token",                                          # any key is accepted
    http_options={"base_url": "https://antigravity.sandboxapis.dev"},  # the only line that changes
)

triggers = client.triggers.list().triggers
print("triggers:", ", ".join(t.display_name for t in triggers))

executions = client.triggers.list_executions(trigger_id=triggers[0].id).trigger_executions
run = next(e for e in executions if e.interaction_id)
interaction = client.interactions.get(id=run.interaction_id)
print("interaction", interaction.id, interaction.status, len(interaction.steps or []), "steps")
Sends
  • GET /v1beta/triggers
  • GET /v1beta/triggers/{trigger_id}/executions
  • GET /v1beta/interactions/{interaction_id}
Prints
every scheduled trigger's display name, then the status and step count of an interaction one of them started.
Asserted
triggers:interaction v1_
Your key
Replace any-token in api_key: it rides the x-goog-api-key header the SDK already sends.

There is no list of interactions in this API, here or upstream, so the snippet reaches one the way a read-only client must: through a trigger's executions. Inference (interactions.create, models.generate_content) is not served.

010 / Prometheus

Prometheus, drop-in.

Base URL https://prom.sandboxapis.dev. The endpoint-by-endpoint coverage, the pins and the spec of record are on the Prometheus reference page; the narrative quickstart is at /docs/prometheus.

prometheus-api-client

0.7.2prom.sandboxapis.dev
install.sh
pip install prometheus-api-client==0.7.2
python
from prometheus_api_client import PrometheusConnect

prom = PrometheusConnect(
    url="https://prom.sandboxapis.dev",           # the only line that changes
    headers={"Authorization": "Bearer any-token"},  # any token is accepted
    disable_ssl=False,
)

names = prom.all_metrics()
print("metrics:", ", ".join(sorted(n for n in names if n.startswith("http_"))))

p99 = prom.custom_query('http_request_duration_seconds{quantile="0.99"}')
print("p99 by service:", ", ".join(sorted(r["metric"]["service"] for r in p99)))
Sends
  • GET /api/v1/label/__name__/values
  • GET /api/v1/query
Prints
the data set's HTTP metric names, then every service that reports a p99 latency at 00:00 UTC today.
Asserted
metrics:http_request_duration_secondspipeline-
Your key
Replace any-token in headers={"Authorization": "Bearer ..."}: prometheus-api-client sends the dict on every request.

custom_query sends no time, so the server evaluates at its own now, which is 00:00 UTC today, the data set's last instant. A client that sends its own clock gets an empty answer for anything after that.

011 / Grafana

Grafana, drop-in.

Base URL https://grafana.sandboxapis.dev. The endpoint-by-endpoint coverage, the pins and the spec of record are on the Grafana reference page; the narrative quickstart is at /docs/grafana.

fetch (Node built-in)

node 22grafana.sandboxapis.dev
install.sh
# nothing to install: Node 22 has fetch
javascript
const grafana = "https://grafana.sandboxapis.dev";   // the only line that changes
const headers = {
  authorization: "Bearer any-token",                 // any token is accepted
  "content-type": "application/json",
};

const boards = await (await fetch(`${grafana}/api/search?type=dash-db`, { headers })).json();
console.log("dashboards:", boards.map((b) => b.uid).sort().join(", "));

const res = await fetch(`${grafana}/api/ds/query`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    queries: [{ refId: "A", datasource: { uid: "prometheus" }, expr: "service:http_request_errors:ratio" }],
    from: "now-24h",
    to: "now",
  }),
});
const { results } = await res.json();
const services = results.A.frames.map((f) => f.schema.fields[1].labels.service);
console.log("error ratio frames:", services.sort().join(", "));
Sends
  • GET /api/search?type=dash-db
  • POST /api/ds/query
Prints
every dashboard's uid (one per service), then the services whose error ratio the Prometheus data source returns for the data set's last 24 hours, one data frame each.
Asserted
dashboards:svc-pipeline-error ratio frames:pipeline-
Your key
Replace any-token in the authorization header: a Grafana service-account token rides there as Bearer <token>, and so does a SandboxAPIs key.

now in from and to is 00:00 UTC today on this host, the data set's last instant, so the last 24 hours always hold data. mcp-grafana needs no snippet: set GRAFANA_URL to the base URL and give it any service-account token.

012 / Datadog

Datadog, drop-in.

Base URL https://api.dd.sandboxapis.dev. The endpoint-by-endpoint coverage, the pins and the spec of record are on the Datadog reference page; the narrative quickstart is at /docs/datadog.

datadog-api-client

2.62.0api.dd.sandboxapis.dev
install.sh
pip install datadog-api-client==2.62.0
python
import time
from datadog_api_client import ApiClient, Configuration
from datadog_api_client.v1.api.monitors_api import MonitorsApi
from datadog_api_client.v1.api.metrics_api import MetricsApi

config = Configuration(host="https://api.dd.sandboxapis.dev")   # the only line that changes (DD_SITE is checked against Datadog's own sites)
config.api_key["apiKeyAuth"] = "any-token"                       # any key is accepted
config.api_key["appKeyAuth"] = "any-token"

with ApiClient(config) as client:
    monitors = MonitorsApi(client).list_monitors()
    print(len(monitors), "monitors, services:", ", ".join(sorted({t for m in monitors for t in m.tags if t.startswith("service:")})))
    now = int(time.time())
    q = MetricsApi(client).query_metrics(_from=now - 86400, to=now, query="avg:trace.http.request.hits{*} by {service}")
    print("series:", ", ".join(sorted(s.scope for s in q.series)))
Sends
  • GET /api/v1/monitor
  • GET /api/v1/query
Prints
how many monitors the data set carries and the services they watch, then one request-count series per service over the last 24 hours.
Asserted
monitors, services:service:pipeline-series:
Your key
Replace any-token in config.api_key["apiKeyAuth"]: it rides the DD-API-KEY header the client already sends.

The last 24 hours always overlap the data set, which ends at 00:00 UTC today.

Client notes

What a client does that a snippet cannot show.

Every entry above is a snippet that runs. These are the things that stop one from running — a constructor that takes something other than a URL, a module format, a path this sandbox deliberately does not serve. They came out of a sweep that configured thirteen further clients exactly as each one's own documentation says and drove them against the live hosts, and each is a limit of the client or of our coverage rather than something a renderer can fix.

  • @octokit/graphql (GitHub) — The GraphQL search connection is not in the served schema subset, so a search(type: ISSUE) query comes back with an explicit coverage error in errors[] rather than a silent null.
  • bitbucket (Bitbucket) — The npm package is CommonJS, so a named ESM import throws SyntaxError: Named export 'Bitbucket' not found — use require, or the default import and destructure Bitbucket off it.
  • @anthropic-ai/sdk (Anthropic) — This host mirrors the Admin API and nothing else, so messages.create(), models.list() and models.retrieve() all answer 404; the reads that work are the organization ones, reached with client.get("/v1/organizations/…").
  • pybuildkite (Buildkite) — There is no base_url constructor argument — it is a plain attribute, so assign bk.base_url after constructing the client, with the trailing slash the client expects to concatenate onto.
  • sentry-cli (Sentry) — Its info and releases commands call GET /api/0/ and the project-scoped release path, neither of which this host serves; the org-scoped release list is served under its own path.
  • @pagerduty/pdjs and pdpyras (PagerDuty) — A bare GET /incidents comes back empty because PagerDuty's own default window is the last month and this data set's incident is older than that — pass date_range=all and it is there.
  • zenpy (Zendesk) — It takes subdomain and domain rather than a URL, so the swap is subdomain="zendesk" with domain="sandboxapis.dev" — every other client in the sweep took a base URL.

Next

Where to go from here.

All quickstarts → · Coverage manifest → · Tour the universe → · MCP setup →