Docs

Build from OpenAPI

Turn selected operations from an OpenAPI document into a CrabTalk service, with generated inputs, HTTP requests and callable tools.

crab-openapi generates Rust inputs, HTTP requests and tools from an OpenAPI 3.0 or 3.1 JSON document. Choose the operations to expose, configure their HTTP client and declare access in crab.toml.

This example wraps GitHub’s Get a repository endpoint. Public repository reads need no API key. For an existing GitHub integration, you can also reuse a service.

1. Create the crate

With Crab and Rust installed, run:

crab new repo-tools
cd repo-tools

Keep the scaffold’s package and [lib] settings. Add crab-openapi and url alongside berm-lang in Cargo.toml, using the same Crab tag for both crates:

[dependencies]
berm-lang = { git = "https://github.com/crabtalk/crab", tag = "v0.1.1" }
crab-openapi = { git = "https://github.com/crabtalk/crab", tag = "v0.1.1" }
url = { version = "2", default-features = false }

2. Add the API contract

Save this minimal contract as openapi.json beside Cargo.toml. For another API, use its provider’s OpenAPI JSON document and select the operations you need.

{
  "openapi": "3.1.0",
  "info": { "title": "GitHub repository lookup", "version": "1" },
  "servers": [{ "url": "https://api.github.com" }],
  "paths": {
    "/repos/{owner}/{repo}": {
      "get": {
        "summary": "Read a GitHub repository",
        "parameters": [
          { "name": "owner", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "repo", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": { "200": { "description": "Repository metadata" } }
      }
    }
  }
}

The macro reads the local file during compilation; it does not download the spec. If a build script downloads a provider’s spec, pin and verify its contents and put the downloaded file in OUT_DIR. The crate reference covers that setup.

3. Generate the tool

Replace src/lib.rs with:

#![cfg_attr(target_arch = "wasm32", no_std)]
extern crate alloc;

use berm_lang::http::Client;

const CLIENT: Client<'static> = Client {
    name: "GitHub",
    headers: &[
        ("Accept", "application/vnd.github+json"),
        ("X-GitHub-Api-Version", "2026-03-10"),
    ],
};

crab_openapi::service! {
    spec: "openapi.json",
    client: crate::CLIENT,
    operations: {
        repository: "GET /repos/{owner}/{repo}",
    },
}

repository is your tool’s name; the string selects the HTTP method and path from the spec. Only listed operations become tools. The macro generates types, api and tools modules; api::repository returns the upstream JSON response.

Replace the scaffold’s crab.toml with:

buffer = 1048576

[http]
fetch = ["api.github.com"]

[tools.repository]
access = "public"
title = "Read a GitHub repository"

The HTTP host and tool access are declared here, not inferred from the spec. The larger buffer allows repository metadata beyond the default 16 KiB result limit. The deployment stays private unless you set public = true; tool access and deployment visibility are separate settings.

4. Test without calling GitHub

Replace the scaffold’s tests/tools.rs with:

use berm_lang::test;

#[test]
fn repository_returns_upstream_json() {
    let response = br#"{"full_name":"rust-lang/rust","private":false}"#;
    test::respond("GET", "https://api.github.com/repos/rust-lang/rust", 200, response);
    let output = test::call(
        repo_tools::tools::berm_tool_repository,
        br#"{"input":{"path":{"owner":"rust-lang","repo":"rust"}}}"#,
    ).unwrap();
    assert_eq!(output, response);
}
cargo nextest run

Generated tool arguments are nested under input. Path parameters go under path; query parameters, headers and JSON payloads go under query, headers and body when the operation has them. Inspect the generated tool schema for required fields.

5. Deploy and call

Run it locally:

crab --context default deploy
crab --context default call repo-tools repository '{"input":{"path":{"owner":"rust-lang","repo":"rust"}}}'

deploy builds the WebAssembly image and starts the local host if needed. The call returns GitHub’s repository JSON. HTTP failures become tool errors; pagination and retries are not automatic.

Deploy the same crate to Cloud:

crab login
crab deploy
crab call repo-tools repository '{"input":{"path":{"owner":"rust-lang","repo":"rust"}}}'

Follow the deployment approval link. The deployed tool is also available through MCP.

APIs that need credentials

For a Bearer-authenticated API, add ("Authorization", "Bearer {secret:API_KEY}") to the client’s headers and declare the credential and allowed host in crab.toml:

[credentials.provider]
title = "Provider API key"
guide = "Create an API key in your provider account."
hosts = ["api.example.com"]
secrets = [{ name = "API_KEY", label = "API key" }]

Replace the spec’s server URL, the host under [http] fetch and the credential’s host and guide with your provider’s actual values. Set local credentials with crab --context default secret fill repo-tools; on Cloud, complete the deployment’s credential setup. Secrets stay host-managed. OpenAPI security declarations do not configure the client’s authentication headers.

Boundaries

Supported requests include scalar path parameters, supported query/header encodings and JSON bodies. External references, cookie parameters, server URL templates and deprecated operations are rejected. Convert YAML specs to JSON before generation.

Responses retain upstream JSON. HTTP failures use the shared client’s error handling; provider-specific failures inside a successful response need a response hook or wrapper. Imported constraints remain in tool schemas, but generated Rust deserialization does not enforce every JSON Schema constraint.

See the crate reference for response hooks, server overrides, API-only bindings and supported parameter encodings.