# Http.*

> Make an outbound HTTP call to a URL you build at runtime — a webhook, a third-party API, a discovered endpoint. `Http.Get`/`Post`/`Put`/`Patch`/`Delete` return an `HttpResponse` you branch on (`StatusCode`, `Body`, `IsSuccess`); a 4xx/5xx is data, not an exception. Default-open to public hosts; internal addresses are blocked.

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

## Summary        {#summary}
**`Http.*`** is the outbound-HTTP facade for URLs you can't name at author time — a webhook target, a REST API, an
endpoint that comes from data. It mirrors the shape of a familiar HTTP client:

```osy syntax
use Osysharp.Http;

var r = Http.Get("https://api.example.com/orders/" + order.Code);
if (r.IsSuccess) {
  order.Tracking = r.Body;
}
```

Every verb returns an [HttpResponse](https://osysharp.com/reference/http/response/) (`StatusCode`, `Body`, `IsSuccess`). A non-2xx status is a **normal return**,
not an error — a `404` gives you `r.StatusCode == 404` and `r.IsSuccess == false`, so you branch on the result instead
of catching an exception.

For a **declared, fixed** endpoint with typed request/response, use a `client` block instead — `Http.*` is the
escape hatch for the dynamic case beside it.

## Signature      {#signature}
```osy syntax
use Osysharp.Http;

HttpResponse Http.Get(string url [, Map<string, string> headers])
HttpResponse Http.Delete(string url [, Map<string, string> headers])
HttpResponse Http.Post(string url, string|byte[] body, string contentType [, Map<string, string> headers])
HttpResponse Http.Put(string url, string|byte[] body, string contentType [, Map<string, string> headers])
HttpResponse Http.Patch(string url, string|byte[] body, string contentType [, Map<string, string> headers])
```

- **`url`** — an absolute `http`/`https` URL.
- **`body`** / **`contentType`** (Post/Put/Patch) — the request body and its media type (e.g. `"application/json"`).
  The body may be a **`string`** or a **`byte[]`**; the argument's type decides how it goes on the wire. Bytes go as
  bytes — pushing them through a string would UTF-8-encode them, which silently corrupts anything that is not text.
- **`headers`** (optional, any verb) — extra request headers such as `Authorization`. A `Map<string, string>`.

## Description    {#description}
`Http.*` is enabled by declaring the dependency in your app manifest:

```osy title="the manifest declaration that enables Http.*" syntax
app Shop {
  model "model/**/*.osy";
  use Osysharp.Http;
}
```

An `Http.*` call without `use Osysharp.Http;` is a compile error naming the fix — a network dependency is visible in the
manifest, not hidden in a function body.

**Default open, host-protected.** You can reach any **public** host — that's your call, the same as any dependency.
What the platform guarantees is that you **cannot** reach its own internals: a URL that resolves to a loopback,
private (`10.x`/`192.168.x`/…), or cloud-metadata address is refused — checked against the *resolved* address, so a
hostname that points at an internal IP is blocked too. Two more limits protect the host: an absolute **timeout ceiling**
and a **maximum response size**; a call that runs too long is cancelled and an over-size response is refused.

**Headers and auth.** Pass a headers map to authenticate an outbound call or set a custom content type:

```osy title="authenticating an outbound call with a headers map" syntax
var headers = new Dictionary<string, string>();
headers.Add("Authorization", "Bearer " + token);
var r = Http.Post("https://hooks.example.com/notify", payload, "application/json", headers);
```

**Bodies are text.** The request `body` and the response `Body` are strings. Build or parse JSON with the JSON surface
(paired with this facade) — `Http.*` moves the bytes; it doesn't assume a format.

**Long-running callbacks aren't held connections.** If an external system calls you back minutes or hours later, model
that as a workflow event (a webhook that raises an event), not an `Http.*` call that blocks — the timeout ceiling
exists precisely so a call can't hold a connection open indefinitely.

## Examples       {#examples}

Post a JSON webhook and record whether it was accepted:

```osy title="post a JSON webhook" test app=http-facade
// `use` is a MANIFEST declaration — it belongs in your app.osy, not in a model file.
app Shop {
  model "model/**/*.osy";
  use Osysharp.Http;
}

entity Order {
  [Required] string Code;
  bool WebhookAccepted;
  int WebhookStatus;
}

void NotifyShipped(Order order) {
  var body = "{\"order\":\"" + order.Code + "\",\"status\":\"shipped\"}";
  var r = Http.Post("https://hooks.partner.com/orders", body, "application/json");
  order.WebhookAccepted = r.IsSuccess;
  order.WebhookStatus = r.StatusCode;
}
```

Call an authenticated API and use the response body:

```osy title="send request headers, and read the response body" test app=http-facade
string LookupTracking(string carrier, string code, string token) {
  var headers = new Dictionary<string, string>();
  headers.Add("Authorization", "Bearer " + token);
  var r = Http.Get("https://api." + carrier + ".com/track/" + code, headers);
  return r.IsSuccess ? r.Body : "";
}
```

## See also       {#see-also}
- [HttpResponse](https://osysharp.com/reference/http/response/) — the `StatusCode` / `Body` / `IsSuccess` result every verb returns
- [Outbound calls in a test](https://osysharp.com/reference/testing/outbound-calls/) — a `[Test]` makes the call for real; `Http.<Verb>.Stub(url => …)` answers it instead
- [File.Url](https://osysharp.com/reference/storage/file-url/) — the other capability-gated I/O surface (`use Osysharp.Storage;`)
