Bluesky AT Protocol Posts: Why No Result Appears

A Bluesky post is created in a repository, and a reliable result should identify its record URI and CID. If your tool reports nothing, separate a missing response from a missing record, then confirm the authenticated DID, repository, record, and indexing state before retrying.

By · · 9 min read

Drafted with AI assistance from our own research and Search Console data, and reviewed by Rahul A before publishing. Rules and prices change; check the linked official source before you act.

What should a Bluesky post return when it succeeds?

A successful Bluesky post should return a record URI and a CID, which together identify the created record. The URI tells you where the post lives in the account repository, while the CID identifies the exact content version stored there.

The posting operation is normally the AT Protocol createRecord call against the account's Personal Data Server. A feed post uses the app.bsky.feed.post collection and a record containing the post type, text, and creation time. The response is not the post text repeated back to you, nor is it a promise that every Bluesky surface already displays the post.

For an operator, the useful success result is therefore compact but specific: the authenticated account identity, the repository or DID, the record URI, and the CID. A publishing layer that returns only “sent” throws away the evidence needed to investigate later. A layer that returns an empty value should treat the result as unknown, not successful. Developers should preserve the complete response before transforming it into a friendly status message.

For more context, read How to Publish Social Posts Safely from an MCP Server.

How do I separate an empty response from a failed post?

An empty response proves only that the caller received no usable result; it does not prove that Bluesky created nothing. The request may have completed while the response was discarded, parsed incorrectly, or hidden by a wrapper that returns an empty value for every outcome.

Start by recording the account identity and the exact createRecord request inputs, excluding credentials. Then inspect the publishing library's native result before any mapping to fields such as published or message. If a record URI or CID exists anywhere in the returned object, preserve it as the primary evidence. If no result exists, query the account repository or the account's profile feed for the expected record, using the text and creation time only as clues.

A later discovery of the record means the original operation was successful but the acknowledgement was lost. A failure to discover it means the operation remains unconfirmed, not automatically failed. Keep these states separate in dashboards and agent tools. Otherwise, an automatic retry can create duplicate posts while the operator sees only one earlier blank result.

For more context, read What an AI Agent Needs Before It Can Post for You.

Which Bluesky account did the AT Protocol request actually use?

The account that owns the created repository is determined by the authenticated session, not by the profile selected in a dashboard. A wrong session can publish a perfectly valid post to the wrong account, making the result look missing when the operator checks another profile.

Before publishing, resolve and display the session's DID and handle. The DID is the durable identity used by AT Protocol, while a handle can change and can be presented differently across tools. Compare that identity with the intended client or brand account. Also check that the repository named in the createRecord request matches the authenticated account rather than a cached account identifier.

Multi-account systems should bind every result to an internal channel identifier, the authenticated DID, the handle observed at publish time, and the returned record URI. Do not infer ownership from the browser tab, a human-readable name, or the post text. For an AI agent, make account selection an explicit input and return the chosen DID in its result. This single check catches many “nothing appeared” reports before feed indexing becomes relevant.

Why can a created Bluesky post be missing from a feed?

A created record can be valid before a feed, profile view, search result, or third-party client has indexed it. AT Protocol separates storage from the services that collect and present records, so visibility can lag behind repository creation.

Use the returned record URI as the first search key, not a feed refresh. A repository-level lookup can establish whether the record exists in the account's data. If the record exists there but not in a profile or feed, the storage step succeeded and the remaining issue concerns propagation, indexing, moderation, or the viewing service. Those are different from a failed post.

The distinction matters for operators managing several brands. A dashboard can label the item “created, awaiting visibility” instead of “failed,” while retaining a link built from the record URI. Developers should avoid treating a missing search result as proof of absence, especially immediately after creation. Search and custom feeds may apply their own indexing and filtering rules. When the repository does not contain the expected record, investigate identity, authentication, request construction, or response handling instead.

What record details must an AT Protocol post contain?

A Bluesky feed post needs a valid record type, text, and creation timestamp, with the repository collection set to app.bsky.feed.post. Optional facets can describe links, mentions, and rich text ranges, but malformed facets can make an otherwise readable post fail validation.

The timestamp must be formatted as an AT Protocol record expects, and text ranges must match the encoding used by the client. A link-looking string in plain text is not the same as a correctly declared facet. Mentions also require the correct identity reference rather than only a visible handle. Keep the initial diagnostic payload simple: plain text, a valid timestamp, and the required record type. Add facets, replies, quotes, or embeds one at a time.

This creates a useful decision rule. If a minimal text-only record succeeds, the account and basic write path work, so inspect the optional structure. If the minimal record also produces no identifiable result, focus first on session identity, repository selection, and response capture. Log the final normalized record shape, but never log passwords, session tokens, or other credential material.

How should a multi-account operator keep proof of publication?

A multi-account publishing system should store an immutable publication receipt containing the intended channel, authenticated DID, record URI, CID, request time, and observed status. The receipt should remain available even if a later dashboard refresh cannot load the post.

The record URI is the durable handoff between the publishing action and later investigation. The CID adds content-version evidence, which helps identify whether a later record is the same object or a changed version. Store the text fingerprint or internal post identifier alongside the receipt so an operator can compare a suspected duplicate without searching by wording alone.

Separate three labels in the interface: created, not confirmed, and not created. “Created” requires a record URI and CID or a repository lookup that proves the record. “Not confirmed” covers a lost or empty acknowledgement. “Not created” should be used only after a suitable repository check finds no matching record and the request outcome is known. This vocabulary prevents a blank tool response from triggering an unnecessary duplicate. It also gives an agent a safe, structured result instead of asking it to interpret vague prose.

When should a publisher stop and ask for a decision?

A publisher should stop when it cannot establish both the target identity and the outcome of the original write. An unknown outcome is not permission to send the same post again, because the original record may exist even when its acknowledgement disappeared.

Ask for a decision when the account DID differs from the selected channel, when the repository lookup is unavailable, when the returned identity is missing, or when the proposed retry would create a duplicate. The operator can then choose to inspect the account manually, accept a possible duplicate, or wait for a verified lookup. An agent should expose those choices rather than silently selecting one.

A safe automation boundary is simple. Automatic follow-up work may enrich a confirmed record, such as saving its URI or updating internal state. It should not create another post from an unconfirmed write unless the system has an explicit idempotency design and the operator accepts its behavior. This is especially important when one person manages many paid connections. Ambiguous output multiplied across accounts becomes an operational cost, not merely a technical inconvenience.

What should an AT Protocol publishing tool show its caller?

An AT Protocol publishing tool should return a structured result with identity, record evidence, visibility state, and a next action. The caller should never have to infer those fields from a sentence such as “published successfully.”

A useful result names the requested channel, authenticated DID, repository, collection, record URI, CID, and whether the record was found in storage. It can separately report whether a profile, feed, or search view has caught up. If the write response was empty, the result should say “not confirmed” and identify the lookup still needed. If validation failed before creation, it should identify the input area to fix without exposing internal credentials or confusing implementation details.

For AI-agent integrations, return machine-readable fields first and human guidance second. Include the original internal post ID so an agent can correlate a later lookup. Keep the record URI as a linkable artifact, but do not treat a clickable profile view as stronger proof than repository evidence. The most useful design is not the one with the friendliest success message. It is the one that makes an ambiguous write impossible to mistake for a confirmed publication.

Sources consulted: Bluesky Developer Documentation (docs.bsky.app)

Common questions

Does an empty Bluesky API response mean the post failed?

No. An empty response means the caller has no usable acknowledgement. The post may still exist if the response was lost or mishandled. Check the authenticated DID and inspect the account repository for the expected record before deciding whether the write failed or whether a retry could create a duplicate.

What are the URI and CID returned for a Bluesky post?

The record URI identifies the post within the account's AT Protocol repository. The CID identifies the exact content version stored at that record. Together, they provide stronger publication evidence than a generic success message, and they should be retained in the publishing receipt.

Why is my Bluesky post on the account but not in a feed?

Storage and display are separate steps in the AT Protocol ecosystem. A repository can contain a valid post before a profile, feed, search service, or third-party client indexes it. Check the repository first. If the record exists there, investigate propagation or indexing rather than publishing the post again.

How can I confirm which Bluesky account published a post?

Confirm the DID returned by the authenticated session and compare it with the intended channel. Then check the repository named by the write request and retain the resulting record URI. A dashboard selection or visible handle alone is not sufficient because cached sessions and changed handles can point an operation at the wrong account.

Should an automation retry when Bluesky returns nothing?

Not before checking whether the original record exists. A blank result creates an unknown outcome, and retrying immediately can publish duplicates. First preserve the request context, verify the authenticated identity, and search the repository. Retry only when the original write is safely established as not created.

PostWharf is priced per workspace rather than per connected channel, and every plan carries unlimited channels. See per-brand pricing.