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
configurescope, and thesecretsscope 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.petstoretools 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.
Related
spotlight_integration: every field of theopenapiandgraphqlblocks, and how tools are named.- Agent commands: how an agent gives a tool its inputs.
- Limits and timeouts