a love letter to tangled (android, iOS, and a search API)
at main 453 lines 19 kB view raw
1[ 2 { 3 "page": "search", 4 "route": "/search", 5 "method": "GET", 6 "summary": "Unified search across all indexed Tangled documents.", 7 "details": "Delegates to keyword search. The mode parameter is accepted but only \"keyword\" is supported.", 8 "queryParams": [ 9 { 10 "name": "q", 11 "type": "string", 12 "required": true, 13 "description": "Search query. Supports boolean operators, phrases, and prefix matching." 14 }, 15 { 16 "name": "mode", 17 "type": "string", 18 "required": false, 19 "description": "Search mode. Only \"keyword\" is supported." 20 }, 21 { "name": "limit", "type": "int", "required": false, "description": "Results per page (1–100). Default 20." }, 22 { "name": "offset", "type": "int", "required": false, "description": "Pagination offset. Default 0." }, 23 { 24 "name": "collection", 25 "type": "string", 26 "required": false, 27 "description": "Filter by ATProto collection NSID." 28 }, 29 { 30 "name": "type", 31 "type": "string", 32 "required": false, 33 "description": "Filter by record type: repo, issue, pull, profile, string." 34 }, 35 { "name": "author", "type": "string", "required": false, "description": "Filter by author handle or DID." }, 36 { "name": "repo", "type": "string", "required": false, "description": "Filter by repository name." }, 37 { "name": "language", "type": "string", "required": false, "description": "Filter by programming language." }, 38 { "name": "state", "type": "string", "required": false, "description": "Filter by state: open, closed, merged." }, 39 { "name": "from", "type": "string", "required": false, "description": "Created after (ISO 8601)." }, 40 { "name": "to", "type": "string", "required": false, "description": "Created before (ISO 8601)." } 41 ] 42 }, 43 { 44 "page": "search", 45 "route": "/search/keyword", 46 "method": "GET", 47 "summary": "PostgreSQL full-text keyword search over indexed Tangled documents.", 48 "details": "Same parameters and response shape as GET /search. Use this route to bypass mode negotiation.", 49 "queryParams": [ 50 { 51 "name": "q", 52 "type": "string", 53 "required": true, 54 "description": "Search query. Supports boolean operators, phrases, and prefix matching." 55 }, 56 { "name": "limit", "type": "int", "required": false, "description": "Results per page (1–100). Default 20." }, 57 { "name": "offset", "type": "int", "required": false, "description": "Pagination offset. Default 0." }, 58 { 59 "name": "collection", 60 "type": "string", 61 "required": false, 62 "description": "Filter by ATProto collection NSID." 63 }, 64 { 65 "name": "type", 66 "type": "string", 67 "required": false, 68 "description": "Filter by record type: repo, issue, pull, profile, string." 69 }, 70 { "name": "author", "type": "string", "required": false, "description": "Filter by author handle or DID." }, 71 { "name": "repo", "type": "string", "required": false, "description": "Filter by repository name." }, 72 { "name": "language", "type": "string", "required": false, "description": "Filter by programming language." }, 73 { "name": "state", "type": "string", "required": false, "description": "Filter by state: open, closed, merged." }, 74 { "name": "from", "type": "string", "required": false, "description": "Created after (ISO 8601)." }, 75 { "name": "to", "type": "string", "required": false, "description": "Created before (ISO 8601)." } 76 ] 77 }, 78 { 79 "page": "documents", 80 "route": "/documents/{id}", 81 "method": "GET", 82 "summary": "Fetch a single indexed document by its stable ID.", 83 "details": "The document ID has the format did|collection|rkey. Deleted documents return 404.", 84 "pathParams": [{ "name": "id", "type": "string", "description": "Stable document ID (did|collection|rkey)." }] 85 }, 86 { 87 "page": "actors", 88 "route": "/actors/{handle}", 89 "method": "GET", 90 "summary": "Tangled actor profile with optional Bluesky social data.", 91 "details": "Resolves handle to DID, fetches sh.tangled.actor.profile from the actor's PDS. If the profile record has bluesky:true, Bluesky display name and avatar are also returned.", 92 "pathParams": [{ "name": "handle", "type": "string", "description": "Tangled handle or DID." }] 93 }, 94 { 95 "page": "actors", 96 "route": "/actors/{handle}/repos", 97 "method": "GET", 98 "summary": "List all sh.tangled.repo records for an actor.", 99 "pathParams": [{ "name": "handle", "type": "string", "description": "Tangled handle or DID." }] 100 }, 101 { 102 "page": "actors", 103 "route": "/actors/{handle}/repos/{repo}", 104 "method": "GET", 105 "summary": "Repo record with resolved knot host and AT URI.", 106 "pathParams": [ 107 { "name": "handle", "type": "string", "description": "Tangled handle or DID." }, 108 { "name": "repo", "type": "string", "description": "Repository name." } 109 ] 110 }, 111 { 112 "page": "actors", 113 "route": "/actors/{handle}/repos/{repo}/tree", 114 "method": "GET", 115 "summary": "Directory tree for a ref, proxied from the actor's knot.", 116 "pathParams": [ 117 { "name": "handle", "type": "string", "description": "Tangled handle or DID." }, 118 { "name": "repo", "type": "string", "description": "Repository name." } 119 ], 120 "queryParams": [ 121 { 122 "name": "ref", 123 "type": "string", 124 "required": false, 125 "description": "Branch, tag, or commit SHA. Defaults to default branch." 126 }, 127 { "name": "path", "type": "string", "required": false, "description": "Subdirectory path within the tree." } 128 ] 129 }, 130 { 131 "page": "actors", 132 "route": "/actors/{handle}/repos/{repo}/blob", 133 "method": "GET", 134 "summary": "File contents at a path and ref, proxied from the actor's knot.", 135 "pathParams": [ 136 { "name": "handle", "type": "string", "description": "Tangled handle or DID." }, 137 { "name": "repo", "type": "string", "description": "Repository name." } 138 ], 139 "queryParams": [ 140 { "name": "ref", "type": "string", "required": false, "description": "Branch, tag, or commit SHA." }, 141 { "name": "path", "type": "string", "required": true, "description": "File path within the repository." } 142 ] 143 }, 144 { 145 "page": "actors", 146 "route": "/actors/{handle}/repos/{repo}/log", 147 "method": "GET", 148 "summary": "Commit log for a ref, proxied from the actor's knot (raw bytes).", 149 "pathParams": [ 150 { "name": "handle", "type": "string", "description": "Tangled handle or DID." }, 151 { "name": "repo", "type": "string", "description": "Repository name." } 152 ], 153 "queryParams": [ 154 { "name": "ref", "type": "string", "required": false, "description": "Branch, tag, or commit SHA." }, 155 { 156 "name": "path", 157 "type": "string", 158 "required": false, 159 "description": "Limit log to commits touching this path." 160 }, 161 { "name": "limit", "type": "int", "required": false, "description": "Max commits to return." }, 162 { 163 "name": "cursor", 164 "type": "string", 165 "required": false, 166 "description": "Pagination cursor from a previous response." 167 } 168 ] 169 }, 170 { 171 "page": "actors", 172 "route": "/actors/{handle}/repos/{repo}/branches", 173 "method": "GET", 174 "summary": "Branch list for a repo, proxied from the actor's knot (raw bytes).", 175 "pathParams": [ 176 { "name": "handle", "type": "string", "description": "Tangled handle or DID." }, 177 { "name": "repo", "type": "string", "description": "Repository name." } 178 ], 179 "queryParams": [ 180 { "name": "limit", "type": "int", "required": false, "description": "Max branches to return." }, 181 { "name": "cursor", "type": "string", "required": false, "description": "Pagination cursor." } 182 ] 183 }, 184 { 185 "page": "actors", 186 "route": "/actors/{handle}/repos/{repo}/default-branch", 187 "method": "GET", 188 "summary": "Default branch name for a repo, proxied from the actor's knot.", 189 "pathParams": [ 190 { "name": "handle", "type": "string", "description": "Tangled handle or DID." }, 191 { "name": "repo", "type": "string", "description": "Repository name." } 192 ] 193 }, 194 { 195 "page": "actors", 196 "route": "/actors/{handle}/repos/{repo}/languages", 197 "method": "GET", 198 "summary": "Language breakdown for a repo at a ref, proxied from the actor's knot.", 199 "pathParams": [ 200 { "name": "handle", "type": "string", "description": "Tangled handle or DID." }, 201 { "name": "repo", "type": "string", "description": "Repository name." } 202 ], 203 "queryParams": [ 204 { "name": "ref", "type": "string", "required": false, "description": "Branch, tag, or commit SHA." } 205 ] 206 }, 207 { 208 "page": "actors", 209 "route": "/actors/{handle}/repos/{repo}/tags", 210 "method": "GET", 211 "summary": "Tag list for a repo, proxied from the actor's knot (raw bytes).", 212 "pathParams": [ 213 { "name": "handle", "type": "string", "description": "Tangled handle or DID." }, 214 { "name": "repo", "type": "string", "description": "Repository name." } 215 ] 216 }, 217 { 218 "page": "actors", 219 "route": "/actors/{handle}/repos/{repo}/diff", 220 "method": "GET", 221 "summary": "Diff for a ref, proxied from the actor's knot (raw bytes).", 222 "pathParams": [ 223 { "name": "handle", "type": "string", "description": "Tangled handle or DID." }, 224 { "name": "repo", "type": "string", "description": "Repository name." } 225 ], 226 "queryParams": [ 227 { "name": "ref", "type": "string", "required": false, "description": "Commit SHA or branch to diff." } 228 ] 229 }, 230 { 231 "page": "actors", 232 "route": "/actors/{handle}/repos/{repo}/compare", 233 "method": "GET", 234 "summary": "Comparison between two refs, proxied from the actor's knot (raw bytes).", 235 "pathParams": [ 236 { "name": "handle", "type": "string", "description": "Tangled handle or DID." }, 237 { "name": "repo", "type": "string", "description": "Repository name." } 238 ], 239 "queryParams": [ 240 { "name": "from", "type": "string", "required": true, "description": "Base ref (branch, tag, or SHA)." }, 241 { "name": "to", "type": "string", "required": true, "description": "Head ref (branch, tag, or SHA)." } 242 ] 243 }, 244 { 245 "page": "actors", 246 "route": "/actors/{handle}/repos/{repo}/issues", 247 "method": "GET", 248 "summary": "Issues scoped to a repo, pre-joined with state (open/closed).", 249 "pathParams": [ 250 { "name": "handle", "type": "string", "description": "Tangled handle or DID." }, 251 { "name": "repo", "type": "string", "description": "Repository name." } 252 ] 253 }, 254 { 255 "page": "actors", 256 "route": "/actors/{handle}/repos/{repo}/pulls", 257 "method": "GET", 258 "summary": "Pull requests targeting a repo, pre-joined with status (open/closed/merged).", 259 "pathParams": [ 260 { "name": "handle", "type": "string", "description": "Tangled handle or DID." }, 261 { "name": "repo", "type": "string", "description": "Repository name." } 262 ] 263 }, 264 { 265 "page": "actors", 266 "route": "/actors/{handle}/issues", 267 "method": "GET", 268 "summary": "All issues authored by an actor, pre-joined with state.", 269 "pathParams": [{ "name": "handle", "type": "string", "description": "Tangled handle or DID." }] 270 }, 271 { 272 "page": "actors", 273 "route": "/actors/{handle}/pulls", 274 "method": "GET", 275 "summary": "All pull requests authored by an actor, pre-joined with status.", 276 "pathParams": [{ "name": "handle", "type": "string", "description": "Tangled handle or DID." }] 277 }, 278 { 279 "page": "actors", 280 "route": "/actors/{handle}/following", 281 "method": "GET", 282 "summary": "sh.tangled.graph.follow records for an actor.", 283 "pathParams": [{ "name": "handle", "type": "string", "description": "Tangled handle or DID." }] 284 }, 285 { 286 "page": "actors", 287 "route": "/actors/{handle}/strings", 288 "method": "GET", 289 "summary": "sh.tangled.string records posted by an actor.", 290 "pathParams": [{ "name": "handle", "type": "string", "description": "Tangled handle or DID." }] 291 }, 292 { 293 "page": "issues", 294 "route": "/issues/{handle}/{rkey}", 295 "method": "GET", 296 "summary": "Single issue with pre-joined state (open/closed).", 297 "details": "State is derived from sh.tangled.repo.issue.state records. If no state record exists the issue is considered open.", 298 "pathParams": [ 299 { "name": "handle", "type": "string", "description": "Author's Tangled handle or DID." }, 300 { "name": "rkey", "type": "string", "description": "Record key of the issue." } 301 ] 302 }, 303 { 304 "page": "issues", 305 "route": "/issues/{handle}/{rkey}/comments", 306 "method": "GET", 307 "summary": "All comments for a specific issue.", 308 "details": "Filters sh.tangled.repo.issue.comment records from the actor's PDS by matching the issue AT URI.", 309 "pathParams": [ 310 { "name": "handle", "type": "string", "description": "Author's Tangled handle or DID." }, 311 { "name": "rkey", "type": "string", "description": "Record key of the parent issue." } 312 ] 313 }, 314 { 315 "page": "pulls", 316 "route": "/pulls/{handle}/{rkey}", 317 "method": "GET", 318 "summary": "Single pull request with pre-joined status (open/closed/merged).", 319 "details": "Status is derived from sh.tangled.repo.pull.status records. If no status record exists the PR is considered open.", 320 "pathParams": [ 321 { "name": "handle", "type": "string", "description": "Author's Tangled handle or DID." }, 322 { "name": "rkey", "type": "string", "description": "Record key of the pull request." } 323 ] 324 }, 325 { 326 "page": "pulls", 327 "route": "/pulls/{handle}/{rkey}/comments", 328 "method": "GET", 329 "summary": "All comments for a specific pull request.", 330 "details": "Filters sh.tangled.repo.pull.comment records from the actor's PDS by matching the pull AT URI.", 331 "pathParams": [ 332 { "name": "handle", "type": "string", "description": "Author's Tangled handle or DID." }, 333 { "name": "rkey", "type": "string", "description": "Record key of the parent pull request." } 334 ] 335 }, 336 { 337 "page": "identity", 338 "route": "/identity/resolve", 339 "method": "GET", 340 "summary": "Resolve a handle to a DID via bsky.social.", 341 "details": "Proxies com.atproto.identity.resolveHandle. The response shape mirrors the ATProto lexicon.", 342 "queryParams": [ 343 { "name": "handle", "type": "string", "required": true, "description": "Tangled or ATProto handle to resolve." } 344 ] 345 }, 346 { 347 "page": "identity", 348 "route": "/identity/did/{did}", 349 "method": "GET", 350 "summary": "Fetch a DID document for did:plc or did:web identifiers.", 351 "details": "did:plc resolves via plc.directory. did:web resolves via https://{host}/.well-known/did.json. Other DID methods return 400.", 352 "pathParams": [{ "name": "did", "type": "string", "description": "A did:plc:… or did:web:… identifier." }] 353 }, 354 { 355 "page": "activity", 356 "route": "/activity", 357 "method": "GET", 358 "summary": "Paginated list of recent ATProto events from the Jetstream cache.", 359 "details": "Events are cached as the Jetstream firehose is consumed. Only Tangled-relevant collections are retained by default.", 360 "queryParams": [ 361 { "name": "limit", "type": "int", "required": false, "description": "Results per page (1–200). Default 50." }, 362 { "name": "offset", "type": "int", "required": false, "description": "Pagination offset. Default 0." }, 363 { 364 "name": "collection", 365 "type": "string", 366 "required": false, 367 "description": "Filter by ATProto collection NSID." 368 }, 369 { 370 "name": "operation", 371 "type": "string", 372 "required": false, 373 "description": "Filter by operation: create, update, delete." 374 }, 375 { "name": "did", "type": "string", "required": false, "description": "Filter by actor DID." } 376 ] 377 }, 378 { 379 "page": "activity", 380 "route": "/activity/stream", 381 "method": "GET", 382 "summary": "WebSocket proxy to the Bluesky Jetstream firehose.", 383 "details": "Accepts a WebSocket upgrade and forwards all query parameters to jetstream2.us-east.bsky.network. Standard Jetstream params: wantedCollections, wantedDids, cursor." 384 }, 385 { 386 "page": "profiles", 387 "route": "/profiles/{did}/summary", 388 "method": "GET", 389 "summary": "Social summary for an actor: follower count via Constellation.", 390 "details": "Best-effort — if Constellation is unavailable follower_count is 0 rather than an error.", 391 "pathParams": [{ "name": "did", "type": "string", "description": "ATProto DID, e.g. did:plc:abc." }] 392 }, 393 { 394 "page": "profiles", 395 "route": "/backlinks/count", 396 "method": "GET", 397 "summary": "Constellation backlinks count for any subject/source pair.", 398 "details": "Used by the mobile client for star counts (source: sh.tangled.graph.star) and follower counts (source: sh.tangled.graph.follow).", 399 "queryParams": [ 400 { "name": "subject", "type": "string", "required": true, "description": "AT URI or DID being linked to." }, 401 { 402 "name": "source", 403 "type": "string", 404 "required": true, 405 "description": "Collection NSID of the backlink records." 406 } 407 ] 408 }, 409 { 410 "page": "proxy", 411 "route": "/xrpc/knot/{knotHost}/{nsid}", 412 "method": "GET", 413 "summary": "Proxy a GET request to a Tangled knot's XRPC endpoint.", 414 "details": "Forwards to https://{knotHost}/xrpc/{nsid} with all query parameters. knotHost must not contain slashes or whitespace.", 415 "pathParams": [ 416 { "name": "knotHost", "type": "string", "description": "Knot hostname (no scheme), e.g. knot.example." }, 417 { "name": "nsid", "type": "string", "description": "XRPC NSID to call on the knot." } 418 ] 419 }, 420 { 421 "page": "proxy", 422 "route": "/xrpc/pds/{pds}/{nsid}", 423 "method": "GET", 424 "summary": "Proxy a GET request to an ATProto PDS XRPC endpoint.", 425 "details": "Forwards to https://{pds}/xrpc/{nsid} with all query parameters. pds must not contain slashes or whitespace.", 426 "pathParams": [ 427 { "name": "pds", "type": "string", "description": "PDS hostname (no scheme), e.g. bsky.social." }, 428 { "name": "nsid", "type": "string", "description": "XRPC NSID to call on the PDS." } 429 ] 430 }, 431 { 432 "page": "proxy", 433 "route": "/xrpc/bsky/{nsid}", 434 "method": "GET", 435 "summary": "Proxy a GET request to the Bluesky public API.", 436 "details": "Forwards to https://public.api.bsky.app/xrpc/{nsid}. No authentication is forwarded; only public endpoints are accessible.", 437 "pathParams": [{ "name": "nsid", "type": "string", "description": "XRPC NSID to call on the Bluesky public API." }] 438 }, 439 { 440 "page": "health", 441 "route": "/healthz", 442 "method": "GET", 443 "summary": "Liveness probe. Returns 200 if the process is responsive.", 444 "details": "No dependency checks. Safe to call at any frequency." 445 }, 446 { 447 "page": "health", 448 "route": "/readyz", 449 "method": "GET", 450 "summary": "Readiness probe. Checks database reachability.", 451 "details": "Checks database reachability." 452 } 453]