# HttpResponse

> The result of an `Http.*` call. Carries the HTTP status code, the body as text AND as bytes, every response header, and a convenience `IsSuccess` flag for 2xx. A non-2xx status is a normal value here, not an exception — you branch on it.

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

## Summary        {#summary}
**`HttpResponse`** is what every `Http.*` verb ([Http.*](https://osysharp.com/reference/http/facade/)) returns. It has three fields:

| Field | Type | Meaning |
|---|---|---|
| `StatusCode` | `int` | The HTTP status code — `200`, `404`, `500`, … |
| `Body` | `string` | The response body decoded as UTF-8 text |
| `Bytes` | `byte[]` | The body **as it arrived** — the honest answer for anything that is not text |
| `Headers` | `List<HttpHeader>` | Every response header, each with a `Name` and a `Value` |
| `IsSuccess` | `bool` | `true` when the status is in the 2xx range |

A non-2xx response is a **normal return**, so you inspect it rather than catch an error:

```osy syntax
use Osysharp.Http;

var r = Http.Get(url);
if (r.IsSuccess) {
  Process(r.Body);
} else {
  Log("fetch failed with " + r.StatusCode);
}
```

## Signature      {#signature}
```osy syntax
class HttpResponse {
  int StatusCode;
  string Body;                   // decoded as UTF-8
  byte[] Bytes;                  // as it arrived
  List<HttpHeader> Headers;      // each has .Name and .Value
  bool IsSuccess;
}
```

The type enters scope with the same dependency that enables the facade — `use Osysharp.Http;`. You rarely name it
explicitly: `var r = Http.Get(url);` infers it.

## Description    {#description}
`Body` is the response **decoded as UTF-8**. That is right for JSON and wrong for everything else: an image, a PDF,
an object out of a blob store is not text, and decoding it produces mojibake rather than an error. **Read `Bytes`
for anything that is not text** — it is the body exactly as it arrived, and `.Length` tells you how much of it there
is.

### Reading a header      {#headers}
`Headers` is a **list**, not a map, because HTTP headers repeat — `Set-Cookie` is the everyday case, and a map would
silently keep one of them. Each entry has a `Name` and a `Value`:

```osy syntax
var etag = "";
foreach (var h in r.Headers) {
  if (h.Name == "ETag") { etag = h.Value; }
}
```

`ETag` is what verifies a store's own write; `Content-Type` and `Content-Length` are the other two anything binary
usually wants.

`IsSuccess` is exactly `200 ≤ StatusCode < 300`. It's a convenience for the common "did it work?" branch; when you
care about a specific code (a `429` to back off, a `404` to treat as absent), read `StatusCode` directly.

## Examples       {#examples}

Distinguish "not found" from a real failure:

```osy title="a 404 is data, not a failure" test app=http-response
app Shop {
  model "model/**/*.osy";
  use Osysharp.Http;       // the manifest dependency that makes `Http.*` available
}

string FetchOrEmpty(string url) {
  var r = Http.Get(url);
  if (r.StatusCode == 404) return "";        // absent — expected
  if (!r.IsSuccess) return "";                // some other failure
  return r.Body;
}
```

## See also       {#see-also}
- [Http.*](https://osysharp.com/reference/http/facade/) — the `Http.Get`/`Post`/`Put`/`Delete` verbs that return this type
