Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / HTTP

a typed HTTP client (client)

client <Name> { BaseUrl = "…"; [Get("/path")] <Result> Op([Query] string q); }

A `client` block declares a typed wrapper around an external HTTP API: name the BaseUrl once, then declare each operation tagged with its verb — [Get], [Post], [Put], [Patch], [Delete] — and a path. Path parameters bind by name; [Query] and [Header] bind a parameter to the query string or a header; [ResponsePath] unwraps a nested field from the JSON response. You call the operation; the platform makes the request.

stable2 examples compiled by CIhttpclientapiintegration

Summary#

A client block is a typed wrapper around an external HTTP API. You declare the base URL once and then, for each endpoint, an operation tagged with its HTTP verb and path. Calling the operation makes the request and returns the typed result — you never build a URL or parse a response by hand. It is the declarative counterpart to the imperative Http.* facade: reach for a client when you call the same API repeatedly.

Signature#

client <Name> {
  BaseUrl = "<https://…>";                 // required: the API's root
  // optional: Timeout, Auth, Retry, Headers …

  [Get("/path/{param}")]                   // the verb + path; {param} binds to a same-named argument
  <Result> <Op>(<params>);

  [Post("/path"), ResponsePath("data")]    // ResponsePath unwraps a nested JSON field
  <Result> <Op>(<Body> body, [Query] string q, [Header] string h);
}

Description#

Naming the verb and path — [Get("/users/{id}")]#

Each operation carries exactly one verb attribute naming its method and path: [Get], [Post], [Put], [Patch], or [Delete] — e.g. [Get("/users/{id}")]. A {name} segment in the path is a path parameter: it binds to the operation argument of the same name, so [Get("/track/{code}")] fills {code} from the code argument.

Where does each argument go — query, header, body?#

An argument that is not a path parameter is bound by an attribute on it:

  • [Query] binds the argument to a query-string value: [Query] string pageToken becomes ?pageToken=….
  • [Header] binds it to a request header.
  • An un-attributed argument on a [Post]/[Put]/[Patch] is the request body — serialized as JSON.

Unwrapping the response with [ResponsePath]#

Many APIs wrap the payload you want in an envelope — { "data": { … } } or { "messages": [ … ] }. [ResponsePath] names the field to unwrap, so the operation returns just that part already typed: [Get("/messages"), ResponsePath("messages")] Message[] List(); hands you the array, not the envelope.

Timeout, auth, retry and headers for the whole block#

Beyond BaseUrl, a client may set a Timeout, an Auth (e.g. an API key drawn from a Secret), a Retry policy, and default Headers sent on every request. These are declared once at the top of the block and apply to every operation.

You just call an operation — there is no async and no await (async / await — why Osy# has neither). Like any effect, the platform makes the request in place and resumes your function with the result; the suspension is the engine's business, not something the signature or the call site has to spell.

Generating a client from an OpenAPI spec#

You rarely hand-write a client for a large API — generate it. osy import-api <spec> reads an OpenAPI/Swagger document (a local file, a URL, or a GitHub blob URL) and writes an .osy file containing the whole client block: one operation per endpoint — already tagged with its verb, path, and [Query]/[Header]/[ResponsePath] bindings — plus the request/response class types and any enums, and an Auth block wired to a Secret. Import just the operations you need with --tag, --filter, or --select; name the auth secret with --secret and its method with --auth (bearer, apiKey-header, apiKey-query). The output is ordinary source — review it, trim it, and commit it like any other client.

osy import-api ./resend-openapi.yaml --tag Emails --auth bearer --secret ResendApiKey -o resend.osy
# → resend.osy: `client Resend { BaseUrl = "…"; Auth = new BearerAuth { Secret = Secret.ResendApiKey }; … }`
#   plus the request/response classes. It reminds you to declare the secret once in your app:
#     app.Secrets = [ new Secret("ResendApiKey") ];   // value injected out-of-band, never committed

Use --list to see the available operations and tags before importing, and --json to preview the source it would generate without writing a file.

Examples#

class TrackResult {
  string Status;
  string Location;
}

client Tracking {
  BaseUrl = "https://api.tracking.example";

  // GET /track/{code}?carrier=…  with an API key header; unwrap the "data" envelope.
  [Get("/track/{code}"), ResponsePath("data")]
  TrackResult Track(string code, [Query] string carrier, [Header] string apiKey);
}
class ShipmentRequest {
  string OrderCode;
  string Address;
}

class ShipmentResult {
  string TrackingNumber;
}

client Shipping {
  BaseUrl = "https://api.ship.example";

  // The un-attributed `body` argument is the JSON request body.
  [Post("/shipments"), ResponsePath("shipment")]
  ShipmentResult CreateShipment(ShipmentRequest body);
}

See also#

Related

Http.*

Make an outbound HTTP call to a URL you build at runtime — a webhook, a third-party API, a discovered endpoint…

declaring secrets (app.Secrets)

`app.Secrets` declares the named secrets your app uses — API keys, tokens, client secrets. Each is `new…

publishing a REST API (app.Apis)

`app.Apis` publishes your app to a third party over HTTP. You never write a controller or an endpoint handler — you…

async / await — why Osy# has neither

Osy# has no async and no Task. A function that calls out to the world is written like any other function — the engine…