# HTTP endpoint reference
solid-pod-rs is framework-agnostic: it does not ship an HTTP server.
This page describes the endpoint surface your integration should
expose, derived from the example in
[`examples/standalone.rs`](../../examples/standalone.rs) and the LDP
specification.
## Method matrix
| Pod root `/` | ✓ container | ✓ | ✗ 405 | ✓ | ✗ 405 | ✗ 405 |
| Container `/c/` | ✓ container | ✓ | ✗ 405 | ✓ | ✓ if empty | ✗ 405 |
| Resource `/c/r` | ✓ body | ✓ | ✓ | ✗ 405 | ✓ | ✓ N3 or SPARQL |
| ACL sidecar `/c/r.acl` | ✓ | ✓ | ✓ (acl:Control required) | ✗ | ✓ | ✗ |
| Meta sidecar `/c/r.meta` | ✓ | ✓ | server-managed | ✗ | ✗ | ✗ |
| `.well-known/openid-configuration` | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ |
| `.notifications` | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ |
| `.notifications/websocket` | ✗ | ✗ | ✗ | ✓ (subscribe) | ✗ | ✗ |
| `.notifications/webhook` | ✗ | ✗ | ✗ | ✓ (subscribe) | ✗ | ✗ |
| `/.well-known/solid` | partial (OIDC discovery only) | | | | | |
## Response status codes
| 200 OK | `GET` / `HEAD` success |
| 201 Created | `PUT` (new resource) or `POST` (Slug → child) |
| 204 No Content | `DELETE`, successful `PATCH` |
| 400 Bad Request | Malformed body, invalid path, base64/hex/JSON decode error |
| 401 Unauthorized | Missing or invalid NIP-98 / OIDC token (send `WWW-Authenticate`) |
| 403 Forbidden | ACL evaluator denied |
| 404 Not Found | `PodError::NotFound` |
| 405 Method Not Allowed | See matrix |
| 409 Conflict | `PodError::AlreadyExists` — usually only for idempotent-create semantics |
| 412 Precondition Failed | N3 PATCH `where` clause missed, or `If-Match` mismatch |
| 415 Unsupported Media Type | Unknown `Content-Type` for resource kind or PATCH |
| 500 Internal Server Error | I/O, backend, or `PodError::Backend` |
See [reference/error-codes.md](error-codes.md) for the `PodError` →
status mapping.
## Response headers (per method / path kind)
### All non-error responses
- `Link` — see [reference/link-headers.md](link-headers.md).
- `WAC-Allow` — derived via `wac::wac_allow_header`. Shape
`user="…", public="…"`.
- `ETag` — strong validator on resource bodies (SHA-256 hex).
- `Accept-Post` — on containers: `text/turtle, application/ld+json,
application/n-triples` (`ldp::ACCEPT_POST`).
- `Content-Type` — passed through from `ResourceMeta.content_type` for
resources; `application/ld+json` or the negotiated RDF format for
containers.
### `401`
```
WWW-Authenticate: Nostr
WWW-Authenticate: DPoP algs="ES256 RS256"
```
## Request headers the server honours
| `Authorization: Nostr <b64>` | NIP-98 authentication — `auth::nip98::verify`. |
| `Authorization: DPoP <token>` | Solid-OIDC access token (feature `oidc`). |
| `DPoP` | DPoP proof — `oidc::verify_dpop_proof`. |
| `Content-Type` | Stored verbatim on `PUT`; selects PATCH dialect (`text/n3` or `application/sparql-update`). |
| `Accept` | Drives RDF format for container GET — `ldp::negotiate_format`. |
| `Prefer` | Controls container representation — `ldp::PreferHeader::parse`. See [reference/prefer-headers.md](prefer-headers.md). |
| `Slug` | On `POST` to container: UTF-8 child name. Rejected if contains `/` or `..`. |
| `If-Match` / `If-None-Match` | P2 item — the storage layer returns canonical ETags; middleware enforces. |
## Path conventions
- Container paths end with `/`; resources do not.
- ACL sidecar for a resource `/c/r` lives at `/c/r.acl`.
- ACL sidecar for a container `/c/` lives at `/c/.acl`.
- Meta sidecar for `/c/r` lives at `/c/r.meta`.
- Pod root ACL lives at `/.acl`.
- Root path `/` is always a container.
- Paths are resolved case-sensitive.
- Backends MUST reject paths containing `..` or `\0`.
## Slug semantics on POST
```rust
pub fn resolve_slug(container: &str, slug: Option<&str>) -> String;
```
- If `slug` is `Some(s)` and `s` is non-empty, contains no `/`, and
contains no `..`: append `s` to `container`.
- Otherwise: append a fresh UUID v4.
## Discovery endpoints
### `GET /.well-known/openid-configuration`
Feature `oidc` required. Build with `oidc::discovery_for(issuer)`.
### `GET /.notifications`
Returns the subscription-discovery JSON-LD document. Build with
`notifications::discovery_document(pod_base)`.
```json
{
"@context": ["https://www.w3.org/ns/solid/notifications-context/v1"],
"id": "https://pod.example/.notifications",
"channelTypes": [
{ "id": "WebSocketChannel2023", "endpoint": ".../websocket", "features": ["as:Create","as:Update","as:Delete"] },
{ "id": "WebhookChannel2023", "endpoint": ".../webhook", "features": ["as:Create","as:Update","as:Delete"] }
]
}
```
## Admin / provisioning endpoints
These endpoints are only present in the `solid-pod-rs-server` binary and
require the `git` feature (or the standalone binary build with admin routes
compiled in). They are **not** part of the core library surface.
### `POST /_admin/provision/{pubkey}`
Creates a new pod for the given owner public key.
| Auth | PSK — `X-Pod-Admin-Key: <secret>` header. Requests without a valid key receive `403 Forbidden`. The key must match `SOLID_ADMIN_KEY` / `--admin-key`. |
| Feature gate | Compiled only when `--features git` is passed (or the default server build that includes it). |
| Path parameter | `pubkey` — hex-encoded Nostr/secp256k1 public key of the future pod owner. |
| Request body | None. |
**Response `200 OK`:**
```json
{ "podUrl": "https://pods.example.com/<pubkey>/", "ok": true }
```
**What it does:**
1. Creates the pod directory under the configured storage root.
2. Writes an owner-only `.acl` granting full control to the pubkey.
3. Runs `git init -b main` and sets `receive.denyCurrentBranch=updateInstead`
so the pod directory is a bare-ish working-tree repo that can receive
`git push` over HTTP via `/_git/{pubkey}/`.
**Error responses:**
| `400 Bad Request` | `pubkey` is not valid hex or fails secp256k1 key validation. |
| `403 Forbidden` | Missing or incorrect `X-Pod-Admin-Key`. |
| `409 Conflict` | Pod directory already exists. |
| `500 Internal Server Error` | I/O error or `git init` failure. |
**Security note:** This endpoint is intended for the CF Workers ↔ agentbox
handshake only. Bind the server to a non-public interface or protect it
with a firewall; the PSK is a defence-in-depth measure, not a public API.
## Git Control Panel endpoints
Present only when built with `--features git`.
### `OPTIONS /_git/{pubkey}/{tail}`
CORS preflight handler for the Git HTTP smart-protocol routes used by the
forum's VS Code-style Source Control panel.
| Auth | None — OPTIONS responses are unauthenticated by design. |
| Feature gate | `git` feature. |
| Path | `/_git/{pubkey}/{tail}` — matches any sub-path under a pubkey's git namespace. |
| Request body | None. |
**Response `204 No Content`** with the following headers:
```
Access-Control-Allow-Headers: Content-Type, Authorization, X-Pod-Admin-Key
Access-Control-Max-Age: 86400
```
The `Access-Control-Allow-Origin` value is determined by the
`SOLID_ALLOWED_ORIGINS` / `--allowed-origins` list. If the request
`Origin` is present in the allowlist it is echoed back verbatim;
otherwise `*` is returned when the allowlist is empty (dev default).
When the allowlist is non-empty and the origin is not on it, `*` is
**not** returned — the header is omitted so the browser blocks the
preflight.
## See also
- [reference/api.md](api.md) — the Rust API backing each endpoint.
- [reference/link-headers.md](link-headers.md)
- [reference/prefer-headers.md](prefer-headers.md)
- [reference/content-types.md](content-types.md)
- [reference/patch-semantics.md](patch-semantics.md)