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

Reference / API

publishing a REST API (app.Apis)

app.Apis = [ new RestApi("Name") { Route = "…", Expose = [ new Crud<Entity>() { … } ], Endpoints = [ new Endpoint(Fn) { … } ] } ];

`app.Apis` publishes your app to a third party over HTTP. You never write a controller or an endpoint handler — you EXPOSE what already exists: `Expose` turns an entity into CRUD routes (`new Crud<Order>() { Operations = [...] }`), and `Endpoints` maps one of your functions to a route (`new Endpoint(SubmitOrder) { Method = HttpMethod.Post, Path = "/submit" }`). Each `RestApi` has a `Route` prefix and an optional `Auth` (API key and/or bearer). The entity in a `Crud<T>` and the function in an `Endpoint(...)` must exist — a name that doesn't resolve is a compile error that names what does.

stable2 examples compiled by CIapiresthttpconfig

Summary#

app.Apis publishes your application over HTTP for other systems to call. The guiding idea is that you expose what you already have rather than write a web layer: you do not author controllers, routes, DTOs or handlers. Two things can be published — an entity, as a set of CRUD routes, and a function, mapped to a single route. Each API has a Route prefix and an optional Auth gate.

app.Apis = [
  new RestApi("Orders") {
    Route   = "orders",
    Auth    = new ApiAuth { ApiKey = true },
    Expose  = [ new Crud<Order>() { Operations = [CrudOp.Read, CrudOp.Create] } ],
    Endpoints = [ new Endpoint(SubmitOrder) { Method = HttpMethod.Post, Path = "/submit" } ],
  },
];

Signature#

app.Apis = [
  new RestApi("Name") {          // one entry per published API
    Route   = "orders",          // the URL prefix for every route below
    Version = "1.0",             // optional — omit and it defaults to v1
    Auth    = new ApiAuth { ApiKey = true, Bearer = true },   // optional gate
    Expose  = [                  // entities → CRUD routes
      new Crud<Order>() { Operations = [CrudOp.Read, CrudOp.Create, CrudOp.Update, CrudOp.Delete] },
    ],
    Endpoints = [                // functions → one route each
      new Endpoint(SubmitOrder) { Method = HttpMethod.Post, Path = "/submit", SuccessStatus = 201 },
    ],
  },
];

app.Apis is a list — an app may publish several independent APIs, each with its own Route and Auth.

Description#

A RestApi has:

  • Route — the URL prefix under which its routes live.
  • Version, Deprecated, Sunset (optional) — API lifecycle metadata. Every route lives under a /v{major}/ prefix; an omitted Version defaults to v1, so the simple case needs no version at all. A multi-major API gives each RestApi its own Version (e.g. "1.0", "2.0").
  • Auth (optional) — an ApiAuth { ApiKey = true, Bearer = true }. Absent, the API is unauthenticated.
  • Expose — a list of new Crud<Entity>() { Operations = [CrudOp.…] }. Each entry turns one entity into REST routes; Operations chooses which of Read / Create / Update / Delete exist. The entity must be defined in your app — a Crud<Unknown> is a compile error that lists the entities you do define.
  • Endpoints — a list of new Endpoint(Function) { Method = HttpMethod.…, Path = "…" }. Each maps one of your functions to a route. The function must be declared — an Endpoint(NoSuchFn) is a compile error that lists your functions.

You write no request parsing, no serialization, and no routing table: the shape of the entity and the signature of the function are the contract.

Refusals map to the right status. When an endpoint's function refuses — it throws a typed exception, or a declared security policy denies the caller — the response carries the matching 4xx, not a blanket 500, and the client sees the refusal's own message:

The function…Response
throw new ValidationException("…")400 — the request was invalid
throw new NotFoundException("…")404 — the target does not exist / is not visible
throw new ConflictException("…")409 — conflicts with current state
is denied by a security / authorization policy403 — the caller may not do this
hits a genuine, unexpected error500 — a generic message; the details stay in the server log, never the response

So a business rule like "a backup can only be downloaded once it is ready" is a throw new ValidationException(…) that reaches the caller as a 400 with your wording — not a server crash.

Examples#

A complete app that publishes one entity as read/create CRUD and one function as a POST route:

entity Order {
  [Required, MaxLength(200)] string CustomerEmail;
  decimal Total;
}

decimal SubmitOrder(decimal total, decimal taxRate) {
  return total + total * taxRate;
}

app.Apis = [
  new RestApi("Orders") {
    Route  = "orders",
    Expose = [ new Crud<Order>() { Operations = [CrudOp.Read, CrudOp.Create] } ],
    Endpoints = [
      new Endpoint(SubmitOrder) { Method = HttpMethod.Post, Path = "/submit", SuccessStatus = 201 },
    ],
  },
];

Require an API key on every route — declare the secret, then gate the API with Auth:

entity Product { [Required, MaxLength(200)] string Name; decimal Price; }

app.Secrets = [ new Secret("OrdersApiKey") ];
app.Apis = [
  new RestApi("Catalog") {
    Route  = "catalog",
    Auth   = new ApiAuth { ApiKey = true },
    Expose = [ new Crud<Product>() { Operations = [CrudOp.Read] } ],
  },
];

See also#

Related

declaring secrets (app.Secrets)

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

[AuthMethod] — a function an unauthenticated visitor may call

`[AuthMethod]` marks a sign-in function — login, signup, password-reset — as reachable by a visitor who is not signed…

function

A function is a top-level unit of work, written like a C# method — a return type, a name, typed parameters, a body. It…