> **For coding agents and LLMs:** This is one page from the Social Fetch docs (markdown export). For curated orientation and workflow guidance, start with [`/llms.txt`](https://www.socialfetch.dev/llms.txt); for agent onboarding and crawl rules, use [`/agents.txt`](https://www.socialfetch.dev/agents.txt); for the full endpoint list with links to pages like this one, use [`/llms-endpoints.txt`](https://www.socialfetch.dev/llms-endpoints.txt); for one platform's parameters and curls, use [`/llms-{platform}.txt`](https://www.socialfetch.dev/llms-tiktok.txt); use [`/llms.json`](https://www.socialfetch.dev/llms.json) when you need structured JSON for tool registration.

## This page

- **On-site (HTML):** <https://www.socialfetch.dev/docs/api/v1/substack/publications/publication/posts/bodies/get>
- **Markdown (.mdx) URL:** <https://www.socialfetch.dev/docs/api/v1/substack/publications/publication/posts/bodies/get.mdx>

## API base URL and authentication

- **API origin (from OpenAPI `servers`):** `https://api.socialfetch.dev`
- **Authentication:** send `x-api-key: sfk_...` on `/v1/**` and `/v2/**` routes unless the operation is explicitly anonymous (check OpenAPI `security`, the [API reference hub](https://www.socialfetch.dev/docs/api.mdx), [`/llms.txt`](https://www.socialfetch.dev/llms.txt), or [`/llms.json`](https://www.socialfetch.dev/llms.json) for each route).
- **OpenAPI JSON:** <https://www.socialfetch.dev/openapi.json>

## Recommended docs entrypoints (this site)

- [Documentation overview](https://www.socialfetch.dev/docs.mdx) — top-level orientation (markdown).
- [Quickstart](https://www.socialfetch.dev/docs/quickstart.mdx) — authenticate with `x-api-key`, validate auth with `whoami`, and understand the JSON envelope.
- [SDK](https://www.socialfetch.dev/docs/sdk.mdx) — official TypeScript SDK guide, including `SocialFetchClient`, `Result`, and `unwrap()`.
- [Capability matrix](https://www.socialfetch.dev/docs/capability-matrix.mdx) — every operation with identifiers, pagination, outcomes, media download, credits, and its SDK method. Generated from OpenAPI, so use it for route selection instead of scanning individual pages.
- [Recipes](https://www.socialfetch.dev/docs/recipes.mdx) — copyable workflows (brand monitoring, transcripts, Ad Library, creator scoring, Reddit research) with credit callouts and SDK examples.
- [Integrations](https://www.socialfetch.dev/docs/integrations.mdx) — MCP for AI clients, n8n verified node, Apify Store Actors, Make custom app, SDK, and REST API connection paths.
- [MCP product page](https://www.socialfetch.dev/mcp) — hosted MCP overview, OAuth, Skills install.
- [MCP integration](https://www.socialfetch.dev/docs/integrations/mcp.mdx) — hosted `/mcp` server, OAuth, Cursor/VS Code/Claude install snippets, 910 endpoint tools, plus docs_search/docs_read for implementation help.
- [n8n integration](https://www.socialfetch.dev/docs/integrations/n8n.mdx) — install `n8n-nodes-socialfetch`, credentials, and workflow examples.
- [Apify integration](https://www.socialfetch.dev/docs/integrations/apify.mdx) — Store Actors under @social-fetch, PPE billing, dataset export, and quick start.
- [Make integration](https://www.socialfetch.dev/docs/integrations/make.mdx) — custom app modules for Make scenarios, API key credentials, and module catalog.
- [`/llms-endpoints.txt`](https://www.socialfetch.dev/llms-endpoints.txt) — every documented operation with a direct link to that route's agent-readable markdown page (prefer this over parsing OpenAPI).
- [`/llms-{platform}.txt`](https://www.socialfetch.dev/llms-tiktok.txt) — per-platform endpoint files generated from OpenAPI (parameters, credits, curls).
- [`/agents.txt`](https://www.socialfetch.dev/agents.txt) — agent crawl/onboarding file with capabilities, auth rules, and allowlist.
- [`/llms.json`](https://www.socialfetch.dev/llms.json) — structured machine-readable operation inventory with parameter names, pagination, outcomes, credits, and SDK mapping.
- [API reference hub](https://www.socialfetch.dev/docs/api.mdx) — human-friendly index of operations with links into generated pages.
- [Errors](https://www.socialfetch.dev/docs/errors.mdx) — shared error envelope and HTTP status guidance.
- [Credits](https://www.socialfetch.dev/docs/credits.mdx) — metering, `402`, and planning batch jobs.
- Outcome semantics such as `found`, `not_found`, and `private` are documented in [Errors](https://www.socialfetch.dev/docs/errors.mdx) and on operation pages when present in the OpenAPI contract.

## Markdown docs convention

- Every docs page has a markdown twin: append **`.mdx`** to the docs pathname (for example `/docs/quickstart` → `/docs/quickstart.mdx`).
- Agents that send `Accept: text/markdown` on `/docs/**` HTML URLs may receive markdown directly (same URL, `Vary: Accept`).
- Published blog posts use the same convention: `/blog/{slug}` → `/blog/{slug}.mdx`, or `Accept: text/markdown` on the HTML URL (`Vary: Accept`).

---
# List Substack posts with bodies (https://www.socialfetch.dev/docs/api/v1/substack/publications/publication/posts/bodies/get)

## Summary

List Substack posts with their full free bodies.

**Tags:** `Substack`

## HTTP

- **Method:** GET
- **Path:** `/v1/substack/publications/{publication}/posts/bodies`
- **Base URL:** `https://api.socialfetch.dev`

## Capability summary

- **SDK mapping:** `client.substack.listPublicationPostBodies({ publication: "astralcodexten", limit: 10 })`
- **Accepted identifiers:** `publication` (path)
- **Pagination:** cursor via `cursor`, next cursor: `data.page.nextCursor`, has more: `data.page.hasMore`
- **Business outcome field:** `data.lookupStatus` with values `found`, `not_found`, `restricted`

## Credits

- **Base:** 1 credit per successful lookup.
- **Maximum on success (200):** 1 credit.
- **Failed requests (`lookup_failed`, `503`):** not charged.
- **Authoritative field:** `meta.creditsCharged`.

## Authentication

- **`x-api-key`**: API key (`sfk_...`)

## Parameters

### `publication` (path)

- **Required:** yes
- **Constraints:** type `string`; minLength: 1; maxLength: 400
- **Description:** A publication: its numeric id (`89120`), its Substack subdomain (`astralcodexten`), its host (`www.astralcodexten.com`) or its URL. A numeric id is refused with a 400 on the routes that need a subdomain, host or URL.

### `limit` (query)

- **Required:** no
- **Constraints:** type `integer`; default: `20`; minimum: 1; maximum: 50
- **Description:** Posts to return, 1 to 50.

### `cursor` (query)

- **Required:** no
- **Constraints:** type `string`; maxLength: 4096
- **Description:** Opaque cursor from the previous page's `nextCursor`.

## Pagination

This endpoint uses **cursor-based pagination** via the `cursor` query parameter.

- Read **hasMore** from `data.page.hasMore`.
- When that value is `true`, read **nextCursor** from `data.page.nextCursor` and pass it as the `cursor` query parameter on the **next** request (URL-encode when building a query string).
- Omit `cursor` on the **first** request.
- Stop when **hasMore** is `false` or **nextCursor** is null (end of list).

## Responses (status codes)

- **200**: Substack post bodies.
- **400**: Invalid request
- **401**: Missing or invalid API key
- **402**: Insufficient credits
- **500**: Unexpected or billing error
- **502**: Lookup could not be completed.
- **503**: Service temporarily unavailable; safe to retry with backoff.

## Response body (200)

Substack post bodies.

### Field outline

- **data** (required) — type `object`. Endpoint-specific response payload.
  - **lookupStatus** (required) — type `string`; enum: found, not_found, restricted. Whether the publication was found.
  - **restrictedReason** (required) — type `string`; enum: invite_only, flagged, paid, null; nullable. Why the publication is restricted, when it is.
  - **publicationId** (required) — type `integer`; nullable. Numeric publication id.
  - **posts** (required) — type `array`. The posts on this page.
    - _items:_
      - **id** (required) — type `integer`. Numeric post id.
      - **publicationId** (required) — type `integer`; nullable. Numeric publication id.
      - **slug** (required) — type `string`; nullable. The post's URL slug.
      - **title** (required) — type `string`; nullable. The post's title.
      - **subtitle** (required) — type `string`; nullable. The post's subtitle.
      - **description** (required) — type `string`; nullable. The post's short description.
      - **kind** (required) — type `string`; nullable. `newsletter`, `podcast`, `video` or `restack`.
      - **audience** (required) — type `string`; nullable. `everyone`, `only_paid`, `only_subscribers` or another value Substack uses.
      - **isPaywalled** (required) — type `boolean`; nullable. Whether the post is paid.
      - **url** (required) — type `string`; nullable. Canonical post URL.
      - **publishedAt** (required) — type `string`; nullable. ISO 8601 timestamp, or null when Substack does not publish one.
      - **updatedAt** (required) — type `string`; nullable. ISO 8601 timestamp, or null when Substack does not publish one.
      - **language** (required) — type `string`; nullable. The post's language code.
      - **bodyHtml** (required) — type `string`; nullable. Full body HTML for a free post; the preview only when `bodyIsPreview` is true.
      - **bodyIsPreview** (required) — type `boolean`. True when the audience is not `everyone`.
      - **previewText** (required) — type `string`; nullable. The public preview text.
      - **wordCount** (required) — type `integer`; nullable. The post's word count.
      - **commentCount** (required) — type `integer`; nullable. The number of comments on the post.
      - **replyCount** (required) — type `integer`; nullable. Replies to comments on the post.
      - **reactionCount** (required) — type `integer`; nullable. The number of reactions to the post.
      - **reactions** (required) — type `object`. Emoji to reaction count.
      - **restackCount** (required) — type `integer`; nullable. The number of restacks.
      - **commentPermission** (required) — type `string`; nullable. Who may comment on the post.
      - **coverImageUrl** (required) — type `string`; nullable. The post's cover image URL.
      - **coverImageIsSquare** (required) — type `boolean`; nullable. Whether the cover image is square.
      - **coverImageIsExplicit** (required) — type `boolean`; nullable. Whether the cover image is explicit.
      - **isGeoblocked** (required) — type `boolean`; nullable. Whether the post is geoblocked.
      - **blockedCountries** (required) — type `array`. Countries where the post is blocked.
        - _items:_
          - type `string`
      - **liveStreamId** (required) — type `string`; nullable. The live stream id, when the post is a live stream.
      - **isPublished** (required) — type `boolean`; nullable. Whether the post is published.
      - **isPinned** (required) — type `boolean`; nullable. Whether the post is pinned.
      - **seoTitle** (required) — type `string`; nullable. The title shown to search engines.
      - **seoDescription** (required) — type `string`; nullable. The description shown to search engines.
      - **socialTitle** (required) — type `string`; nullable. The title used for social previews.
      - **previousSlug** (required) — type `string`; nullable. The previous post's slug.
      - **nextSlug** (required) — type `string`; nullable. The next post's slug.
      - **section** (required) — type `object`; nullable. The section the post belongs to.
      - **tags** (required) — type `array`. The post's tags.
        - _items:_
      - **authors** (required) — type `array`. The post's authors.
        - _items:_
      - **podcast** (required) — type `object`; nullable. Podcast metadata, when the post is an episode.
      - **audio** (required) — type `object`; nullable. Audio, when the post has any.
      - **video** (required) — type `object`; nullable. Video, when the post has any.
  - **page** (required) — type `object`. Pagination state for the current page.
    - **hasMore** (required) — type `boolean`. Whether another page is available.
    - **nextCursor** (required) — type `string`; nullable. Cursor to pass in the next request when more pages exist; null on the last page.
- **meta** (required) — type `object`. Metadata describing the request and billing outcome.
  - **requestId** (required) — type `string`; minLength: 1. Unique request identifier for tracing this API call.
  - **creditsCharged** (required) — type `integer`; minimum: 0. Credits charged for this request.
  - **version** (required) — type `string`; enum: v1. Public API version that served the response.

### Example JSON (found)

```json
{
  "data": {
    "lookupStatus": "found",
    "restrictedReason": null,
    "publicationId": 1,
    "posts": [
      {
        "id": 222802023,
        "publicationId": 1,
        "slug": "new-on-substack-chat-channels-lists",
        "title": "New on Substack: chat channels, lists, freewalls, discounts, podcast updates, and more",
        "subtitle": "More ways to connect with your audience, get paid for your work, and be in control of your feed",
        "description": "More ways to connect with your audience, get paid for your work, and be in control of your feed",
        "kind": "newsletter",
        "audience": "everyone",
        "isPaywalled": false,
        "url": "https://on.substack.com/p/new-on-substack-chat-channels-lists",
        "publishedAt": "2026-10-30T17:03:17.633Z",
        "updatedAt": "2026-10-30T17:03:18.348Z",
        "language": "en",
        "bodyHtml": "<div class=\"captioned-image-container\"><figure><a class=\"image-link image2 is-viewable-img\" target=\"_blank\" href=\"https://substackcdn.com/image/fetch/$s_!2-fo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd8a5350-c953-404c-884a-0101eb29a821_2048x1474.p…",
        "bodyIsPreview": false,
        "previewText": "Our latest product updates give everyone more ownership over what they read, listen to, and watch on Substack and give creators more ways to connect with their audiences and turn that connection into long-lasting support, including paid subscriptions.",
        "wordCount": 1115,
        "commentCount": 0,
        "replyCount": 0,
        "reactionCount": 808,
        "reactions": {
          "❤": 808
        },
        "restackCount": 92,
        "commentPermission": "none",
        "coverImageUrl": "https://substack-post-media.s3.amazonaws.com/public/images/4be31bf5-e657-46bf-b998-79e7cb8f0306_2398x1342.png",
        "coverImageIsSquare": false,
        "coverImageIsExplicit": false,
        "isGeoblocked": false,
        "blockedCountries": [],
        "liveStreamId": null,
        "isPublished": true,
        "isPinned": false,
        "seoTitle": null,
        "seoDescription": null,
        "socialTitle": "New on Substack: chat channels, lists, freewalls, discounts, podcast updates, and more",
        "previousSlug": "introducing-lists-on-substack",
        "nextSlug": null,
        "section": {
          "id": 22019,
          "name": null,
          "slug": null
        },
        "tags": [],
        "authors": [],
        "podcast": {
          "episodeNumber": null,
          "seasonNumber": null,
          "episodeType": null
        },
        "audio": null,
        "video": null
      }
    ],
    "page": {
      "hasMore": true,
      "nextCursor": "eyJ2IjoxLCJ1IjoiZXlKd2IzTjBYMlJoZEdVaU9pSXlNREkyTFRFd0xUSXdWREUzT2pFek9qRTRMalU1Tmprek0xb2lmUT09IiwicCI6MSwiZiI6IjgxMzk4ZmQ3ZWNlOTFmZTYifQ"
    }
  },
  "meta": {
    "requestId": "req_3911cf34-198d-41ed-9c3d-8e2a635389ea",
    "creditsCharged": 1,
    "version": "v1"
  }
}
```

### Machine-readable error codes

When an error JSON body is returned, it may include one of these `error.code` values (derived from the OpenAPI schemas for this operation; additional codes may exist at runtime):

- `bad_request`

## Error handling & retries

Interpret HTTP status codes using the descriptions below. Do not assume a JSON body unless the OpenAPI schema defines one for that status.

- **400**: Invalid request **Retry:** Fix the request; retrying the same invalid payload will not help.
- **401**: Missing or invalid API key **Retry:** Fix the API key first; retrying without changes will not help.
- **402**: Insufficient credits **Retry:** Do not retry without resolving billing/credits (retrying the same request will not help).
- **500**: Unexpected or billing error
- **502**: Lookup could not be completed. **Retry:** May be transient; a few retries with backoff are reasonable.
- **503**: Service temporarily unavailable; safe to retry with backoff. **Retry:** Usually safe to retry with exponential backoff and jitter.

### Suggested client defaults

- Send the API key using the `x-api-key` header on every request.
- On `503` (and sometimes `502`), retry with backoff; cap retries and surface a clear error to the user.
- On `402`, surface an actionable billing message rather than blind retries.

## Examples

### TypeScript SDK

```typescript
import { SocialFetchClient } from "@socialfetch/sdk";

const client = new SocialFetchClient({
  apiKey: process.env.SOCIALFETCH_API_KEY!,
});

const result = await client.substack.listPublicationPostBodies({
  publication: "astralcodexten",
  limit: 10,
});

if (!result.ok) {
  console.error(result.error);
} else {
  console.log(result.value.data);
}
```

### Node.js

```javascript
const response = await fetch(
  "https://api.socialfetch.dev/v1/substack/publications/astralcodexten/posts/bodies?limit=10",
  {
    headers: {
      "x-api-key": "YOUR_API_KEY"
    }
  }
);

const data = await response.json();
console.log(data);
```

### cURL

```bash
curl "https://api.socialfetch.dev/v1/substack/publications/astralcodexten/posts/bodies?limit=10" \
  -H "x-api-key: YOUR_API_KEY"
```

### Python

```python
import requests

response = requests.get(
    "https://api.socialfetch.dev/v1/substack/publications/astralcodexten/posts/bodies?limit=10",
    headers={"x-api-key": "YOUR_API_KEY"},
)
data = response.json()
print(data)
```

### Example: next page (pagination)

After a successful response, if pagination is not finished, request the next page using `cursor` (URL-encode when composing the query string):

```javascript
const previous = await response.json();
const nextCursor = previous?.data?.page?.nextCursor;
const hasMore = previous?.data?.page?.hasMore;
if (!hasMore || nextCursor == null) {
  // no more pages
} else {
  const nextUrl = new URL("https://api.socialfetch.dev/v1/substack/publications/value/posts/bodies");
  nextUrl.searchParams.set("cursor", nextCursor);
  // optionally preserve sort: nextUrl.searchParams.set("sortBy", "latest");
  const nextResponse = await fetch(nextUrl.toString(), {
    headers: { "x-api-key": "YOUR_API_KEY" },
  });
  const nextData = await nextResponse.json();
}
```