block / block/buzz

docs: NIP-CW and bridge-channel-window never show the POST /query request body, so the documented shape returns 400

Open
#6,477 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.