# Tempo API

## Overview

The Tempo API is Tempo's next-generation data API. It is designed for
clients that need a high-level surface for exploring the Tempo chain.

## Usage

The Tempo API is a composable [Hono](https://hono.dev) app. You can run your own
instance (**Self-hosted**) or call the hosted API directly (**Hosted**).

### Hosted

Call the hosted Tempo API directly at `https://api.tempo.xyz`:

```sh
curl https://api.tempo.xyz/v1/tokens
```

```ts
const response = await fetch('https://api.tempo.xyz/v1/tokens/0x…')
const token = await response.json()
```

Explore the API interactively at the root [`/`](https://api.tempo.xyz).

### Self-hosted

Install the package:

```sh
pnpm i tapimo
```

Stateful features (webhook subscriptions, verified tokens) live in Postgres.
Provision a database and apply migrations with the CLI:

```sh
DATABASE_URL=postgresql://… tapimo admin migrate
```

The API is composed from route groups (apps) imported from `tapimo/apps`.
Create a base app with `App.create` and mount the apps you want:

<!-- prettier-ignore -->
```ts
import { App, Db } from 'tapimo'
import { data, funding } from 'tapimo/apps'

const app = App.create({
  db: Db.postgres({ connectionString: process.env.DATABASE_URL }),
})
  .route('/', data())
  .route('/', funding())
```

| App            | Purpose                        | Resources (non-exhaustive)                     |
| -------------- | ------------------------------ | ---------------------------------------------- |
| `data()`       | Chain and indexed data reads   | Blocks, transactions, tokens, webhooks         |
| `funding()`    | Inbound funding                | Quotes, chains, providers, deposits, transfers |
| `relay()`      | Wallet relay & fee sponsorship | Relay, sponsor                                 |
| `management()` | Tenancy and access management  | Orgs, projects, members, API keys              |

The composed app exposes a Web `fetch` method, and `App.listener` adapts it
to a Node.js request listener, so you can serve it from any runtime or
framework:

<!-- prettier-ignore -->
```ts
createServer(App.listener(app))              // Node.js
Bun.serve({ fetch: app.fetch })              // Bun
Deno.serve({ fetch: app.fetch })             // Deno
server.all('*', (c) => app.fetch(c.request)) // Elysia
server.use(App.listener(app))                // Express
server.use((c) => app.fetch(c.req.raw))      // Hono
export const GET = app.fetch                 // Next.js
export const POST = app.fetch                // Next.js
```

## Typed Client

Use the typed client for endpoint autocomplete and API-key/header configuration:

```ts twoslash
import { Client } from 'tapimo'

const client = Client.create({
  // API key sugar for the canonical `tempo-api-key` header.
  apiKey: 'tempo_api_key',
  // Override the API URL. Defaults to `Client.defaultUrl`.
  url: 'https://api.tempo.xyz',
  // Or pass custom headers directly.
  headers: { authorization: 'Bearer tempo_api_key' },
})
```

Requests are end-to-end type-safe across params, statuses, response bodies, and errors. For example, `GET /v1/tokens/:token`:

```ts twoslash
const response = await client.v1.tokens[':token'].$get({
  param: { token: '0x20c0000000000000000000000000000000000000' },
})

// Narrow responses by status
if (response.status !== 200) {
  response.status
  //       ^? (property) status: 400 | 401 | 402 | 403 | 404 | 429 | 502

  if (response.status === 404) {
    const json = await response.json()
    //    ^? const json: { error: { code: "token_not_found"; message: string; ... }; requestId: string }
    json.error.code
    //         ^? (property) code: "token_not_found"
  } else if (response.status === 402) {
    const challenge = await response.text()
    //    ^? const challenge: string
    // Handle the payment challenge from `WWW-Authenticate`.
  } else {
    const json = await response.json()
    //    ^? const json: { error: { code: "query_invalid" | "api_key_invalid" | "upstream_error" | ...; ... }; requestId: string }
    // Handle validation, auth, rate-limit, or upstream errors.
  }

  throw new Error('Request failed')
}

response.status
//       ^? (property) status: 200
const token = await response.json()
//    ^? const token: { address: Hex.Hex; currency: string; decimals: number; name: string; ... }
token.symbol
//    ^? (property) symbol: string
```

## Development

### Quickprompt

Paste into your agent:

```text
Read https://github.com/tempoxyz/api/blob/main/README.md, then spin up the
Tempo API container and provide me links to the services.
```

### Prerequisites

- [OrbStack](https://orbstack.dev): runs the Docker Compose stack and serves the `*.tempo.local` domains
- [pnpm](https://pnpm.io)

### Commands

```sh
pnpm install        # Install dependencies
pnpm dev            # Start the local stack (Docker Compose)
pnpm test           # Run tests against live resources
pnpm test:local     # Run tests against local resources
```

### Services

`pnpm dev` spins up:

| Service    | Purpose                        | URL                            |
| ---------- | ------------------------------ | ------------------------------ |
| `api`      | Tempo API                      | `http://api.tempo.local`       |
| `console`  | Developer Console              | `http://console.tempo.local`   |
| `postgres` | Database                       | `localhost:5432`               |
| `tempo`    | Tempo Node                     | `http://rpc.tempo.local:8545`  |
| `tidx`     | Indexer                        | `http://tidx.tempo.local:8080` |
| `stripe`   | Stripe CLI (Webhook Forwarder) | -                              |
