> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tensorlake.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Public Endpoints

> Expose an application endpoint that callers can invoke without a Tensorlake API key.

By default, applications require an [API Key](/platform/access-control#api-keys) for authorization. Public endpoints let you call applications without Tensorlake credentials.

## Enable a public endpoint

Add the `unauthenticated_requests` capability to the `@application` decorator:

```python public_api.py theme={null}
from tensorlake.applications import application, function

@application(allow=["unauthenticated_requests"])
@function()
def public_api(payload: dict) -> dict:
    return {"status": "accepted", "payload": payload}
```

The `allow` attribute accepts a list of application capabilities. At the moment, `unauthenticated_requests` is the only supported capability.

## Deploy the application

Deploy the application normally:

```bash theme={null}
tl app deploy public_api.py
```

On the first deployment, Tensorlake assigns the application a stable, opaque `public_endpoint_id`. Redeploying the application preserves this ID. The public URL has this form:

```text theme={null}
https://api.tensorlake.ai/applications/public/<public_endpoint_id>
```

After the application is deployed, you can call the public endpoint without an `Authorization` header:

```bash theme={null}
curl  https://api.tensorlake.ai/applications/public/<public_endpoint_id> \
  --json '{"event": "created"}'
```

## Read request headers

A sanitized list of HTTP request headers are available inside your application's request context when you use public endpoints. You can access them through `RequestContext.get().headers`. The `Headers` collection is immutable and supports case-insensitive lookups:

```python theme={null}
from tensorlake.applications import RequestContext

headers = RequestContext.get().headers

request_type = headers["X-Request-Type"]        # Required header
signature = headers.get("x-request-signature")  # Optional header
all_values = headers.getlist("X-Provider-Tag")  # Repeated header
```

When a header has multiple values, `get()` return the last value. `getlist()` returns all values in their received order.

Tensorlake prevents the following headers from being forwarded into application invocations:

* Credentials and browser state: `Authorization`, `Authentication`, `Cookie`, and proxy authentication headers
* Routing and transport details: `Host`, standard hop-by-hop headers, and headers named by `Connection`

## Receive a raw request body

Use the type `HttpBody` when you need to parse the request body sent into a public endpoint.

```python theme={null}
from tensorlake.applications import HttpBody, application, function

@application(allow=["unauthenticated_requests"])
@function()
def receive_raw_body(body: HttpBody) -> dict:
    return {
        "content_type": body.content_type,
        "size": len(body.content),
        "payload": body.json(),
    }
```

`HttpBody` exposes the raw bytes through `body.content`, along with `body.content_type`, `body.text()`, and `body.json()`.

## Usage example: Receiving webhooks

Webhook providers are a common use for public endpoints. The following handler receives GitHub `workflow_job` events and validates each request using GitHub's `X-Hub-Signature-256` header and the exact request body:

```python github_webhook.py theme={null}
import hmac
import os
from hashlib import sha256

from tensorlake.applications import (
    HttpBody,
    RequestContext,
    application,
    function,
)


def verify_signature(
    raw_body: bytes,
    signature: str | None,
    secret: str,
) -> bool:
    if not signature or not signature.startswith("sha256="):
        return False

    expected = "sha256=" + hmac.new(
        secret.encode("utf-8"),
        raw_body,
        sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature)


@application(allow=["unauthenticated_requests"])
@function(secrets=["GITHUB_WEBHOOK_SECRET"])
def github_webhook(body: HttpBody) -> dict[str, str]:
    headers = RequestContext.get().headers

    if headers.get("X-GitHub-Event") != "workflow_job":
        return {"status": "ignored"}

    if not verify_signature(
        body.content,
        headers.get("X-Hub-Signature-256"),
        os.environ["GITHUB_WEBHOOK_SECRET"],
    ):
        return {"status": "rejected"}

    event = body.json()
    # Process the verified event.
    return {"status": "accepted", "action": event.get("action", "")}
```

Store the same secret that you configure with GitHub, then deploy the application:

```bash theme={null}
tl secrets set GITHUB_WEBHOOK_SECRET=<your-webhook-secret>
tl app deploy github_webhook.py
```

## Disable public access

If you want to disable your application's public endpoint, remove `unauthenticated_requests` from the `allow` list and redeploy:

```python theme={null}
@application()
@function()
def github_webhook(body: HttpBody) -> dict[str, str]:
    ...
```
