Docs · Post
Post
A post with its replies (three levels deep) and the posts it replies to.
https://api.lurkapi.com /v1/bluesky/postWhen to use this
Use it to read a conversation: the post, its replies nested under replies, and parents from the thread's first post down to the one this post answers. Replies go three levels deep; pass a reply's uri as url to read further down.
Some posts are left out. Deleted posts, posts from accounts that block viewers, and posts by accounts hidden from logged-out visitors don't appear in replies (a hidden reply takes its branch with it). In parents, the chain stops below such a post. Quotes of hidden posts are null.
If the post you asked for is hidden from logged-out visitors, you get 403 not_public. Unknown and deleted posts return 404 not_found.
Example request
Copy one and replace YOUR_API_KEY with your API key (get one free). The code versions read the key from the LURKAPI_API_KEY environment variable instead.
curl "https://api.lurkapi.com/v1/bluesky/post?url=https%3A%2F%2Fbsky.app%2Fprofile%2Fbsky.app%2Fpost%2F3mtykea5kds2d" \
-H "x-api-key: YOUR_API_KEY"Paste it into a terminal, with your key in place of YOUR_API_KEY.
const params = new URLSearchParams({
url: "https://bsky.app/profile/bsky.app/post/3mtykea5kds2d",
});
const res = await fetch(`https://api.lurkapi.com/v1/bluesky/post?${params}`, {
headers: { "x-api-key": process.env.LURKAPI_API_KEY },
});
const data = await res.json();
if (!data.success) throw new Error(`${data.code}: ${data.error}`);
console.log(data.replies);Node 18+. Save it as lurk.mjs and run node lurk.mjs. It reads your key from LURKAPI_API_KEY.
import os
import requests
res = requests.get(
"https://api.lurkapi.com/v1/bluesky/post",
params={
"url": "https://bsky.app/profile/bsky.app/post/3mtykea5kds2d",
},
headers={"x-api-key": os.environ["LURKAPI_API_KEY"]},
timeout=60,
)
data = res.json()
if not data["success"]:
raise RuntimeError(f"{data['code']}: {data['error']}")
print(data["replies"])Run pip install requests, save it as lurk.py and run python lurk.py. It reads your key from LURKAPI_API_KEY.
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"time"
)
func main() {
params := url.Values{}
params.Set("url", "https://bsky.app/profile/bsky.app/post/3mtykea5kds2d")
req, _ := http.NewRequest("GET", "https://api.lurkapi.com/v1/bluesky/post?"+params.Encode(), nil)
req.Header.Set("x-api-key", os.Getenv("LURKAPI_API_KEY"))
client := &http.Client{Timeout: 60 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var data map[string]any
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
panic(err)
}
if data["success"] != true {
panic(fmt.Sprintf("%v: %v", data["code"], data["error"]))
}
out, _ := json.MarshalIndent(data["replies"], "", " ")
fmt.Println(string(out))
}Save it as main.go and run go run main.go. It reads your key from LURKAPI_API_KEY.
<?php
$params = http_build_query([
'url' => 'https://bsky.app/profile/bsky.app/post/3mtykea5kds2d',
]);
$context = stream_context_create(['http' => [
'header' => 'x-api-key: ' . getenv('LURKAPI_API_KEY'),
'timeout' => 60,
'ignore_errors' => true,
]]);
$body = file_get_contents('https://api.lurkapi.com/v1/bluesky/post?' . $params, false, $context);
$data = json_decode($body, true);
if (!$data['success']) {
throw new Exception("{$data['code']}: {$data['error']}");
}
print_r($data['replies']);Save it as lurk.php and run php lurk.php. It reads your key from LURKAPI_API_KEY.
require 'json'
require 'net/http'
uri = URI('https://api.lurkapi.com/v1/bluesky/post')
uri.query = URI.encode_www_form(
'url' => 'https://bsky.app/profile/bsky.app/post/3mtykea5kds2d',
)
req = Net::HTTP::Get.new(uri)
req['x-api-key'] = ENV.fetch('LURKAPI_API_KEY')
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https', read_timeout: 60) do |http|
http.request(req)
end
data = JSON.parse(res.body)
raise "#{data['code']}: #{data['error']}" unless data['success']
puts JSON.pretty_generate(data['replies'])Save it as lurk.rb and run ruby lurk.rb. It reads your key from LURKAPI_API_KEY.
Parameters
Add them to the URL after a ?. Join several with &.
| Parameter | Description |
|---|---|
urlstringrequired | The post's URL, e.g. https://bsky.app/profile/bsky.app/post/3l6oveex3ii2l, or its at:// URI.
|
Example response
A real response, captured from the live API. Lists are trimmed to a couple of items, and long text such as media links is cut short with ….
Show the example response (13 KB)Hide
{
"success": true,
"credits_remaining": 999985,
"credits_charged": 1,
"post": {
"uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtykea5kds2d",
"cid": "bafyreiekdsq4kh7go75zsdw7echsceb6zp2zczozoqoz3kcmtezievbdtu",
"url": "https://bsky.app/profile/bsky.app/post/3mtykea5kds2d",
"text": "NOW PLAYING: \"Flight over Jupiter\" by @kevinmgill.bsky.social",
"createdAt": "2026-08-26T14:31:49.108Z",
"author": {
"did": "did:plc:z72i7hdynmk6r22z27h6tvur",
"handle": "bsky.app",
"displayName": "Bluesky",
"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:z72i7hdynmk6r22z27h6tvur/b…"
},
"replyCount": 10,
"repostCount": 32,
"likeCount": 494,
"quoteCount": 0,
"bookmarkCount": 66,
"langs": [
"en"
],
"images": [],
"video": null,
"external": null,
"quote": {
"uri": "at://did:plc:we2butxgfrqjxqxztwznhrrv/app.bsky.feed.post/3mtw2mowtdc2q",
"url": "https://bsky.app/profile/kevinmgill.bsky.social/post/3mtw2mowtdc2q",
"text": "Take a relaxing flight with NASA's Juno Spacecraft as it passes over Jup…",
"createdAt": "2026-08-25T14:44:53.700Z",
"author": {
"did": "did:plc:we2butxgfrqjxqxztwznhrrv",
"handle": "kevinmgill.bsky.social",
"displayName": "Kevin M. Gill",
"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:we2butxgfrqjxqxztwznhrrv/b…"
}
},
"links": [],
"mentions": [
"kevinmgill.bsky.social"
],
"tags": [],
"replyTo": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtwm5grjns22"
},
"replies": [
{
"uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtyzbvfbb222",
"cid": "bafyreighwp7yizyb62fchduf2wta5xc5bubbjkn4552rvpyi4j4apunxsq",
"url": "https://bsky.app/profile/bsky.app/post/3mtyzbvfbb222",
"text": "NOW PLAYING: \"Tour of Western Railway BART Exhibit\" by @bart.gov",
"createdAt": "2026-08-26T18:58:56.845Z",
"author": {
"did": "did:plc:z72i7hdynmk6r22z27h6tvur",
"handle": "bsky.app",
"displayName": "Bluesky",
"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:z72i7hdynmk6r22z27h6tvur/b…"
},
"replyCount": 9,
"repostCount": 18,
"likeCount": 402,
"quoteCount": 2,
"bookmarkCount": 32,
"langs": [
"en"
],
"images": [],
"video": null,
"external": null,
"quote": {
"uri": "at://did:plc:qddhk5tchy7gdyws2l4srl47/app.bsky.feed.post/3mtwudf3uok27",
"url": "https://bsky.app/profile/bart.gov/post/3mtwudf3uok27",
"text": "Come take the full tour of the BART A, B and C Cars on display at the We…",
"createdAt": "2026-08-25T22:24:58.681Z",
"author": {
"did": "did:plc:qddhk5tchy7gdyws2l4srl47",
"handle": "bart.gov",
"displayName": "BART",
"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:qddhk5tchy7gdyws2l4srl47/b…"
}
},
"links": [],
"mentions": [
"bart.gov"
],
"tags": [],
"replyTo": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtykea5kds2d",
"replies": [
{
"uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtzbwnbvrs24",
"cid": "bafyreie4usq5gutvl7drrj6rliswozwzzmcei3qy7g6wyuxyobwb4wprdy",
"url": "https://bsky.app/profile/bsky.app/post/3mtzbwnbvrs24",
"text": "NOW PLAYING: \"Sludgecam\" by @neorsd.org",
"createdAt": "2026-08-26T21:33:42.923Z",
"author": {
"did": "did:plc:z72i7hdynmk6r22z27h6tvur",
"handle": "bsky.app",
"displayName": "Bluesky",
"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:z72i7hdynmk6r22z27h6tvur/b…"
},
"replyCount": 15,
"repostCount": 16,
"likeCount": 298,
"quoteCount": 0,
"bookmarkCount": 11,
"langs": [
"en"
],
"images": [],
"video": null,
"external": null,
"quote": {
"uri": "at://did:plc:itwimoiaj7qfal7hizju3syz/app.bsky.feed.post/3mtwwrztp622j",
"url": "https://bsky.app/profile/neorsd.org/post/3mtwwrztp622j",
"text": "longer videos on @bsky.app means i can finally post 10 full minutes of s…",
"createdAt": "2026-08-25T23:08:57.677Z",
"author": {
"did": "did:plc:itwimoiaj7qfal7hizju3syz",
"handle": "neorsd.org",
"displayName": "NE Ohio Regional Sewer District",
"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:itwimoiaj7qfal7hizju3syz/b…"
}
},
"links": [],
"mentions": [
"neorsd.org"
],
"tags": [],
"replyTo": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtyzbvfbb222",
"replies": [
{
"uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtzmdtsaw22w",
"cid": "bafyreie7xfzf65hhtseni2ppxs5q5mrjr5cyzawblhwuknvjcy2mc7kgny",
"url": "https://bsky.app/profile/bsky.app/post/3mtzmdtsaw22w",
"text": "NOW PLAYING: \"Taskmaster Fan Fiction (Greg's Christmas Feast)\" by @thehornesection.bsky.social",
"createdAt": "2026-08-27T00:40:03.375Z",
"author": {
"did": "did:plc:z72i7hdynmk6r22z27h6tvur",
"handle": "bsky.app",
"displayName": "Bluesky",
"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:z72i7hdynmk6r22z27h6tvur/b…"
},
"replyCount": 9,
"repostCount": 23,
"likeCount": 525,
"quoteCount": 3,
"bookmarkCount": 55,
"langs": [
"en"
],
"images": [],
"video": null,
"external": null,
"quote": {
"uri": "at://did:plc:sfytqb5ksdwbhen5jorylyf6/app.bsky.feed.post/3mtwakstrcc2k",
"url": "https://bsky.app/profile/thehornesection.bsky.social/post/3mtwakstrcc2k",
"text": "Throwback to when Alex found some Taskmaster fan fiction about him and G…",
"createdAt": "2026-08-25T16:31:13.139Z",
"author": {
"did": "did:plc:sfytqb5ksdwbhen5jorylyf6",
"handle": "thehornesection.bsky.social",
"displayName": "The Horne Section",
"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:sfytqb5ksdwbhen5jorylyf6/b…"
}
},
"links": [],
"mentions": [
"thehornesection.bsky.social"
],
"tags": [],
"replyTo": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtzbwnbvrs24",
"replies": []
}
]
},
{
"uri": "at://did:plc:xwzonyjk7rpsn7v6attpxmf2/app.bsky.feed.post/3mu3j2i2m222k",
"cid": "bafyreieuadtlbmfyw4hl4gtyg66eb7nkdtil5csqirktskq3v67mdgd3ui",
"url": "https://bsky.app/profile/hologrid99.bsky.social/post/3mu3j2i2m222k",
"text": "nice ⭐",
"createdAt": "2026-08-27T18:46:27.328Z",
"author": {
"did": "did:plc:xwzonyjk7rpsn7v6attpxmf2",
"handle": "hologrid99.bsky.social",
"displayName": null,
"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:xwzonyjk7rpsn7v6attpxmf2/b…"
},
"replyCount": 0,
"repostCount": 0,
"likeCount": 0,
"quoteCount": 0,
"bookmarkCount": 0,
"langs": [
"en"
],
"images": [],
"video": null,
"external": null,
"quote": null,
"links": [],
"mentions": [],
"tags": [],
"replyTo": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtyzbvfbb222",
"replies": []
}
]
},
{
"uri": "at://did:plc:xh3czhp62wowuzbor5jfmklx/app.bsky.feed.post/3mtylqgnnjc25",
"cid": "bafyreib35r3mx4fuk4g5aiviczcsxhwhqkjwqytcqpphpjvm7cwgbmpnce",
"url": "https://bsky.app/profile/gokaired215.bsky.social/post/3mtylqgnnjc25",
"text": "OK so for some reason videos over like 5 minutes don't appear on a profile for me?",
"createdAt": "2026-08-26T14:56:32.321Z",
"author": {
"did": "did:plc:xh3czhp62wowuzbor5jfmklx",
"handle": "gokaired215.bsky.social",
"displayName": "G",
"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:xh3czhp62wowuzbor5jfmklx/b…"
},
"replyCount": 0,
"repostCount": 0,
"likeCount": 0,
"quoteCount": 0,
"bookmarkCount": 0,
"langs": [
"en"
],
"images": [],
"video": null,
"external": null,
"quote": null,
"links": [],
"mentions": [],
"tags": [],
"replyTo": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtykea5kds2d",
"replies": []
}
],
"parents": [
{
"uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtwkahfcss2p",
"cid": "bafyreie76xk3wtpecmwh3s5ybju7vjv7cybe2he4l77okrmpcpr3uuwece",
"url": "https://bsky.app/profile/bsky.app/post/3mtwkahfcss2p",
"text": "NOW PLAYING: \"Chiitan Blooper Reel\" by @chiitan.love",
"createdAt": "2026-08-25T19:24:23.007Z",
"author": {
"did": "did:plc:z72i7hdynmk6r22z27h6tvur",
"handle": "bsky.app",
"displayName": "Bluesky",
"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:z72i7hdynmk6r22z27h6tvur/b…"
},
"replyCount": 11,
"repostCount": 76,
"likeCount": 806,
"quoteCount": 8,
"bookmarkCount": 50,
"langs": [
"en"
],
"images": [],
"video": null,
"external": null,
"quote": {
"uri": "at://did:plc:t5pur7eruxfgxy7ynpfrmg2w/app.bsky.feed.post/3mtw4hu5cy22l",
"url": "https://bsky.app/profile/chiitan.love/post/3mtw4hu5cy22l",
"text": "✨Chiitan Special video✨\nCelebrating that Bluesky now lets you post longe…",
"createdAt": "2026-08-25T15:17:58.872Z",
"author": {
"did": "did:plc:t5pur7eruxfgxy7ynpfrmg2w",
"handle": "chiitan.love",
"displayName": "Chiitan🌈ちぃたん☆",
"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:t5pur7eruxfgxy7ynpfrmg2w/b…"
}
},
"links": [],
"mentions": [
"chiitan.love"
],
"tags": [],
"replyTo": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtwgs26j2c2d"
},
{
"uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtwm5grjns22",
"cid": "bafyreifn4ucipyjbdtylqjnfbpimp5ujf3wmbuvechb6lwjgf36vlj5vvi",
"url": "https://bsky.app/profile/bsky.app/post/3mtwm5grjns22",
"text": "NOW PLAYING: \"Four Minutes and Nineteen Seconds of College Football High…",
"createdAt": "2026-08-25T19:58:29.180Z",
"author": {
"did": "did:plc:z72i7hdynmk6r22z27h6tvur",
"handle": "bsky.app",
"displayName": "Bluesky",
"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:z72i7hdynmk6r22z27h6tvur/b…"
},
"replyCount": 13,
"repostCount": 31,
"likeCount": 524,
"quoteCount": 9,
"bookmarkCount": 29,
"langs": [
"en"
],
"images": [],
"video": null,
"external": null,
"quote": {
"uri": "at://did:plc:upyx3raxzpwlnzojrncogd7y/app.bsky.feed.post/3mtw23sqfcs2w",
"url": "https://bsky.app/profile/sickoscommittee.org/post/3mtw23sqfcs2w",
"text": "I set a college football highlight video to the full 4 minutes and 19 se…",
"createdAt": "2026-08-25T14:35:27.264Z",
"author": {
"did": "did:plc:upyx3raxzpwlnzojrncogd7y",
"handle": "sickoscommittee.org",
"displayName": "Sickos Committee",
"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:upyx3raxzpwlnzojrncogd7y/b…"
}
},
"links": [],
"mentions": [
"sickoscommittee.org"
],
"tags": [],
"replyTo": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtwkahfcss2p"
}
]
}Response fields
Every field in a successful response, and what it means. A name with [] belongs to each item of a list: post.images[].thumbnail is the thumbnail of every item in post.images. null means there's no value.
Show all 136 fieldsHide
| Field | Type | Description |
|---|---|---|
| success | true | Always true here; errors have success: false. |
| credits_remaining | number | Your balance after this call. On anonymous playground calls: free tries left today. |
| credits_charged | number | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). |
| post | object | The post. |
| post.uri | string | The post's at:// URI, its permanent id. |
| post.cid | string | Content hash of this version of the post. |
| post.url | string | The post on bsky.app. |
| post.text | string | Post text, with links the app shortened (example.com/long-pa...) written out in full. Empty for media-only posts. |
| post.createdAt | string | When it was posted, ISO 8601. |
| post.author | object | Who posted it. |
| post.author.did | string | The account's permanent id, e.g. did:plc:z72i7hdynmk6r22z27h6tvur. |
| post.author.handle | string | Handle, e.g. bsky.app. It can change; the DID doesn't. |
| post.author.displayName | stringcan be null | Display name; null if not set. |
| post.author.avatar | stringcan be null | Avatar image URL; null if none. |
| post.replyCount | number | Replies. |
| post.repostCount | number | Reposts. |
| post.likeCount | number | Likes. |
| post.quoteCount | number | Quote posts. |
| post.bookmarkCount | number | Times saved to bookmarks. |
| post.langs | string[] | Languages the author tagged, e.g. en. |
| post.images | object[] | Attached images, in order. Empty if none. |
| post.images[].thumbnail | string | Small image URL. |
| post.images[].fullsize | string | Full-size image URL. |
| post.images[].alt | string | Alt text; empty if none. |
| post.video | objectcan be null | Attached video; null if none. |
| post.video.playlist | string | HLS playlist (.m3u8) URL. |
| post.video.thumbnail | stringcan be null | Poster image URL; null if none. |
| post.video.alt | stringcan be null | Alt text; null if none. |
| post.external | objectcan be null | Link card; null if none. |
| post.external.uri | string | The linked page. |
| post.external.title | string | Link card title. |
| post.external.description | string | Link card description. |
| post.external.thumbnail | stringcan be null | Link card image URL; null if none. |
| post.quote | objectcan be null | The post this one quotes; null if none or hidden from logged-out visitors. Embedded lists, feeds and starter packs aren't returned. |
| post.quote.uri | string | The quoted post's at:// URI. Pass it as url to the post endpoint. |
| post.quote.url | string | The quoted post on bsky.app. |
| post.quote.text | stringcan be null | Its text; null when it was deleted or its author blocks viewers. |
| post.quote.createdAt | stringcan be null | When it was posted, ISO 8601; null when unavailable. |
| post.quote.author | objectcan be null | Who posted it; null when unavailable. |
| post.quote.author.did | string | The account's permanent id, e.g. did:plc:z72i7hdynmk6r22z27h6tvur. |
| post.quote.author.handle | string | Handle, e.g. bsky.app. It can change; the DID doesn't. |
| post.quote.author.displayName | stringcan be null | Display name; null if not set. |
| post.quote.author.avatar | stringcan be null | Avatar image URL; null if none. |
| post.links | string[] | Full URLs of the links in the text. |
| post.mentions | string[] | Accounts mentioned in the text: the handle as written, without @, or the DID when the mention isn't written as @handle. |
| post.tags | string[] | Hashtags in the text, without #. |
| post.replyTo | stringcan be null | at:// URI of the post this replies to; null for top-level posts. |
| replies | object[] | Replies in Bluesky's order, each with its own replies. |
| replies[].uri | string | The post's at:// URI, its permanent id. |
| replies[].cid | string | Content hash of this version of the post. |
| replies[].url | string | The post on bsky.app. |
| replies[].text | string | Post text, with links the app shortened (example.com/long-pa...) written out in full. Empty for media-only posts. |
| replies[].createdAt | string | When it was posted, ISO 8601. |
| replies[].author | object | Who posted it. |
| replies[].author.did | string | The account's permanent id, e.g. did:plc:z72i7hdynmk6r22z27h6tvur. |
| replies[].author.handle | string | Handle, e.g. bsky.app. It can change; the DID doesn't. |
| replies[].author.displayName | stringcan be null | Display name; null if not set. |
| replies[].author.avatar | stringcan be null | Avatar image URL; null if none. |
| replies[].replyCount | number | Replies. |
| replies[].repostCount | number | Reposts. |
| replies[].likeCount | number | Likes. |
| replies[].quoteCount | number | Quote posts. |
| replies[].bookmarkCount | number | Times saved to bookmarks. |
| replies[].langs | string[] | Languages the author tagged, e.g. en. |
| replies[].images | object[] | Attached images, in order. Empty if none. |
| replies[].images[].thumbnail | string | Small image URL. |
| replies[].images[].fullsize | string | Full-size image URL. |
| replies[].images[].alt | string | Alt text; empty if none. |
| replies[].video | objectcan be null | Attached video; null if none. |
| replies[].video.playlist | string | HLS playlist (.m3u8) URL. |
| replies[].video.thumbnail | stringcan be null | Poster image URL; null if none. |
| replies[].video.alt | stringcan be null | Alt text; null if none. |
| replies[].external | objectcan be null | Link card; null if none. |
| replies[].external.uri | string | The linked page. |
| replies[].external.title | string | Link card title. |
| replies[].external.description | string | Link card description. |
| replies[].external.thumbnail | stringcan be null | Link card image URL; null if none. |
| replies[].quote | objectcan be null | The post this one quotes; null if none or hidden from logged-out visitors. Embedded lists, feeds and starter packs aren't returned. |
| replies[].quote.uri | string | The quoted post's at:// URI. Pass it as url to the post endpoint. |
| replies[].quote.url | string | The quoted post on bsky.app. |
| replies[].quote.text | stringcan be null | Its text; null when it was deleted or its author blocks viewers. |
| replies[].quote.createdAt | stringcan be null | When it was posted, ISO 8601; null when unavailable. |
| replies[].quote.author | objectcan be null | Who posted it; null when unavailable. |
| replies[].quote.author.did | string | The account's permanent id, e.g. did:plc:z72i7hdynmk6r22z27h6tvur. |
| replies[].quote.author.handle | string | Handle, e.g. bsky.app. It can change; the DID doesn't. |
| replies[].quote.author.displayName | stringcan be null | Display name; null if not set. |
| replies[].quote.author.avatar | stringcan be null | Avatar image URL; null if none. |
| replies[].links | string[] | Full URLs of the links in the text. |
| replies[].mentions | string[] | Accounts mentioned in the text: the handle as written, without @, or the DID when the mention isn't written as @handle. |
| replies[].tags | string[] | Hashtags in the text, without #. |
| replies[].replyTo | stringcan be null | at:// URI of the post this replies to; null for top-level posts. |
| replies[].replies | BlueskyReply[] | Replies to this reply. |
| parents | object[] | The posts above this one in the thread, first post first. Empty for top-level posts. |
| parents[].uri | string | The post's at:// URI, its permanent id. |
| parents[].cid | string | Content hash of this version of the post. |
| parents[].url | string | The post on bsky.app. |
| parents[].text | string | Post text, with links the app shortened (example.com/long-pa...) written out in full. Empty for media-only posts. |
| parents[].createdAt | string | When it was posted, ISO 8601. |
| parents[].author | object | Who posted it. |
| parents[].author.did | string | The account's permanent id, e.g. did:plc:z72i7hdynmk6r22z27h6tvur. |
| parents[].author.handle | string | Handle, e.g. bsky.app. It can change; the DID doesn't. |
| parents[].author.displayName | stringcan be null | Display name; null if not set. |
| parents[].author.avatar | stringcan be null | Avatar image URL; null if none. |
| parents[].replyCount | number | Replies. |
| parents[].repostCount | number | Reposts. |
| parents[].likeCount | number | Likes. |
| parents[].quoteCount | number | Quote posts. |
| parents[].bookmarkCount | number | Times saved to bookmarks. |
| parents[].langs | string[] | Languages the author tagged, e.g. en. |
| parents[].images | object[] | Attached images, in order. Empty if none. |
| parents[].images[].thumbnail | string | Small image URL. |
| parents[].images[].fullsize | string | Full-size image URL. |
| parents[].images[].alt | string | Alt text; empty if none. |
| parents[].video | objectcan be null | Attached video; null if none. |
| parents[].video.playlist | string | HLS playlist (.m3u8) URL. |
| parents[].video.thumbnail | stringcan be null | Poster image URL; null if none. |
| parents[].video.alt | stringcan be null | Alt text; null if none. |
| parents[].external | objectcan be null | Link card; null if none. |
| parents[].external.uri | string | The linked page. |
| parents[].external.title | string | Link card title. |
| parents[].external.description | string | Link card description. |
| parents[].external.thumbnail | stringcan be null | Link card image URL; null if none. |
| parents[].quote | objectcan be null | The post this one quotes; null if none or hidden from logged-out visitors. Embedded lists, feeds and starter packs aren't returned. |
| parents[].quote.uri | string | The quoted post's at:// URI. Pass it as url to the post endpoint. |
| parents[].quote.url | string | The quoted post on bsky.app. |
| parents[].quote.text | stringcan be null | Its text; null when it was deleted or its author blocks viewers. |
| parents[].quote.createdAt | stringcan be null | When it was posted, ISO 8601; null when unavailable. |
| parents[].quote.author | objectcan be null | Who posted it; null when unavailable. |
| parents[].quote.author.did | string | The account's permanent id, e.g. did:plc:z72i7hdynmk6r22z27h6tvur. |
| parents[].quote.author.handle | string | Handle, e.g. bsky.app. It can change; the DID doesn't. |
| parents[].quote.author.displayName | stringcan be null | Display name; null if not set. |
| parents[].quote.author.avatar | stringcan be null | Avatar image URL; null if none. |
| parents[].links | string[] | Full URLs of the links in the text. |
| parents[].mentions | string[] | Accounts mentioned in the text: the handle as written, without @, or the DID when the mention isn't written as @handle. |
| parents[].tags | string[] | Hashtags in the text, without #. |
| parents[].replyTo | stringcan be null | at:// URI of the post this replies to; null for top-level posts. |
Cost and freshness
Each call costs 1 credit. Calls turned away before they reach the platform (invalid parameters, a key or balance problem, a rate limit) are free, and so are 5xx errors. A not_found is charged, since the platform was checked. How charging works.
Answers are cached for up to 3 minutes. A repeat call within that time can return the same data, and costs the same.
Errors
A failed call returns success: false, an error message, a code your program can check, and a docs link. These are the errors this endpoint can return. All error codes.
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_api_key | No API key was sent. Put it in the x-api-key header. |
| 401 | invalid_api_key | This key doesn't exist or was revoked. |
| 402 | insufficient_credits | Not enough credits for this call. Buy a pack, or wait for tomorrow's free top-up. |
| 402 | over_max_credits | This call would cost more than the max_credits you set. The error says what it needs; raise max_credits to allow it. Not charged. |
| 403 | account_suspended | This account is suspended. Email support@lurkapi.com. Not charged. |
| 403 | not_public | The platform shows this account or post only to people who are logged in. Try a public one. Charged, since the platform was checked. |
| 404 | not_found | What you asked for (a post, an ad, a subreddit) doesn't exist, or there's no endpoint at that path. Charged if the platform was checked. |
| 405 | method_not_allowed | Endpoints only take GET, with parameters in the URL. |
| 429 | rate_limited | Too many calls in a short time. Wait as many seconds as the retry-after header says, then retry. |
| 500 | internal_error | Something broke on our side. Retry. Not charged. |
| 502 | upstream_error | The platform didn't send a usable answer. Retry. Not charged. |
| 503 | upstream_busy | All our connections to the platform are busy. Retry in a few seconds. Not charged. |
| 504 | upstream_timeout | The platform took over 30 seconds to answer (90 for web pages). Retry. Not charged. |
Use it in Claude
Connect LurkAPI to Claude and ask in plain English. This endpoint is the bluesky_post tool. For example:
Use LurkAPI's bluesky_post tool with url "https://bsky.app/profile/bsky.app/post/3mtykea5kds2d", then summarize what you find.Claude calls bluesky_post with arguments like these, and each call costs 1 credit:
{"url": "https://bsky.app/profile/bsky.app/post/3mtykea5kds2d"}To save tokens, tool results leave out empty fields, keep one URL per image or video, and give transcripts as plain text unless you pass timestamps=true. A result over 45,000 characters comes in parts. Its _note gives the offset for the next part, and later parts are free for 15 minutes. Pass trim=false for the full response.
From the blog
All posts
· 5 min read
Best Reddit scrapers in 2026: price and speed compared
LurkAPI is the best Reddit scraper: 1,000 subreddit posts for $0.04, a feed in 0.92 s, and search and comment threads on the same key.

· 5 min read
Instagram MCP server for Claude and ChatGPT
Claude can’t read Instagram on its own. Add one MCP URL and it reads any public account’s posts, reels and comments, 1 credit a call, no Meta app.

· 5 min read
Best Threads scrapers in 2026: what 1,000 posts cost
LurkAPI is the best Threads scraper: 1,000 posts for $0.06, a page in about 1.2 s, and pages back to an account's first post.