---
title: "Read a content key"
method: GET
path: "/api/v1/sites/{slug}/content/{key}"
tags: ["Content"]
---

# Read a content key

`GET /api/v1/sites/{slug}/content/{key}`

## Request

- Base URL: `https://api.dotsy.ai`
- URL: `https://api.dotsy.ai/api/v1/sites/{slug}/content/{key}`
- Auth: one of:
  - HTTP bearer
  - HTTP bearer

Reads one key as the draft sees it (a staged edit wins over the live version). By default the response is the stored body itself, with its stored Content-Type. `format=json` wraps the body with its hash and type, `format=post` parses a blog post, and `selector` returns only the matched elements' tag, attributes and text (a cheap read of a large page). If you send more than one of these, `selector` wins over `format=json`, which wins over `format=post`. `published=1` reads the last published version and ignores the draft.

Every successful read carries an `ETag` (the quoted `contentHash`); send it as `If-Match` on a later write to catch a concurrent edit.

## Path parameters

- `slug` string, required
- `key` string, required

## Query parameters

- `format` 'json' | 'post'
- `selector` string
- `published` '1'

## Response `200`

The body, or the JSON form you asked for. Without `format` or `selector` the body is returned as is, with its stored Content-Type.

- union
  - ContentItem
    - `key` string, required: The content key.
    - `contentType` string, required
    - `contentHash` string, required: SHA-256 of the effective body: the staged draft if there is one, else the live version. Send it back as `If-Match` on a write to refuse the write if someone else changed the key meanwhile.
    - `updatedAt` number, required: Last write, Unix ms.
    - `body` string, required: The effective body.
  - ContentSelection
    - `key` string, required: The content key.
    - `contentHash` string, required: SHA-256 of the effective body: the staged draft if there is one, else the live version. Send it back as `If-Match` on a write to refuse the write if someone else changed the key meanwhile.
    - `selector` string, required: The selector you sent.
    - `count` integer, required: How many elements matched.
    - `matches` object[], required: One entry per matched element, in document order.
      - `tag` string, required
      - `attributes` object, required: The element's attributes.
      - `text` string, required: The element's text, without its child markup.
  - PostDocument
    - `slug` string, required
    - `meta` object, required
      - `title` string, required: Post title — shown on the blog card/listing and used as the <title> unless seoTitle overrides it.
      - `seoTitle` string: SEO override for the <title>/og:title tags only. The blog card and listing always show `title`; empty or absent inherits `title`.
      - `date` string
      - `dateDisplay` string
      - `excerpt` string
      - `tags` string[]
      - `author` string
      - `image` string
      - `url` string
      - `visibleAt` string
      - `collections` string[]
      - `custom` object
    - `head` string[], required: Extra `<head>` tags the post carries (SEO and social tags the platform derives are left out).
    - `body` string, required: The post body HTML.
    - `warnings` string[]: Read problems that would otherwise lose data on the next save, for example a malformed collection list that was dropped from `meta`.
    - `urlChangeable` boolean, required: Whether the post's URL can be moved once it is published (the rename route accepts `movePath` for it).

## Other responses

- `400`: `invalid_key`, or the `selector` is not supported (`invalid_selector`).
- `401`: Missing or invalid credentials.
- `403`: The key or token cannot read this site.
- `404`: No such site (`site_not_found`), no such key (`content_not_found`), or no published version (`no_published_version`).

---

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