# Publishing queries (http and websockets)

> Let consumers invoke your queries as standard HTTP endpoints

Source: https://orbitalhq.com/docs/querying/queries-as-endpoints

You might decide that instead of [submitting queries](/docs/querying/writing-queries#submitting-queries) directly to Orbital, or using one of our
[SDKs](/docs/sdks/java-and-kotlin/jvm-query-sdk) to execute a query programmatically, that you'd rather just publish the query as an HTTP(s) endpoint.

## Saved queries
Any queries committed to your taxonomy that have an `@HttpOperation` annotation will automatically be converted into a HTTP query.

For example:

```taxi MyQuery.taxi
import taxi.http.HttpOperation
import taxi.http.PathVariable

@HttpOperation(url = '/api/q/movies/{movieId}', method = 'GET')
query getMoviesAndReivews(@PathVariable("movieId") movieId: MovieId) {
  given { movieId }
  find {
    title : MovieTitle
    reviewScore : ReviewScore
  }
}
```
Any declared `query` that's in a Taxi project published to Orbital can be converted into an HTTP Operation.

### HttpOperation annotation
Queries need to have an `@HttpOperation` annotation:

| Param  | Description                                                                                     |
|--------|-------------------------------------------------------------------------------------------------|
| url    | The url the query is exposed under. It *must* start `/api/q`.  Any other query URLs are ignored |
| method | The HTTP method to respond to, one of `GET`,`POST`,`PUT`, or `DELETE`                           |

Variables can be defined in your path using `{myVar}`, which are then made available in your query declaration using a `@PathVariable` annotation.

**Note: Heads up!**
For security reasons, queries are only exposed under the `/api/q` endpoint. Any queries where url in the `@HttpOperation` annotation starts with something other than `/api/q` are ignored.

  **Routes are workspace-scoped.** Orbital inserts the workspace's coordinates between `/api/` and `/q/` when registering the route, so a query declared as `@HttpOperation(url = '/api/q/movies')` in workspace `acme/films` is actually served at `/api/acme/films/q/movies`.

  You can also write the workspace-scoped path explicitly (`/api/acme/films/q/movies`), but the coordinates must match the workspace the query is declared in — declaring another workspace's coordinates is rejected to prevent cross-workspace routing. In general, prefer the shorthand `/api/q/...` form and let the router add the workspace prefix.

### Declaring the query
Named queries are defined using a `query` syntax:

```taxi
import taxi.http.HttpOperation

namespace com.foo

@HttpOperation(url = '/api/q/movies', method = 'GET')
query GetMoviesAndReviews {
  find { Film[] }
}
```

This declares a query named `com.foo.GetMoviesAndReviews`. If the query lives in workspace `acme/films`, it is served at `http://{orbitalUrl}/api/acme/films/q/movies` — the router inserts the workspace coordinates into the declared `/api/q/...` path.

### Inputs to queries
Queries can accept inputs. 

Inputs are declared as variables, and are available to use throughout your query, for projecting,
as inputs to functions, or as values on your response object.

#### Path Variables

Path variables can be specified by using the `taxi.http.PathVariable` annotation:

```taxi
>  import taxi.http.PathVariable
   @HttpOperation(url = '/api/q/movies/{movieId}', method = 'GET')
   query getMoviesAndReviews(
      // Define variables to be passed in
>     @PathVariable("movieId") id: MovieId
   ) { 
     find { Film( MovieId == id ) }
   }
```

#### Http Headers
Http headers can be declared using the `taxi.http.HttpHeader` annotation:

```taxi
>   import taxi.http.HttpHeader
    @HttpOperation(url = "/api/q/hello", method = "GET")
    query UpdateTheCsvPeople(
>     @HttpHeader(name = "X-Request-Name") personName:String
    ) {
      find {
         message : String = "Hello " + personName
      }
    }
```

#### Request Body
For a POST query, the request body is also available, use `taxi.http.RequestBody` annotation:

```taxi
>  import taxi.http.RequestBody
   
   @HttpOperation(url = "/api/q/search", method = "POST")
   query doASearch(
>      @RequestBody searchRequest: SearchRequest
    ) {
      find { Film[](DateReleased >= searchRequest.dateReleased ) } 
   }
```

### Given clause

If you've written Taxi queries before, you're used to using the [given](https://docs.taxilang.org/language-reference/querying-with-taxiql/#providing-start-hints) clause
to provide data into the query.

For saved queries, that data is often passed as a variable, so the syntax is slightly different:

```taxi
import taxi.http.PathVariable
// Not a saved query, given clause has a value:
given { movieId: MovieId = 123 }
find { ... }

// A saved query, the movieId comes from the params:
query findMoviesAndReviews(@PathVariable(...) movieId: MovieId) {
  given { movieId } // exposes the parameter into the query as a fact.
  find { ... }
}
```

### Streaming Results with Server-Sent Events (SSE)
To receive query results as a continuous stream of Server-Sent Events, set the Accept header to `text/event-stream`. 

This method can be applied to both standard Request/Response queries and streaming queries. 

When using SSE, especially with queries returning multiple records (e.g., database queries), the time-to-first-byte is generally faster. 
This efficiency is due to records being transmitted as soon as they become available, unlike the standard approach where all records are delivered together 
at the end of the request.

Example request using `curl` (assuming the query is declared in workspace `acme/films`):

```bash
curl -X GET 'http://localhost:9022/api/acme/films/q/streamingTest' \
  -H 'Accept: text/event-stream;charset-UTF-8'
```

## Saved streams
Streaming queries can be exposed over either HTTP (using Server-Sent Events), or Websocket (or both).

Saved streams are executed in the background by Orbital. By connecting to the output stream, you can observe the results.

Note that when you observe a result feed from a streaming query,
results are published from the point the request is received.
Previous results are not published on result stream.

**Note: Heads up!**
You can expose streaming queries on <i>both</i> Server-Sent event endpoints and Websockets - simply add both the annotations.

### Publishing on WebSocket
To publish a query over a websocket, annotate the query with the `WebsocketOperation` annotation, declaring the `path`:

**Note: Heads up!**
For security reasons, websocket streams are only exposed under the `/api/s` path. Any queries where path in the `@WebsocketOperation` annotation starts with something other than `/api/s` are ignored


```taxi
import taxi.http.WebsocketOperation

@WebsocketOperation(path = '/api/s/newReleases')
query getNewReleaseAnnouncements {
  stream { NewReleaseAnnouncement } as {
    title : MovieTitle
    reviewScore : ReviewScore
  }[]
}
```

### Publishing as Server-Sent events
Streaming queries can also be published over HTTP by declaring a `@HttpOperation`,
and then requesting a `text/event-stream` response:

```taxi
import taxi.http.HttpOperation

@HttpOperation(url = '/api/q/newReleases', method = 'GET')
query getNewReleaseAnnouncements {
  stream { NewReleaseAnnouncement } as {
    title : MovieTitle
    reviewScore : ReviewScore
  }[]
}
```

Once this is published, results can be streamed by requesting a `text/event-stream` (substituting the workspace coordinates the query is declared in):

```bash
curl -X GET 'http://localhost:9022/api/acme/films/q/newReleases' \
  -H 'Accept: text/event-stream;charset-UTF-8'
```

## Authentication

When Orbital is deployed with an [authentication provider](/docs/deploying/authentication) configured, all published query endpoints (HTTP and Server-Sent Events) require authentication by default. Requests without a valid token receive a `401 Unauthorized` response.

### Allowing anonymous access

Some queries are intended to be public — for example a health check, a public catalogue listing, or anything you want callers to invoke without a token. To opt a single query out of authentication, annotate it with `@com.orbitalhq.authentication.AllowAnonymous`:

```taxi
import taxi.http.HttpOperation
import com.orbitalhq.authentication.AllowAnonymous

@HttpOperation(url = '/api/q/healthcheck', method = 'GET')
@AllowAnonymous
query Healthcheck {
  find { status: String = "ok" }
}
```

The opt-out is per-query: every other query published in the same workspace continues to require authentication. Authenticated requests against an `@AllowAnonymous` query are still permitted — the annotation only relaxes the requirement, it does not reject tokens.

**Note: Heads up!**
`@AllowAnonymous` only affects authentication (is the caller known?). [Data policies](/docs/data-policies/data-policies) and authorization rules still run, and may require a principal to evaluate. Apply `@AllowAnonymous` only to queries whose results and underlying operations are safe to expose publicly.

When no authentication provider is configured at all, every query is reachable without a token regardless of annotation — the annotation only matters once auth is enabled.

## Live Reload
Any changes made to the queries are automatically deployed.

 * When developing locally, this is as soon as you save a file.
 * In production, when using a Git repository, as soon as changes are merged, they're deployed on the next poll (typically a couple of minutes)

## OpenAPI
Orbital automatically creates an OpenAPI spec for any query endpoints it serves.

The OpenAPI specs are available at either `/api/q/meta/{nameOfQuery}/oas` or the workspace-scoped form `/api/{orgId}/{workspaceId}/q/meta/{nameOfQuery}/oas`.

For example, a query defined as `query findMoviesAndReviews(...)` in workspace `acme/films` would have an OpenAPI spec available at `/api/acme/films/q/meta/findMoviesAndReviews/oas`.
