Generate tools from an API description

An API description is an OpenAPI document or a GraphQL schema: a file that lists every operation an API has and what each one takes. You need:

  • Terraform set up with the Spotlight provider, using a token that has the configure scope, and the secrets scope if you keep a key. See Manage Spotlight with Terraform.
  • An OpenAPI 3.0 or 3.1 description, in JSON or YAML, or a GraphQL endpoint. Spotlight does not read Swagger 2; convert it to OpenAPI 3 first.
  • A Spotlight deployment that allows outbound calls to the host the description is read from and to the API's own host, since Spotlight reads the description and calls the API from its own servers.
  • If the API needs credentials: a key kept as a secret, as in Give stations a tool from an HTTP API, or an OAuth connection, as in Connect an integration that signs in with OAuth.

The examples use the public Swagger Petstore, an OpenAPI sample, and Linear's GraphQL API.

Name the description

For an OpenAPI description, add an openapi block to the integration's definition, with the description's url:

resource "spotlight_integration" "petstore" {
  team_id = data.spotlight_team.core.id
  definition = {
    integration = "petstore"
    name        = "Petstore"
    openapi = {
      url     = "https://petstore3.swagger.io/api/v3/openapi.json"
      baseUrl = "https://petstore3.swagger.io/api/v3"
      auth    = { secrets = ["secret.${spotlight_secret.petstore_key.name}"] }
    }
  }
}

If the description itself can be read only with the key, give baseUrl. See Reading the description for when Spotlight sends auth.

If the description is not published anywhere, write it out as document in place of url, and give baseUrl.

For a GraphQL API, add a graphql block instead, with the endpoint as url. Spotlight asks the endpoint for its schema, sending the block's auth:

resource "spotlight_integration" "linear" {
  team_id = data.spotlight_team.core.id
  definition = {
    integration = "linear"
    name        = "Linear"
    connection = {
      oauth2 = {
        authorize = "https://linear.app/oauth/authorize"
        token     = "https://api.linear.app/oauth/token"
        scopes    = ["read", "write"]
      }
    }
    graphql = {
      url  = "https://api.linear.app/graphql"
      auth = { oauth2 = {} }
    }
  }
}

If the endpoint does not answer schema questions, write the schema out as document in GraphQL's schema language, and keep url for the calls.

An integration has one openapi, graphql or mcp block. Run terraform apply.

Check what was generated

Spotlight reads the description during the apply. Once it finishes:

terraform state show spotlight_integration.petstore

tools lists every tool the integration offers, such as "find-pets-by-status" = "petstore.find-pets-by-status". described.tools counts the generated ones.

The apply's warnings name each operation Spotlight passed over, and why, such as one that takes a body other than JSON.

If described.problem is set, Spotlight could not read the description, and the apply shows the same reason as a warning on the block's url, or on its document. Fix the cause. If the fix is outside the definition, also raise revision. Then run terraform apply.

If the integration signs in with OAuth and the API answers only with credentials, the first read fails. Connect the team, and Spotlight reads the description again. This holds for an integration whose holder is requester too: the description is read with the team's connection, and a person connecting their own account does not read it again.

Choose which tools it offers

To offer only some operations, add tools.include and tools.exclude to the block. Each entry matches a tool name, with * for any run of characters:

openapi = {
  url     = "https://petstore3.swagger.io/api/v3/openapi.json"
  baseUrl = "https://petstore3.swagger.io/api/v3"
  auth    = { secrets = ["secret.${spotlight_secret.petstore_key.name}"] }
  tools = {
    include = ["*pet*"]
    exclude = ["delete-*"]
  }
}

If the apply is refused because the description gives more than 1,000 tools, name fewer in tools.include.

To change how one tool calls the API, write a tool of the same name under the definition's tools. The tool you wrote is offered in place of the generated one.

Decide which calls wait for approval

Generated tools that change data wait for a person to approve each call. To change which ones wait, or the reason a person reads, see Require approval before a tool runs.

Give a tool to a station

Name the tool through the integration's tools map, as for any tool:

station {
  key       = "implementation"
  name      = "Implementation"
  harness   = "claude-code"
  tools     = [spotlight_integration.petstore.tools["find-pets-by-status"]]
  passes_to = ["code-review"]
}

If the tool does not exist, the apply fails, not the plan. See Top level.

The agent calls the tool with the API's own input names. An input that takes a list or an object is given as JSON:

spotlight petstore find-pets-by-status --status sold
spotlight linear issue-create --input '{"teamId": "<team id>", "title": "Flaky upload test"}'

Read the description again

When the API changes behind the same address, set or raise revision, then run terraform apply:

openapi = {
  url      = "https://petstore3.swagger.io/api/v3/openapi.json"
  baseUrl  = "https://petstore3.swagger.io/api/v3"
  revision = "2"
}

Reading the description lists every change that makes Spotlight read it again.

On this page