docs: NIP-CW and bridge-channel-window never show the POST /query request body, so the documented shape returns 400
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Summary
Neither `docs/nips/NIP-CW.md` nor `docs/bridge-channel-window.md` shows a complete `POST /query` request body. Both present the window request as a bare filter object, which is what a reader implementing from the docs will send, and the relay rejects it with a `400`. The body must be a JSON **array** of filters.
## Reproduce
Send the request body exactly as the docs show it, with a valid NIP-98 header from a relay member:
```
POST /query
Content-Type: application/json
Authorization: Nostr
{"kinds":[9],"limit":1}
```
Response:
```
400 Bad Request
{"error":"invalid filters: invalid type: map, expected a sequence at line 1 column 0"}
```
A `{"filters": [...]}` wrapper is rejected identically. The array form works:
```
[{"kinds":[9],"limit":1}]
```
```
200 OK
[ ...signed events... ]
```
## Why the docs read this way
`docs/nips/NIP-CW.md` §Request:
> A window request is a standard filter plus extension fields, submitted wherever the relay accepts filters (for Buzz: the NIP-98-authenticated HTTP bridge `POST /query`):
followed by a fenced block containing a single object.
`docs/bridge-channel-window.md` §Request:
> A standard bridge filter plus extension fields:
followed by the same single-object shape.
Both sentences are strictly true, because they describe the *filter*, not the request *body*. But neither document states that `POST /query` accepts an array, and no example anywhere in either file shows a complete body. `/query` is referenced three times across the two files and never once with a body a reader could copy.
## The implementation is unambiguous
`crates/buzz-relay/src/api/bridge.rs`:
```rust
// Two-pass parse: preserve raw JSON for custom extension fields (before_id,
// depth_limit, feed_types) that nostr::Filter silently drops.
let raw_filters: Vec = serde_json::from_slice(body)
.map_err(|e| api_error(StatusCode::BAD_REQUEST, &format!("invalid filters: {e}")))?;
```
The array is correct and consistent with NIP-01, where `REQ` carries one or more filters. The gap is only that the docs never say so.
## Suggested fix
One line in each §Request, plus one complete example. For example in `docs/nips/NIP-CW.md`:
> The request body is a JSON array of filters, as on any NIP-01 filter surface. A window request is one such filter:
>
> ```jsonc
> [
> {
> "kinds": [9],
> "#h": [""],
> "top_level": true,
> "limit": 50
> }
> ]
> ```
## Minor, same area
The error text `invalid filters: invalid type: map, expected a sequence at line 1 column 0` describes the serde failure rather than the contract. Something like `expected a JSON array of filters` would point a reader at the fix directly. Not important if the docs carry a full example.
## Context
Found while implementing a browser client against the channel-window surface. The endpoint itself behaves exactly as specified once the body shape is right; `top_level`, the composite `until` + `before_id` cursor, and the `39006` bounds overlay are not in question here. This is a documentation issue only.
Contributor guide
Assessment
This issue has not been assessed yet.