# a typed HTTP client (client)

> 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.

<!-- id: http-client · area: http · stability: stable · html: https://osysharp.com/reference/http/client/ -->

## Summary        {#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.*`](https://osysharp.com/reference/http/facade/) facade: reach for a `client` when you call the *same* API repeatedly.

## Signature      {#signature}
```osy syntax
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    {#description}

### Naming the verb and path — `[Get("/users/{id}")]`   {#verbs}
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?   {#params}
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]`   {#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   {#settings}
Beyond `BaseUrl`, a `client` may set a `Timeout`, an `Auth` (e.g. an API key drawn from a [`Secret`](https://osysharp.com/reference/config/secrets/)),
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](https://osysharp.com/reference/function/async-await/)). 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   {#generate}
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 `enum`s, and an `Auth` block wired to a [`Secret`](https://osysharp.com/reference/config/secrets/). 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`.

```bash title="generate a typed email client from a spec, just the email operations"
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       {#examples}
```osy title="a typed client for an external tracking API" test app=http-client
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);
}
```

```osy title="a POST whose body is serialized as JSON" test app=http-client
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       {#see-also}
- [Http.*](https://osysharp.com/reference/http/facade/) — the imperative `Http.*` facade, for a one-off call rather than a reused API
- [declaring secrets (app.Secrets)](https://osysharp.com/reference/config/secrets/) — where a client's API key comes from (`Secret.Name`)
- [publishing a REST API (app.Apis)](https://osysharp.com/reference/api/rest/) — the other direction: publishing *your* app as a REST API
- [async / await — why Osy# has neither](https://osysharp.com/reference/function/async-await/) — why there is no `async`; you just call the operation
