# Expose OpenAPI & Scalar

Turn on the generated **OpenAPI** document and the interactive **Scalar** API reference UI for an existing oRPC service — as a one-line `defineService` option or by mounting the Layer-1 primitives onto any Hono app yourself.

## Prerequisites

**What you need before you start**

| Name | Type | Description |
| --- | --- | --- |
| `An oRPC service` | `router` | An existing service with an oRPC router whose contract inputs/outputs are zod schemas. See the Services hub or the Build-a-service tutorial. |
| `@netscript/service` | `package` | Already a dependency of every generated service. Exposes defineService, the createService builder, and the createOpenAPISpec / createScalarDocs / createScalarJs primitives. |
| `zod schemas` | `contract` | Spec generation reads your contract's zod schemas via the ZodToJsonSchemaConverter; routes without zod input/output produce an empty schema for that operation. |

> Already on by default in the scaffold
>
> Generated service entrypoints call
>
> defineService(router, { openapi: { … } })
>
> , so a freshly scaffolded service
>
> already
>
> serves the spec at
>
> /api/openapi.json
>
> and the Scalar UI at
>
> /api/docs
>
> . This recipe is for adding the surface to a service that does not have it yet, or for hand-wiring the primitives into a custom Hono host.

## Steps

### 1. Turn it on with `defineService` (the one-line path)

Pass an `openapi` block to the preset. That single option enables the generated OpenAPI JSON spec, the Scalar docs page, and the bundled Scalar JS — no extra imports.

```ts
// services/users/src/main.ts
import { defineService } from '@netscript/service';
import { router } from './router.ts';

await defineService(router, {
  name: 'users',
  version: '1.0.0',
  port: parseInt(Deno.env.get('PORT') || '3001'),
  openapi: {
    title: 'Users API',
    description: 'User management service',
  },
});
```

Aspire injects `PORT` at runtime, so the entrypoint reads it from the environment; the typed source of truth is your `netscript.config.ts` `services.<name>.port` field, which the scaffold wires as the fallback default — set the port there rather than editing this line.

This wires three routes onto the service:

**Routes added by the openapi option**

| Name | Type | Description |
| --- | --- | --- |
| `/api/openapi.json` | `GET → JSON` | The machine-readable OpenAPI document, generated from your contract's zod schemas. |
| `/api/docs` | `GET → HTML` | The Scalar interactive API reference UI; it fetches the spec above and renders try-it docs. |
| `/api/docs/scalar.js` | `GET → JS` | The locally bundled Scalar runtime, so the docs UI works offline with no CDN dependency. |

### 2. Hit the endpoints

With the service running (directly via `deno task --cwd services/users dev`, or under `aspire start` for DB-backed services), open the docs UI and fetch the raw spec:

```sh
# from a shell — confirm both surfaces answer
curl http://localhost:3001/api/openapi.json   # raw OpenAPI document
open http://localhost:3001/api/docs           # Scalar reference UI in a browser
```

The REST surface generated from the same contract is served under `/api/*`, and the typed oRPC endpoint stays at `/api/rpc/*` — the spec describes the former.

### 3. (Optional) Hand-wire the primitives onto a host app

When you need finer control — a custom path, a different theme, or mounting onto an existing Hono app that is not a `defineService` runtime — drop down to the three Layer-1 primitives. `createOpenAPISpec` serves the spec JSON, `createScalarDocs` serves the Scalar HTML, and `createScalarJs` serves the bundled runtime so the UI loads offline.

```ts
// services/users/src/main.ts
import { defineService } from '@netscript/service';
import { router } from './router.ts';

// One option turns on the spec, the Scalar UI, and the bundled JS.
await defineService(router, {
  name: 'users',
  version: '1.0.0',
  port: 3001,
  openapi: {
    title: 'Users API',
    description: 'User management service',
  },
});
```

```ts
// services/users/src/main.ts — step-by-step, with a custom spec URL
import { createService } from '@netscript/service';
import { router } from './router.ts';

await createService(router, { name: 'users', version: '1.0.0', port: 3001 })
  .withCors()
  .withLogger()
  .withOpenAPI({ title: 'Users API', description: 'User management service' })
  .withDocs() // optional: .withDocs({ specUrl: '/api/openapi.json' })
  .withRPC()
  .withHealth()
  .serve();
```

```ts
// host/openapi-routes.ts — wire the primitives onto an existing Hono app
import {
  createOpenAPISpec,
  createScalarDocs,
  createScalarJs,
} from '@netscript/service';
import { router } from './router.ts';

// 1. Serve the OpenAPI JSON document.
app.get('/api/openapi.json', createOpenAPISpec(router, {
  title: 'Users API',
  version: '1.0.0',
  description: 'User management service',
  servers: [{ url: '/api', description: 'local' }],
}));

// 2. Serve the Scalar UI that loads that spec.
app.get('/api/docs', createScalarDocs({
  specUrl: '/api/openapi.json',
  title: 'Users API',
  theme: 'kepler',
}));

// 3. Serve the bundled Scalar runtime (offline, no CDN).
app.get('/api/docs/scalar.js', createScalarJs());
```

## Option keys

The primitives accept these confirmed option shapes (`@netscript/service`):

**OpenAPIConfig (createOpenAPISpec)**

| Name | Type | Description |
| --- | --- | --- |
| `title` | `string (required)` | API title shown in the spec and the Scalar UI. |
| `version` | `string (required)` | API version string, e.g. '1.0.0'. |
| `description` | `string?` | Longer API description rendered in the docs. |
| `servers` | `Array<{ url; description? }>?` | Server URLs advertised in the spec; defaults to a single entry { url: '/api' }. |

**ScalarDocsOptions (createScalarDocs)**

| Name | Type | Description |
| --- | --- | --- |
| `specUrl` | `string (required)` | URL the Scalar UI fetches the OpenAPI document from, e.g. '/api/openapi.json'. |
| `title` | `string?` | Docs page title; defaults to 'API Documentation'. |
| `theme` | `'default' \| 'kepler' \| 'moon' \| 'purple' \| 'saturn'` | Scalar UI theme; defaults to 'kepler'. |

> Spec quality comes from your contract
>
> The OpenAPI document is generated by oRPC's
>
> OpenAPIGenerator
>
> from your router, with a
>
> ZodToJsonSchemaConverter
>
> turning each route's zod schemas into JSON Schema. Richer schemas —
>
> .describe()
>
> , explicit
>
> .method()
>
> on each contract route, named output shapes — produce a richer, more accurate reference. The spec is only as good as the contract behind it.

## In-production pitfalls

> Before you ship the docs UI
>
> - **The Scalar JS is served from your origin, not a CDN.** `/api/docs/scalar.js` streams the bundled runtime from the package's `assets/`. That keeps the UI working offline, but it also means the `/api/docs` page is *three* routes — if you hand-wire the primitives, mount all three (`createOpenAPISpec`, `createScalarDocs`, `createScalarJs`) or the page loads blank.
> - **The docs and spec are public by default.** With the service-auth seam installed, `.withAuthn()` protects `/api` — which includes `/api/openapi.json` and `/api/docs`. If you do *not* want your API surface publicly enumerable, gate it or set `allowAnonymous` deliberately; do not assume the docs are hidden.
> - **The REST surface is `/api/*`; the typed RPC endpoint is `/api/rpc/*`.** The OpenAPI document describes the REST routes — point human/integration consumers at those, and keep generated typed clients on the RPC path.
> - **Routes without zod schemas under-document.** A contract route with no zod input/output yields an empty operation schema in the spec. Add schemas (and `.describe()`) to get useful request/response docs.

## See also

[Services & contracts](https://rickylabs.github.io/netscript/netscript/services-sdk/services/)

[Understand — Services & contracts  The capability hub: how defineService, the fluent builder, and the OpenAPI/Scalar primitives fit together.](https://rickylabs.github.io/netscript/netscript/capabilities/services/)

[Do — Discover services  Recipe: enumerate the services in a workspace and the endpoints each one exposes.](https://rickylabs.github.io/netscript/netscript/services-sdk/how-to/discover-services/)

[Do — Graceful shutdown  Recipe: drain in-flight requests and run teardown hooks before the service process exits.](https://rickylabs.github.io/netscript/netscript/orchestration-runtime/how-to/graceful-shutdown/)

[Look up — @netscript/service  The full generated API: the OpenAPI/Scalar primitives, the builder, and every option key.](https://rickylabs.github.io/netscript/netscript/reference/service/)

[Discover services](https://rickylabs.github.io/netscript/netscript/services-sdk/how-to/discover-services/) [Use a second database](https://rickylabs.github.io/netscript/netscript/data-persistence/how-to/use-a-second-database/)
