# OpenAPI stock research workflow

> End-to-end Finnhub recipe: `import-openapi`, `ati key set`, `ati assist`, and chained `ati run` calls for quotes, insiders, and sentiment with expected JSON fields.

- Repository: Parcha-ai/ati
- GitHub: https://github.com/Parcha-ai/ati
- Human docs: https://grok-wiki.com/public/docs/parcha-ai-ati-a9d4398f11fa
- Complete Markdown: https://grok-wiki.com/public/docs/parcha-ai-ati-a9d4398f11fa/llms-full.txt

## Source Files

- `README.md`
- `manifests/finnhub.toml`
- `specs/finnhub.json`
- `src/cli/call.rs`
- `src/cli/help.rs`
- `docs/assist-examples.md`

---

---
title: "OpenAPI stock research workflow"
description: "End-to-end Finnhub recipe: `import-openapi`, `ati key set`, `ati assist`, and chained `ati run` calls for quotes, insiders, and sentiment with expected JSON fields."
---

The Finnhub provider is an OpenAPI-backed HTTP handler (`handler = "openapi"`) that loads `specs/finnhub.json`, registers tools as `finnhub:<operationId>`, injects the API key as query parameter `token`, and executes GET requests through `ati run` with optional scope-aware discovery via `ati assist`.

## What this workflow covers

| Step | Command / surface | Outcome |
|------|-------------------|---------|
| Import | `ati provider import-openapi` | Writes `~/.ati/specs/finnhub.json` and `~/.ati/manifests/finnhub.toml` |
| Credential | `ati key set finnhub_api_key` | AES-256-GCM keyring entry used as `?token=` on every call |
| Discovery | `ati assist finnhub "…"` | Scoped LLM answer with ordered `ati run` commands |
| Execution | `finnhub:quote`, `finnhub:insider-transactions`, `finnhub:news-sentiment` | Structured JSON from Finnhub REST |

The repository ships a reference manifest at `manifests/finnhub.toml` with `openapi_max_operations = 50`. That cap keeps the first 50 operations from the spec (by path iteration order) and still includes `news-sentiment` (#11), `insider-transactions` (#24), and `quote` (#44). A fresh `import-openapi` run does not add the cap unless you set it manually.

## Prerequisites

<Note>
Finnhub requires a free API key from [finnhub.io](https://finnhub.io/). `news-sentiment` is US-companies only per the OpenAPI description.
</Note>

- ATI installed and initialized (`ati init` creates `~/.ati/`)
- Network access to `https://finnhub.io/api/v1`
- For `ati assist`: a configured LLM backend (cloud keys) or `--local` / `ATI_ASSIST_PROVIDER=local` per `docs/assist-examples.md`

## End-to-end flow

```mermaid
sequenceDiagram
    participant Agent
    participant ATI as ati CLI
    participant Dir as ~/.ati/
    participant FH as finnhub.io/api/v1

    Agent->>ATI: provider import-openapi spec URL
    ATI->>Dir: specs/finnhub.json + manifests/finnhub.toml
    Agent->>ATI: key set finnhub_api_key
    ATI->>Dir: keyring.enc
    Agent->>ATI: assist finnhub "research AAPL…"
    ATI->>ATI: ManifestRegistry + LLM (/help local or proxy)
    ATI-->>Agent: finnhub:quote / insider / news-sentiment commands
    Agent->>ATI: run finnhub:quote --symbol AAPL
    ATI->>Dir: decrypt key, classify query params
    ATI->>FH: GET /quote?symbol=AAPL&token=***
    FH-->>ATI: Quote JSON
    ATI-->>Agent: text / json / table output
```

<Steps>
<Step title="Import the Finnhub OpenAPI spec">

Download the spec and generate the provider manifest. Provider name defaults from the URL (`finnhub`); auth is auto-detected as query key `token` with keyring name `finnhub_api_key`.

```bash
ati provider import-openapi https://finnhub.io/api/v1/openapi.json
# or preview without writing:
ati provider import-openapi https://finnhub.io/api/v1/openapi.json --dry-run
```

Optional flags from `ati provider import-openapi`:

<ParamField body="--name" type="string">
Override auto-derived provider name (default: `finnhub`).
</ParamField>

<ParamField body="--auth-key" type="string">
Override keyring key name (default: `{name}_api_key` → `finnhub_api_key`).
</ParamField>

<ParamField body="--include-tags" type="string[]">
Only register operations matching these OpenAPI tags.
</ParamField>

<ParamField body="--dry-run" type="boolean">
Print generated TOML and operation count without saving.
</ParamField>

Verify registration:

```bash
ati provider info finnhub
ati tool list --provider finnhub | head -10
ati provider inspect-openapi ~/.ati/specs/finnhub.json
```

</Step>

<Step title="Store the API key">

```bash
ati key set finnhub_api_key "YOUR_FINNHUB_TOKEN"
ati key list   # masked values
```

Alternative without keyring write:

```bash
export ATI_KEY_FINNHUB_API_KEY="YOUR_FINNHUB_TOKEN"
```

The bundled manifest uses `auth_type = "query"`, `auth_query_name = "token"`, and `auth_key_name = "finnhub_api_key"`.

</Step>

<Step title="Discover the research chain with assist">

Scoped assist limits context to Finnhub tools and returns runnable commands:

```bash
ati assist finnhub "research Apple stock — price, insider activity, and sentiment"
```

Typical assist output (from README) orders:

1. `ati run finnhub:quote --symbol AAPL`
2. `ati run finnhub:insider-transactions --symbol AAPL`
3. `ati run finnhub:news-sentiment --symbol AAPL`

`ati assist` auto-detects proxy mode when `ATI_PROXY_URL` is set and forwards to `/help`; otherwise it loads the local registry and calls the internal LLM provider.

</Step>

<Step title="Run the three-tool research chain">

```bash
SYMBOL=AAPL

ati run finnhub:quote --symbol "$SYMBOL"
ati run finnhub:insider-transactions --symbol "$SYMBOL"
ati run finnhub:news-sentiment --symbol "$SYMBOL"
```

For machine-readable output across the chain:

```bash
ati --output json run finnhub:quote --symbol AAPL
ati --output json run finnhub:insider-transactions --symbol AAPL
ati --output json run finnhub:news-sentiment --symbol AAPL
```

<Tip>
Tool names use colon namespacing (`finnhub:quote`). The proxy also accepts underscore form (`finnhub_quote`) by resolving the first underscore to a colon.
</Tip>

</Step>
</Steps>

## Provider manifest (reference)

The committed manifest matches what `import-openapi` generates, plus an operation cap:

```toml
[provider]
name = "finnhub"
handler = "openapi"
base_url = "https://finnhub.io/api/v1"
openapi_spec = "finnhub.json"
auth_type = "query"
auth_query_name = "token"
auth_key_name = "finnhub_api_key"
category = "finance"
openapi_max_operations = 50
```

At load time, `ManifestRegistry` reads `~/.ati/specs/finnhub.json` (sibling of `manifests/`), applies filters including `openapi_max_operations`, and registers tools as `{provider}:{operationId}` with `x-ati-param-location` metadata on each schema property for query/path/header/body routing.

## Research tools

| ATI tool | HTTP | Required args | Notes |
|----------|------|---------------|-------|
| `finnhub:quote` | `GET /quote` | `--symbol` | Real-time US quote; avoid constant polling |
| `finnhub:insider-transactions` | `GET /stock/insider-transactions` | `--symbol` | Max 100 transactions per call; optional `--from`, `--to` (dates) |
| `finnhub:news-sentiment` | `GET /news-sentiment` | `--symbol` | US companies only |

Example invocations with optional date window on insiders:

```bash
ati run finnhub:insider-transactions --symbol AAPL --from 2025-01-01 --to 2025-12-31
```

## Expected response fields

### `finnhub:quote` → `Quote`

Flat object; default text output prints key/value lines.

| Field | Type | Meaning |
|-------|------|---------|
| `c` | number | Current price |
| `d` | number | Change |
| `dp` | number | Percent change |
| `h` | number | Day high |
| `l` | number | Day low |
| `o` | number | Open |
| `pc` | number | Previous close |

<ResponseExample>

```json
{
  "c": 262.52,
  "d": -1.23,
  "dp": -0.4664,
  "h": 266.15,
  "l": 261.43,
  "o": 264.65,
  "pc": 263.75
}
```

</ResponseExample>

### `finnhub:insider-transactions` → `InsiderTransactions`

| Field | Type | Meaning |
|-------|------|---------|
| `symbol` | string | Company symbol |
| `data` | array | Insider transaction rows |

Each `data[]` element (`Transactions`):

| Field | Type | Meaning |
|-------|------|---------|
| `name` | string | Insider name |
| `symbol` | string | Symbol |
| `share` | integer | Shares held after transaction |
| `change` | integer | Share change (positive buy, negative sell) |
| `transactionCode` | string | SEC Form 4 code (e.g. `S` sell, `P` purchase) |
| `transactionPrice` | number | Average transaction price |
| `transactionDate` | string (date) | Transaction date |
| `filingDate` | string (date) | Filing date |

<ResponseExample>

```json
{
  "symbol": "AAPL",
  "data": [
    {
      "name": "COOK TIMOTHY D",
      "symbol": "AAPL",
      "share": 3280295,
      "change": -59751,
      "transactionCode": "S",
      "transactionPrice": 257.57,
      "filingDate": "2025-10-03",
      "transactionDate": "2025-10-01"
    }
  ]
}
```

</ResponseExample>

### `finnhub:news-sentiment` → `NewsSentiment`

| Field | Type | Meaning |
|-------|------|---------|
| `symbol` | string | Requested symbol |
| `companyNewsScore` | number | Company news score |
| `sectorAverageNewsScore` | number | Sector average news score |
| `sectorAverageBullishPercent` | number | Sector average bullish percent |
| `sentiment` | object | `bullishPercent`, `bearishPercent` |
| `buzz` | object | `articlesInLastWeek`, `buzz`, `weeklyAverage` |

<ResponseExample>

```json
{
  "symbol": "AAPL",
  "companyNewsScore": 0.92,
  "sectorAverageNewsScore": 0.58,
  "sectorAverageBullishPercent": 0.62,
  "sentiment": {
    "bullishPercent": 0.71,
    "bearishPercent": 0.29
  },
  "buzz": {
    "articlesInLastWeek": 145,
    "buzz": 1.25,
    "weeklyAverage": 116
  }
}
```

</ResponseExample>

<Warning>
Example numeric values are illustrative shapes. Live values depend on Finnhub market data at call time.
</Warning>

## Chaining pattern for agents

A minimal shell-first pipeline passes the same symbol through all three endpoints and keeps JSON for downstream parsing:

```bash
#!/usr/bin/env bash
set -euo pipefail
SYMBOL="${1:-AAPL}"

quote_json=$(ati --output json run finnhub:quote --symbol "$SYMBOL")
insider_json=$(ati --output json run finnhub:insider-transactions --symbol "$SYMBOL")
sentiment_json=$(ati --output json run finnhub:news-sentiment --symbol "$SYMBOL")

# Agent consumes three JSON blobs (price, insiders, sentiment)
printf '%s\n%s\n%s\n' "$quote_json" "$insider_json" "$sentiment_json"
```

In proxy mode, the same commands run unchanged with `ATI_PROXY_URL` and a JWT carrying scopes such as `tool:finnhub:*` and `help`.

## Operation cap and full catalog

| Configuration | Registered tools | Use when |
|---------------|------------------|----------|
| `import-openapi` (default manifest) | All ~110 operations | Full Finnhub surface |
| `openapi_max_operations = 50` (repo `manifests/finnhub.toml`) | First 50 by spec path order | Smaller agent context |

To raise the cap after import, edit `~/.ati/manifests/finnhub.toml`:

```toml
openapi_max_operations = 110   # or remove the line for unlimited
```

Or filter at import time:

```bash
ati provider import-openapi https://finnhub.io/api/v1/openapi.json --include-tags Default
```

## Troubleshooting

| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| Tool not found | Provider not imported or cap excludes operation | `ati tool search quote`; adjust `openapi_max_operations` |
| 401 / invalid token | Missing or wrong key | `ati key set finnhub_api_key …` or `ATI_KEY_FINNHUB_API_KEY` |
| Empty `data` on insiders | Symbol or date range | Confirm `--symbol`; try wider `--from` / `--to` |
| `news-sentiment` error | Non-US symbol | Use a US-listed ticker |
| Assist unavailable | No LLM credentials | Configure cloud keys or `ati assist --local` |

## Related pages

<CardGroup>
<Card title="Import OpenAPI providers" href="/import-openapi">
Spec download, auth detection, `x-ati-param-location`, and operation filters.
</Card>
<Card title="Quickstart" href="/quickstart">
`ati init`, first provider, key storage, and verification with `ati tool info`.
</Card>
<Card title="Assist and plan reference" href="/assist-and-plan-reference">
`ati assist` flags, scoped queries, plan mode, and LLM backends.
</Card>
<Card title="Providers and handlers" href="/providers-and-handlers">
OpenAPI handler dispatch, `finnhub:tool` naming, and auth injection.
</Card>
<Card title="Configure JWT and keys" href="/configure-jwt-and-keys">
`ati key set`, proxy tokens, and scoped `tool:finnhub:*` access.
</Card>
</CardGroup>
