---
title: "Bluesky User Posts API"
method: GET
path: "/v1/bluesky/user-posts"
tags: ["Bluesky"]
---

# Bluesky User Posts API

`GET /v1/bluesky/user-posts`

## Request

- Base URL: `https://api.captapi.com`
- URL: `https://api.captapi.com/v1/bluesky/user-posts`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-API-Key`

Author feed — posts and reposts (isRepost marked), quote/external/images embeds, opaque cursor.

Send a Bluesky profile URL, @handle, or handle and get that account's public author feed (app.bsky.feed.getAuthorFeed) as clean JSON — original posts and reposts. Reposts keep the original author{} and engagement; they are marked isRepost=true with repostedBy{handle,displayName,did,avatar} and repostedAt so analytics do not credit someone else's likes to the profile you queried. Pass includeReposts=false to drop reposts. Optional filter maps to Bluesky's feed filter (posts_with_replies | posts_no_replies | posts_with_media | posts_and_author_threads | posts_with_video) — that controls replies/media/threads, not reposts. Each row: uri/url/cid, text, publishedAt, indexedAt, author{}, engagement{likes,reposts,replies,quotes}, and embed as one of type external | images | video | quote (quotes include uri/url/text/author — never a raw lexicon NSID). Rows are ordered by effective timestamp — repostedAt for reposts, publishedAt otherwise — matching Bluesky's author-feed order; re-sorting by publishedAt alone yields a different feed. nextCursor is Bluesky's opaque cursor (do not derive it from publishedAt). Billed ~0.1 credits per returned row (limit max 100). Pass cache=true for the 24h shared cache.

Docs: https://captapi.com/apis/bluesky-user-posts

## Query parameters

- `url` string, required
- `limit` integer
- `cursor` string
- `filter` string
- `includeReposts` boolean
- `cache` boolean

## Response `200`

Success. The data object for this endpoint.

- object
  - `success` unknown, required
  - `data` object, required
    - `handle` string: The actor whose author feed you requested.
    - `totalReturned` integer: Number of items returned in this response.
    - `nextCursor` string: Opaque AppView cursor for the next page. Pass it through unchanged — do not build a cursor from publishedAt (feed order includes repost time).
    - `hasMore` boolean: true when nextCursor is present.
    - `posts` object[]
      - `platform` string: Platform identifier for this response (matches the endpoint's platform).
      - `uri` string: AT URI of the post (at://did…/app.bsky.feed.post/rkey).
      - `url` string: bsky.app permalink for the post.
      - `cid` string: Content ID (CID) of this post record — stable content-addressed hash.
      - `text` string: Post text body. Long URLs may be truncated here — use links[] from facets for the full URI.
      - `publishedAt` string: When the original post was created (record createdAt). Not repost time — feed order uses repostedAt for isRepost rows.
      - `indexedAt` string: When the AppView indexed this post. For reposts, feed order follows repostedAt — not this field.
      - `author` object: Author of the underlying post: {handle, displayName, did, avatar}. On reposts this is the original author — not the profile you queried. For verification/labels use post-details.
        - `handle` string: The actor whose author feed you requested.
        - `displayName` string
        - `did` string
        - `avatar` string
      - `engagement` object: Engagement on the underlying post: {likes, reposts, replies, quotes}. On isRepost rows these counts are the original author's — do not average them onto the requested handle without filtering. No view count on Bluesky.
        - `likes` integer
        - `reposts` integer
        - `replies` integer
        - `quotes` integer
      - `embed` object, nullable: Normalized embed: type external {url,title,description,thumb} | images {images[{url,alt}]} | video {playlist,thumbnail,alt} | quote {uri,url,text,author,cid,publishedAt}. Never a raw lexicon NSID.
        - `type` string
        - `url` string: bsky.app permalink for the post.
        - `title` string
        - `description` string
        - `thumb` string

## Other responses

- `400`: Bad request: wrong platform URL, unknown endpoint, or an invalid parameter value. Not billed.
- `401`: Missing, invalid or revoked API key. Not billed.
- `402`: Not enough credits for this request. Not billed.
- `404`: The resource does not exist or is not public. Not billed.
- `422`: A required parameter is missing or has the wrong type. Not billed.
- `429`: Rate limit reached. Wait for Retry-After seconds. Not billed.
- `502`: The platform could not be reached or returned nothing usable. Retryable, not billed. 503 and 504 use the same envelope.

---

[API](https://skmtc.dev/captapi/apis/captapi-api.md) · [All operations](https://skmtc.dev/captapi/apis/captapi-api/llms.txt) · [OpenAPI document](https://skmtc.dev/captapi/apis/captapi-api/revisions/c9b9ac5ff1bd?raw)
