|
| 1 | +--- |
| 2 | +title: "IPIP-0513: Delegated Routing V1 HTTP API Returns 200 for Empty Results" |
| 3 | +date: 2025-09-11 |
| 4 | +ipip: proposal |
| 5 | +editors: |
| 6 | + - name: Marcin Rataj |
| 7 | + github: lidel |
| 8 | + url: https://lidel.org/ |
| 9 | + affiliation: |
| 10 | + name: Shipyard |
| 11 | + url: https://ipshipyard.com |
| 12 | +thanks: |
| 13 | + - name: Alex Potsides |
| 14 | + github: achingbrain |
| 15 | + affiliation: |
| 16 | + name: Shipyard |
| 17 | + url: https://ipshipyard.com |
| 18 | +relatedIssues: |
| 19 | + - https://github.com/ipfs/boxo/issues/1024 |
| 20 | + - https://github.com/ipfs/specs/pull/337 |
| 21 | +order: 513 |
| 22 | +tags: ['ipips'] |
| 23 | +--- |
| 24 | + |
| 25 | +## Summary |
| 26 | + |
| 27 | +Change the Delegated Routing V1 HTTP API server recommendation to return HTTP status code 200 (OK) with empty results instead of 404 (Not Found) when no matching records are found. This improves semantic correctness, prevents unwanted browser console errors, and provides separate guidance for servers and clients - with clients required to handle both response codes for backward compatibility, resiliency, and maximal interoperability with different server implementations. |
| 28 | + |
| 29 | +## Motivation |
| 30 | + |
| 31 | +The current Delegated Routing V1 HTTP API specification requires servers to return HTTP 404 (Not Found) when no matching records are found for a query. This creates two significant problems: |
| 32 | + |
| 33 | +1. **Browser Console Errors**: When routing queries are made from web browsers and return 404s, browsers log error messages to the console that cannot be prevented programmatically. These error messages confuse users who understandably think something is broken, when in reality an empty result is a normal and expected outcome for many queries. |
| 34 | + |
| 35 | +2. **Semantic Incorrectness**: HTTP 404 means "the requested resource does not exist." However, when querying `/routing/v1/providers/{cid}`, the endpoint itself exists and is functioning correctly - it simply found no results. Returning 404 conflates "endpoint not found" with "no data found," which are fundamentally different conditions. |
| 36 | + |
| 37 | +As noted in the original issue, querying for a peer with specific filters demonstrates this problem clearly: |
| 38 | +- A peer query without filters returns 200 with results |
| 39 | +- The same peer query with filters that match nothing returns 404 |
| 40 | +- This suggests the endpoint suddenly "doesn't exist" when it clearly does |
| 41 | + |
| 42 | +## Detailed design |
| 43 | + |
| 44 | +### Specification Changes |
| 45 | + |
| 46 | +Update the following sections in `specs/src/routing/http-routing-v1.md`: |
| 47 | + |
| 48 | +#### GET /routing/v1/providers/{cid} |
| 49 | +Current: |
| 50 | +- `404` (Not Found): must be returned if no matching records are found. |
| 51 | + |
| 52 | +New: |
| 53 | +- `200` (OK): the response body contains 0 or more records. |
| 54 | +- `404` (Not Found): SHOULD NOT be returned by a server, but if a client receives it, it MUST be interpreted as 200 with 0 results for backward compatibility, resiliency, and maximal interoperability. |
| 55 | + |
| 56 | +#### GET /routing/v1/peers/{peer-id} |
| 57 | +Current: |
| 58 | +- `404` (Not Found): must be returned if no matching records are found. |
| 59 | + |
| 60 | +New: |
| 61 | +- `200` (OK): the response body contains 0 or more peer records. |
| 62 | +- `404` (Not Found): SHOULD NOT be returned by a server, but if a client receives it, it MUST be interpreted as 200 with 0 results for backward compatibility, resiliency, and maximal interoperability. |
| 63 | + |
| 64 | +#### GET /routing/v1/ipns/{name} |
| 65 | +Current: |
| 66 | +- `404` (Not Found): must be returned if no matching records are found. |
| 67 | + |
| 68 | +New: |
| 69 | +- `200` (OK): the response body contains the IPNS Record for the given IPNS Name with `Content-Type: application/vnd.ipfs.ipns-record`. Any other content type MUST be interpreted as "no record found". |
| 70 | +- `404` (Not Found): SHOULD NOT be returned by a server, but if a client receives it, it MUST be interpreted as "no record found" for backward compatibility, resiliency, and maximal interoperability. |
| 71 | + |
| 72 | +### Response Format for Empty Results |
| 73 | + |
| 74 | +For JSON responses (`application/json`): |
| 75 | +```json |
| 76 | +{ |
| 77 | + "Providers": [] |
| 78 | +} |
| 79 | +``` |
| 80 | +or |
| 81 | +```json |
| 82 | +{ |
| 83 | + "Peers": [] |
| 84 | +} |
| 85 | +``` |
| 86 | + |
| 87 | +For NDJSON streaming responses (`application/x-ndjson`): |
| 88 | +- Return 200 with an empty stream (zero lines) |
| 89 | +- Do not return any data lines |
| 90 | + |
| 91 | +For IPNS responses: |
| 92 | +- Only `Content-Type: application/vnd.ipfs.ipns-record` indicates a valid IPNS record was found |
| 93 | +- Any other content type (e.g., `text/plain` with error message) MUST be interpreted as "no record found" |
| 94 | +- This allows servers to return 200 with different content types for error conditions while maintaining backward compatibility |
| 95 | + |
| 96 | +### Cache Control |
| 97 | + |
| 98 | +The existing cache control behavior remains unchanged: |
| 99 | +- Empty results should use shorter TTL (typically 15 seconds) |
| 100 | +- Non-empty results can use longer TTL based on the data |
| 101 | + |
| 102 | +## Design rationale |
| 103 | + |
| 104 | +### Industry Best Practices |
| 105 | + |
| 106 | +Research into REST API design patterns shows that returning 200 with empty collections is a common and recommended practice: |
| 107 | +- Major APIs (GitHub, Google, AWS) follow this pattern for collection endpoints |
| 108 | +- REST principles suggest that a successful query with no results is still a success |
| 109 | +- The collection resource exists even when empty |
| 110 | + |
| 111 | +### Semantic Correctness |
| 112 | + |
| 113 | +The HTTP specification defines status codes with specific meanings: |
| 114 | +- **200 OK**: The request succeeded, here are the results (which may be empty) |
| 115 | +- **404 Not Found**: The requested resource (endpoint) does not exist |
| 116 | +- **204 No Content**: Success with explicitly no response body |
| 117 | + |
| 118 | +For collection/search endpoints, the resource is the endpoint itself, not the data it returns. The endpoint exists and functions correctly regardless of whether it finds matching data. |
| 119 | + |
| 120 | +### User benefit |
| 121 | + |
| 122 | +1. **Cleaner Browser Console**: Web applications will no longer show error messages for normal operations, reducing user confusion and support requests. |
| 123 | + |
| 124 | +2. **Clearer Semantics**: Developers can distinguish between "endpoint doesn't exist" (real 404) and "no results found" (200 with empty data). |
| 125 | + |
| 126 | +3. **Consistent Response Structure**: Clients can use the same parsing logic whether results are empty or populated. |
| 127 | + |
| 128 | +### Compatibility |
| 129 | + |
| 130 | +#### Backward Compatibility |
| 131 | + |
| 132 | +This change is designed to be fully backward compatible by following the [robustness principle](https://specs.ipfs.tech/architecture/principles/#robustness) - "be conservative in what you send, and liberal in what you accept": |
| 133 | + |
| 134 | +1. **Servers** (conservative sending): SHOULD return 200 for empty results |
| 135 | +2. **Clients** (liberal accepting): MUST handle both: |
| 136 | + - 200 with empty result list (new behavior) |
| 137 | + - 404 (old behavior from legacy servers) |
| 138 | + |
| 139 | +This approach ensures maximum interoperability across the ecosystem while gradually transitioning to the semantically correct behavior. |
| 140 | + |
| 141 | +#### Migration Strategy |
| 142 | + |
| 143 | +1. **Phase 1**: Update server implementations to return 200 |
| 144 | + - Can be deployed immediately without breaking existing clients that handle both codes |
| 145 | + |
| 146 | +2. **Phase 2**: Update clients to have regression tests that ensure they handle both 200 and 404 |
| 147 | + - Clients must maintain 404 handling indefinitely for compatibility with servers that return 404s |
| 148 | + |
| 149 | +### Security |
| 150 | + |
| 151 | +This change has no security implications. It only affects the HTTP status code returned for empty results, not the data format, authentication, or authorization mechanisms. |
| 152 | + |
| 153 | +### Alternatives |
| 154 | + |
| 155 | +#### 204 No Content |
| 156 | + |
| 157 | +**Considered but rejected** for the following reasons: |
| 158 | + |
| 159 | +1. **Cannot include response body**: 204 explicitly means no response body, but for JSON endpoints we want to maintain consistent response structure (empty arrays/objects) |
| 160 | +2. **Incompatible with streaming**: NDJSON streams need to specify content type and may include headers even when empty |
| 161 | +3. **Inconsistent client handling**: Different code paths needed for empty vs non-empty responses |
| 162 | +4. **Limited applicability**: Could only work for IPNS binary responses, not JSON/NDJSON |
| 163 | + |
| 164 | +#### Keep 404 Status |
| 165 | + |
| 166 | +**Rejected** because: |
| 167 | + |
| 168 | +1. **Browser errors**: The primary motivation is eliminating confusing console errors |
| 169 | +2. **Semantic incorrectness**: 404 should mean "endpoint not found" not "no data" |
| 170 | +3. **Inconsistent with filters**: Same endpoint returning different status codes based on filter parameters is confusing |
| 171 | + |
| 172 | +#### API Versioning (/routing/v1.1) |
| 173 | + |
| 174 | +**Rejected** in favor of in-place upgrade because: |
| 175 | + |
| 176 | +1. **Backward compatible**: The change can be made without breaking existing clients |
| 177 | +2. **Simpler deployment**: No need to maintain multiple API versions |
| 178 | +3. **Faster adoption**: No need for clients to explicitly opt into new version |
| 179 | + |
| 180 | +## Test fixtures |
| 181 | + |
| 182 | +Implementations should test the following scenarios: |
| 183 | + |
| 184 | +### Empty Results Tests |
| 185 | + |
| 186 | +1. **Providers endpoint with no results**: |
| 187 | + ``` |
| 188 | + GET /routing/v1/providers/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi |
| 189 | + Response: 200 OK |
| 190 | + Body: {"Providers": []} |
| 191 | + ``` |
| 192 | + |
| 193 | +2. **NDJSON streaming with no results**: |
| 194 | + ``` |
| 195 | + GET /routing/v1/providers/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi |
| 196 | + Accept: application/x-ndjson |
| 197 | + Response: 200 OK |
| 198 | + Content-Type: application/x-ndjson |
| 199 | + Body: [empty - no lines] |
| 200 | + ``` |
| 201 | + |
| 202 | +3. **IPNS endpoint with no results**: |
| 203 | + ``` |
| 204 | + GET /routing/v1/ipns/k51qzi5uqu5dhlbegona8wfyei6jnjuhrulz3t8femxtfmak9134qpqncw3poc |
| 205 | + Accept: application/vnd.ipfs.ipns-record |
| 206 | + Response: 200 OK |
| 207 | + Content-Type: text/plain; charset=utf-8 |
| 208 | + Body: delegate error: routing: not found |
| 209 | + ``` |
| 210 | + Note: The `Content-Type` is NOT `application/vnd.ipfs.ipns-record`, which indicates no record was found and parsing of body can be skipped. In this example, the body contains text/plain error message for convenience. |
| 211 | + |
| 212 | +### Backward Compatibility Tests |
| 213 | + |
| 214 | +Clients MUST correctly handle: |
| 215 | +1. Old server returning 404 → treat as empty results |
| 216 | +2. New server returning 200 with empty body → treat as empty results |
| 217 | +3. New server returning 200 with results → process normally |
| 218 | + |
| 219 | +### Copyright |
| 220 | + |
| 221 | +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). |
0 commit comments