<!-- Source: https://docs.geopera.com/api-reference/sdks/typescript · Markdown for LLMs -->

# TypeScript SDK

The TypeScript client is **generated from the kernel's OpenAPI document** — the same
document that drives the REST API and the Python SDK. Because the OpenAPI is itself a
projection of the operation registry, the generated types track the live API by
construction: add an operation, regenerate, and you get a fully-typed method for it.

This is the typed client the Geopera portal uses against its own backend, and you can
generate the identical types for your own project from the published OpenAPI.

## Generate the types

Use [`openapi-typescript`](https://github.com/openapi-ts/openapi-typescript) to turn the
OpenAPI document into a `.d.ts` of operation types:

```bash
npx openapi-typescript https://api.geopera.com/openapi.json -o kernel-api.d.ts
```

That gives you an `operations` map keyed by `operation_id`, from which you can derive the
input and output type of any operation:

```ts
import type { operations } from "./kernel-api";

export type KernelOpId = keyof operations;
export type KernelOpInput<K extends KernelOpId> =
  operations[K]["requestBody"]["content"]["application/json"];
export type KernelOpOutput<K extends KernelOpId> =
  operations[K]["responses"][200]["content"]["application/json"];
```

## A typed `invoke` helper

Wrap the operation endpoint once and every call is type-checked — the body type and the
return type are inferred from the `operation_id` you pass:

```ts
const BASE_URL = "https://api.geopera.com";

/** Error that preserves the RFC-7807 problem+json body. */
export class KernelOpError extends Error {
  constructor(public status: number, public problem: unknown) {
    super(`kernel op failed (${status})`);
  }
}

export async function callKernelOp<K extends KernelOpId>(
  op: K,
  body: KernelOpInput<K>,
  token: string,
  extraHeaders: Record<string, string> = {},
): Promise<KernelOpOutput<K>> {
  const res = await fetch(`${BASE_URL}/v1/op/${op}`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
      ...extraHeaders,
    },
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new KernelOpError(res.status, await res.json());
  return (await res.json()) as KernelOpOutput<K>;
}
```

## Use it

The compiler now knows the exact shape of every operation's input and output:

```ts
// Search the catalog — `body` is type-checked against catalog.search's input
const results = await callKernelOp(
  "catalog.search",
  { host_name: "earthsearch-aws", collections: ["sentinel-2-l2a"], bbox: [151, -34, 152, -33], limit: 25 },
  token,
);

// Place an order with an idempotency key — return type is the order output
const order = await callKernelOp(
  "orders.archive.place",
  { projectId, captures: [{ id: "scene-abc", geometry }] },
  token,
  { "Idempotency-Key": crypto.randomUUID() },
);
console.log(order.id, order.status, order.totalCredits);
```

If you only need the raw `Response` (e.g. to stream an NDJSON or binary operation), call
the endpoint directly and skip the JSON parse — the streaming/`raw_response` operations
are the ones to handle this way.

## Authentication

Obtain a token the same way as any other client — the OIDC token endpoint
(`client_credentials` with an API key, or `password` for a user). See
[Authentication](/api-reference/authentication). Tokens are short-lived (5 minutes);
refresh with the refresh token rather than re-authenticating.

## Errors

`callKernelOp` throws `KernelOpError` carrying the
[`application/problem+json`](/api-reference/errors) body, so you branch on
`err.status` (e.g. `402` for payment, `409` for conflicts) and read `err.problem.detail`
for the specific message.
