Synsemadocsv0.6.xENES

LLM

Provider API directly

The built-in ops (reason/decide/…) cover most needs and keep you vendor-neutral. But when you need a parameter the ops don't expose (streaming, vision, a specific endpoint, your own retry logic), call the provider's HTTP API yourself. It's a normal HTTP request — so it's gated by net(host) and uses a secret for auth (see Secrets).

llm-api-direct.syn
-- Doc example: calling a provider's HTTP API directly (instead of the built-in ops).
-- It's a normal HTTP request, so it is gated by net(host) and uses secret/bearer for auth.
intent: "doc example: provider API directly"
require net("api.anthropic.com")
require secret("ANTHROPIC_API_KEY")

task call_undeclared_provider()
    -- api.openai.com is NOT declared → the request is denied at the capability check
    give http_post("https://api.openai.com/v1/chat/completions", {}, {"x-api-key": secret("ANTHROPIC_API_KEY")})

test "a direct provider call is gated by net(host) like any HTTP request"
    assert_error(call_undeclared_provider)

Anthropic (Messages API) — header is x-api-key, not Bearer§

require net("api.anthropic.com")
require secret("ANTHROPIC_API_KEY")

let r be http_post("https://api.anthropic.com/v1/messages",
    json_encode({"model": "claude-sonnet-4-6", "max_tokens": 256,
                 "messages": [{"role": "user", "content": "Say hi"}]}),
    {"x-api-key": secret("ANTHROPIC_API_KEY"),        -- raw secret in a CUSTOM header
     "anthropic-version": "2023-06-01",
     "Content-Type": "application/json"})
let reply be json of r                                 -- parsed for you when the API answers JSON (engine v0.6.20+; json_decode(body of r) before)
let answer be reply["content"][0]["text"]

OpenAI / DeepSeek — Authorization: Bearer§

require net("api.openai.com")
require secret("OPENAI_API_KEY")

let r be http_post("https://api.openai.com/v1/chat/completions",
    json_encode({"model": "gpt-4o", "messages": [{"role": "user", "content": "Say hi"}]}),
    {"Authorization": bearer(secret("OPENAI_API_KEY")), "Content-Type": "application/json"})

The key is a secret: it materializes only at the socket and is redacted in logs/errors. When to use this vs. the built-in ops: the ops for portability and validated decisions; the direct API for provider-specific features. You give up vendor-neutrality and the automatic context/validation.

What the engine records (v0.6.29+). The provider's answer is an input of the program, like any HTTP response: lineage() lists it with source http_post, what = the host only (api.anthropic.com — never the path, the query or a credential) and the sha256 of the body, and receipt() publishes it — see Observability § Lineage. Two HTTP rules of the same release apply here too: an explicit port is part of the grant (require net("http://localhost:11434") covers only that port; net("localhost") covers any), and a URL with user:pass@ sends Authorization: Basic unless you pass your own Authorization — see HTTP client.