--- title: "Quickstart" description: "Sign in to the Obol dashboard, connect a model provider, mint a virtual key, and make your first authenticated call through the hosted gateway." --- This walks from a new account to one authenticated call on `/v1/chat/completions` and one MCP client pointed at `/mcp`, using Obol's hosted platform. Nothing is installed and no infrastructure is yours to run. These examples use the production domains. Hosted Control and gateway are still being commissioned; complete this quickstart after your workspace is enabled. Three addresses matter: | Surface | URL | Serves | | --- | --- | --- | | Gateway | `https://gateway.tryobol.dev` | `/v1/*`, `/mcp`, `/hooks` webhook ingest | | Control plane API | `https://control.tryobol.dev` | `/api/v1`, `/oauth`, `/.well-known` | | Dashboard | `https://app.tryobol.dev` | the operator UI | ## Prerequisites - An Obol account. You create one by signing in at the dashboard. - An API key for at least one model provider (OpenAI, Anthropic, OpenRouter, or any OpenAI-compatible endpoint). Obol injects your key on the outbound hop; it never issues one. ## Create a workspace Open `https://app.tryobol.dev` and sign in. The dashboard calls `GET /api/v1/me`, which provisions an organization and a first workspace on first sign-in and returns `default_workspace_id`. Calling it again returns the same workspace. To do the same by hand, take a session token from the signed-in browser and send it as a bearer token to control: ```bash curl -fsS https://control.tryobol.dev/api/v1/me \ -H "Authorization: Bearer $OBOL_SESSION_TOKEN" ``` ```json { "user": { "id": "usr_...", "email": "you@example.com" }, "workspaces": [{ "id": "ws_...", "slug": "default", "env": "dev", "role": "owner" }], "default_workspace_id": "ws_..." } ``` Control's `/api/v1` routes authenticate a human operating the dashboard. They never accept a virtual key, and a virtual key never reaches control. The two credentials are not interchangeable — see [invariants](/concepts/invariants). ## Connect a model provider A call to `/v1/chat/completions` resolves to a model connection in the workspace's published snapshot. Create one, supplying your own provider key: ```bash curl -fsS -X POST \ "https://control.tryobol.dev/api/v1/workspaces/$WORKSPACE_ID/model-providers" \ -H "Authorization: Bearer $OBOL_SESSION_TOKEN" \ -H "content-type: application/json" \ -d '{ "provider": "openai", "api_key": "sk-...", "model_pattern": "gpt-4o-mini", "upstream_model": "gpt-4o-mini" }' ``` Supported `provider` values are `openai`, `anthropic`, `openrouter`, and `openai_compatible`. The last one requires an explicit `base_url`. `model_pattern` is what your client asks for; `upstream_model` is what the provider is asked for. The key is sealed to the workspace and published into the gateway snapshot in the same request — the response reports `status: "active"` once that completes. {/* TODO: Confirm whether the dashboard exposes a model-provider connect flow; no apps/web caller for /model-providers was found in this checkout. */} ## Mint a virtual key Mint from the dashboard's Keys page, or call control directly: ```bash curl -fsS -X POST \ "https://control.tryobol.dev/api/v1/workspaces/$WORKSPACE_ID/keys" \ -H "Authorization: Bearer $OBOL_SESSION_TOKEN" \ -H "content-type: application/json" \ -d '{"agent_id": "support-bot"}' ``` The response carries `plaintext` exactly once: ```json { "id": "key_...", "agent_id": "support-bot", "prefix": "ob_test", "last4": "9f2c", "scopes": ["mcp", "llm"], "plaintext": "ob_test_..." } ``` Plaintext is returned on mint and never stored, echoed, logged, or re-served. Capture it now or revoke and mint again. The prefix follows the workspace environment: a `prod` workspace mints `ob_live_`, every other environment mints `ob_test_`. Pass `"prefix": "ob_live"` to override. Both forms are the same credential shape and both authenticate at the gateway. Default scopes are `["mcp", "llm"]` — `/v1/*` requires `llm` and `/mcp` requires `mcp`, so a key minted with narrower scopes will be refused on the route it does not carry. More in [virtual keys](/security/virtual-keys). ## Make your first call The gateway speaks the OpenAI-compatible surface on `/v1/*`. Point any OpenAI-compatible client at `https://gateway.tryobol.dev/v1` with the key as the bearer token. ```bash curl curl -fsS https://gateway.tryobol.dev/v1/chat/completions \ -H "Authorization: Bearer $OBOL_KEY" \ -H "content-type: application/json" \ -d '{ "model": "gpt-4o-mini", "messages": [{"role": "user", "content": "Summarize ticket 4182."}] }' ``` ```python Python import os from openai import OpenAI client = OpenAI( base_url="https://gateway.tryobol.dev/v1", api_key=os.environ["OBOL_KEY"], ) response = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": "Summarize ticket 4182."}], ) ``` ```typescript TypeScript import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://gateway.tryobol.dev/v1", apiKey: process.env.OBOL_KEY, }); const response = await client.chat.completions.create({ model: "gpt-4o-mini", messages: [{ role: "user", content: "Summarize ticket 4182." }], }); ``` The gateway also serves `/v1/responses`, `/v1/embeddings`, and `/v1/messages` on the same key. `/v1/route` is a separate discovery route and is never part of a call — see [routing](/gateway/overview). ## Point an MCP client at the gateway Tools are served from a single endpoint, `https://gateway.tryobol.dev/mcp`, with the same virtual key as the bearer token. `GET /api/v1/workspaces/{workspace_id}/mcp` returns the addresses to paste into a client: ```json { "workspace_id": "ws_...", "env": "dev", "gateway_url": "https://gateway.tryobol.dev", "mcp_url": "https://gateway.tryobol.dev/mcp", "openai_base_url": "https://gateway.tryobol.dev/v1", "key_prefix": "ob_test" } ``` Control returns addresses only. It cannot fill in the key, because the plaintext existed for one render in your browser and was never stored. A client configuration looks like this: ```json title="MCP client configuration" { "mcpServers": { "obol": { "url": "https://gateway.tryobol.dev/mcp", "headers": { "Authorization": "Bearer ob_test_..." } } } } ``` `tools/list` returns only the tools this key is permitted to call. A tool the key cannot call is never shown. Destructive tools in a `prod` workspace require an approval unless policy explicitly grants them. The gateway answers with an approval requirement, and the client resupplies `x-obol-approval-id` and `x-obol-approval-token` on the retry. ## Self-hosting Running the whole Obol stack — gateway, control plane, worker, dashboard — in your own environment is available on the Enterprise plan. See [self-hosting](/get-started/self-hosting) for what it involves and how to have it enabled. ## Next steps Scopes, budgets, environments, rotation, and revocation. CEL for visibility, Cedar for permission, approvals for destructive tools. How the data plane and control plane divide the work. Workspaces, keys, policies, approvals, and receipts.