> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stellarium.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# AI on Stellarium

> Call AI models from RequestScript and host your own on the network, priced per token in USDC.

AI models are resources on Stellarium, like any other API. You call them from RequestScript and pay per token in USDC, with no account or API key for each provider. Anyone who runs a model can offer it to the network and get paid for every call.

A model is wrapped in a resource with an **inference function**: a function that takes a prompt and returns text, and is priced by the tokens it uses rather than a flat amount per call. The examples on this page use Claude Sonnet, offered as `sh.stellarium.inference.claude.Sonnet`.

> [!WARNING]
>
> Payments are under active development and have only been exercised on a local Solana validator. Don't use real funds.

## How to call a model

Calling a model looks like calling any other function. Every inference function takes a `maxTokens` argument, the most output tokens the call may produce:

```requestscript AskClaude.req theme={null}
request AskClaude {
  const sonnet: sh.stellarium.inference.claude.Sonnet

  const reply = sonnet.message(prompt: "Write a haiku about Oslo", maxTokens: 200)
  if (reply.error != null) {
    return reply.error.message
  }

  return reply.value
}
```

Send it to your node's `/v1/run` endpoint like any other request:

```bash theme={null}
curl -X POST http://127.0.0.1:3000/v1/run \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $API_KEY" \
  -d '{"source":"request AskClaude {\n  const sonnet: sh.stellarium.inference.claude.Sonnet\n  const reply = sonnet.message(prompt: \"Write a haiku about Oslo\", maxTokens: 200)\n  return reply.value\n}"}'
```

Your node handles the rest. It finds the node that hosts the model, asks it for a price estimate, checks the estimate against your spending limit, and signs a voucher for the call. The script sees only `value` or `error`; token counts stay between the nodes and the operator. A single request can mix model calls with any other resource on the network and use their results together.

### What a call costs

A call's charge, in micro-USDC, is its token counts times the model's advertised rates, rounded up:

```text theme={null}
charge = ceil((inputTokens × inputRate + outputTokens × outputRate) / 1,000,000)
```

Before the call runs, your node agrees to a ceiling: the charge for the estimated input tokens plus `maxTokens` output tokens. Afterwards you pay for the tokens actually used, never more than that ceiling. With Sonnet at \$2.50 per million input tokens and \$10.50 per million output tokens, a 1,000-token prompt with `maxTokens: 400`:

| | Input | Output | Charge |
| - | - | - | - |
| Signed ceiling (`maxAmount`) | 1,000 × 2.5 | 400 × 10.5 | 6,700 micro-USDC (\$0.0067) |
| Actual call, 250 output tokens | 1,000 × 2.5 | 250 × 10.5 | 5,125 micro-USDC (\$0.005125) |

### Raising your spending limit

Your node refuses any inference call whose ceiling is over its `inferenceCostLimitMicroUsdc`, which defaults to `costLimitMicroUsdc` (\$0.01). Larger models or longer outputs can go over it, so raise it if you need to:

```bash .env theme={null}
# The most this node will sign for in one inference call, in micro-USDC.
INFERENCE_COST_LIMIT_MICRO_USDC=50000
```

Your node also needs a funded escrow to pay for calls. See [Escrow accounts](/concepts/payments#escrow-accounts).

### Finding models

Inference functions are listed by [`GET /v1/resources`](/node/http-api) on every node, with `kind: "inference"` and their per-token payment manifest, so you can compare rates before calling anything.

## How to set up a model

If you can run a model, or call one you have access to, you can offer it on Stellarium. You write two pieces of code: the function that runs the model, and an estimator that counts a call's input tokens. Discovery, billing and settlement are handled by the network.

**1. Run a node**

Follow the [Quickstart](/quickstart) to set up a node. Hosting a paid function also needs `OPERATOR_URL` set, so the node can submit payments for settlement. See [Configuration](/node/configuration).

```bash .env theme={null}
OPERATOR_URL=<operator API URL>
CLAUDE_SONNET_PAYMENT_ADDRESS=<Solana wallet that receives payments>
```

The recipient wallet must already have a USDC token account, or payments fail to settle. See [Pricing a function](/concepts/payments#pricing-a-function).

**2. Declare an inference function**

An inference function is declared like any other function on a resource, with a few additions:

* **`kind: "inference"`** marks it as an inference function.
* **A `maxTokens: int32` parameter**, which the caller uses to cap output tokens. The function can take any other parameters too.
* **`exec` reports usage** by returning `{ value, usage: { inputTokens, outputTokens } }`.
* **A per-token `paymentManifest`**, if the function is paid, with `inputCostMicroUsdcPerMillionTokens` and `outputCostMicroUsdcPerMillionTokens` instead of `costMicroUsdc`. Either rate can be `0`.
* **An `estimateInputTokens(args)` estimator**, if the function is paid. It runs only on your node, so it can call a token-counting API such as Anthropic's.

```typescript sonnet.ts highlight={17,20,25-31,39,41-45} theme={null}
import Anthropic from "@anthropic-ai/sdk";
import type { StellariumResource } from "stellarium-ts";

const client = new Anthropic();
const arg = (args: { name: string; value: unknown }[], name: string) => args.find((a) => a.name === name)?.value;

export const sonnet: StellariumResource = {
  id: 1,
  baseUrl: process.env.BASE_URL!,
  metadata: {},
  path: "sh.stellarium.inference.claude",
  name: "Sonnet",
  description: "A collection of functions for Claude Sonnet 5.5",
  functions: [
    {
      name: "message",
      kind: "inference",
      parameters: [
        { name: "prompt", type: "string", description: "The prompt to generate text from." },
        { name: "maxTokens", type: "int32", description: "The maximum number of tokens to generate." },
      ],
      returnType: "string",
      description: "Prompt the model to generate text.",
      // Runs on your node before every paid call.
      estimateInputTokens: async (args) => {
        const { input_tokens } = await client.messages.countTokens({
          model: "claude-sonnet-5-5",
          messages: [{ role: "user", content: arg(args, "prompt") as string }],
        });
        return input_tokens;
      },
      exec: async (args) => {
        const message = await client.messages.create({
          model: "claude-sonnet-5-5",
          max_tokens: arg(args, "maxTokens") as number,
          messages: [{ role: "user", content: arg(args, "prompt") as string }],
        });
        const text = message.content.flatMap((block) => (block.type === "text" ? [block.text] : [])).join("");
        return { value: text, usage: { inputTokens: message.usage.input_tokens, outputTokens: message.usage.output_tokens } };
      },
      paymentManifest: {
        inputCostMicroUsdcPerMillionTokens: 2_500_000,   // $2.50 per million input tokens
        outputCostMicroUsdcPerMillionTokens: 10_500_000, // $10.50 per million output tokens
        recipientAddress: process.env.CLAUDE_SONNET_PAYMENT_ADDRESS!,
      },
    },
  ],
};
```

**3. Register it and start**

Register the resource before `start`, as with any other resource:

```typescript server.ts theme={null}
import Fastify from "fastify";
import { StellariumNode } from "stellarium-ts";
import { sonnet } from "./sonnet";

const node = new StellariumNode();
await node.register(sonnet);
await node.start(Fastify({ logger: true }), { port: 3000 });
```

The node checks inference functions when it starts, and `start` throws if one is missing `maxTokens`, or is paid and lacks per-token rates or an estimator. An inference function without a `paymentManifest` is free: it still needs `maxTokens`, but no estimator.

Once your node is on the network, other nodes discover the model through `GET /v1/resources` and route calls to it. Every paid call arrives with a voucher that covers it, so inference never runs unpaid. For how vouchers are verified and settled, see [Inference functions](/concepts/payments#inference-functions).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.