---
title: "Get a site by id or slug."
method: GET
path: "/sites/{idOrSlug}"
tags: ["Sites"]
---

# Get a site by id or slug.

`GET /sites/{idOrSlug}`

## Request

- Base URL: `https://api.stacktr.ee`
- URL: `https://api.stacktr.ee/sites/{idOrSlug}`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://api.stacktr.ee/oauth/authorize`, token URL `https://api.stacktr.ee/oauth/token`, scopes: `sites:write`, `sites:read`)

Same viewer-number gating as the list endpoint: `metrics_locked`, plus `opened` on every plan. Also carries `badged`, the per-page answer to whether this page shows the "Made with Stacktree" footer, which the plan alone does not decide.

## Response `200`

OK

- Site
  - `id` string, required: Stable site identifier.
  - `slug` string, nullable: Public subdomain slug, if the site is public.
  - `visibility` 'unlisted' | 'public' | 'burn', required: Privacy mode.
  - `url` string, uri: Canonical URL to view the site.
  - `created_at` integer, required: Unix epoch seconds.
  - `updated_at` integer: Unix epoch seconds.
  - `expires_at` integer, nullable: Unix epoch seconds; null = no expiry. Clamped to the plan ceiling at write time (Free: 7 days, anonymous: 24 hours), so this can come back sooner than you asked for.
  - `expires_at_iso` string, nullable: The same instant as `expires_at`, RFC 3339 UTC, second granularity (e.g. "2026-09-08T12:00:00Z"). Present so a client can show a deadline without reconstructing it from the epoch.
  - `ttl_seconds` integer, nullable: Seconds from now until the page stops serving. Null when it does not expire; 0 when it is already out of time. Never negative.
  - `expiry_clamped` boolean: True when the lifetime THIS request asked for was longer than the plan allows and `expires_at` is the shortened reality. Always present, false included. False on reads, which ask for no lifetime. Asking for a page that never expires on a capped plan is refused (409 `expiry_clamped`) rather than clamped, unless `accept_clamp` is sent.
  - `expiry_ceiling_hours` integer, nullable: The longest lifetime this plan allows a page, in hours: 24 anonymous, 168 on Free, null on plans with no ceiling and on the paid one-off rails.
  - `expiry_source` 'request' | 'plan_ceiling' | 'plan_default' | 'stored': What decided the date: `request` (the caller named it), `plan_ceiling` (the plan cap did), `plan_default` (the plan default did), or `stored` (this call only read the row).
  - `byte_size` integer: Total content size in bytes.
  - `email_gate` object, nullable
    - `domain` string
    - `required` boolean
  - `metrics_locked` boolean: True when the plan has no viewer numbers (anonymous and Free). The three viewer fields below are then null; there is no number to reveal, blur, or approximate.
  - `opened` boolean: Whether anyone has opened the page. Returned on every plan, including when metrics_locked is true, so "someone read this" survives the lock.
  - `badged` boolean: Whether THIS page carries the "Made with Stacktree" footer as served on its stacktr.ee URL. Returned by GET /sites/{idOrSlug} only. A fact about the page, not about the plan: pages published before 2026-08-03, generated client portals and end-to-end encrypted pages carry no badge even on Free, and a remove_badge entitlement lifts it off one page or a whole account. Never assume it from the plan; anything offering to remove the badge should read this.
  - `view_count` integer, nullable: Raw view count. Null when metrics_locked.
  - `unique_viewers` integer, nullable: Distinct human viewers over the last 30 days. Null when metrics_locked.
  - `last_viewed_at` integer, nullable: Unix epoch seconds of the most recent open. Null when metrics_locked, or when never opened.
  - `reading` object, nullable: Returned by GET /sites. How closely the newest named reader read this page: their visits and reading time, the sender's own previews excluded. Null when that open carried no name, or when metrics_locked. `seconds` is null on a plan without reading detail (Solo).
    - `by` string
    - `others` integer: How many other named readers opened it.
    - `visits` integer
    - `seconds` integer, nullable
  - `client` object, nullable: Client space this page is filed under, or null for a floating page.
    - `slug` string
    - `name` string
  - `client_url` string: Present on publish when the space has a connected hostname and the page got a space_path: the page’s address on the client’s own domain (e.g. https://acme.theiragency.com/june-report). Prefer handing this link to the user.
  - `recipient` object, nullable: Present on publish when the request named who the page is for (`recipient`). `link` is their own link (`?to=` on the page URL): hand that over rather than `url`, and every open through it comes back with their name. On GET /sites: the newest person the page was named for, on every plan (whether they opened it is a viewer number and is not here), or null.
    - `name` string
    - `link` string
    - `added_at` integer: GET /sites only. Unix seconds when they were named.
    - `others` integer: GET /sites only. How many other people the page was named for.
  - `claim_token` string, nullable: Anonymous publishes only. Adopts the page into an account (POST /sites/{idOrSlug}/claim) and, until then, is its keyless update credential. As sensitive as the page.
  - `claim_url` string, uri: Anonymous publishes only. The dashboard claim screen for this page: sign in, and the page moves into that account at the same address.
  - `keep_url` string, uri: Anonymous publishes only. Give this to the person you published for: it opens a page that says when this page stops working and offers both ways to keep it (a free account, or the link by email); opening it keeps nothing until they choose one. It also marks their browser so the page itself shows them a Keep button. It carries the claim token, so it is as sensitive as claim_url: share `url`, never this.
  - `deleted_at` integer, nullable: Unix epoch seconds the page stopped serving; null when it is live. Non-null means the URL is dead for everyone holding it.
  - `delete_reason` string, nullable: Why it stopped serving: `expired`, `owner` (someone deleted it), `abuse` (a takedown), or `merged` (folded into another page; see `merged_into`). null while the page is live. `abuse` and `merged` are never restorable: POST /sites/{idOrSlug}/restore refuses them, so do not offer a restore control for either.
  - `restorable_until` integer, nullable: Unix epoch seconds. A real deadline, not a hint: the hourly cleanup destroys the content at that moment and restore returns 404 from then on.
  - `likely_revision_of` object: On POST /sites only, and only when the page looks like a new version of one this account already has: the same title (changed in the last 24 hours, same client or none) or the same client and client_path (last 30 days). The page was still published, as asked, at a new link; this names the earlier page and the call that would have kept its link (`hint`). Next time, PUT /sites/{id} on it instead of publishing again. When `can_merge` is true, POST /sites/{newId}/merge folds the two together now. Ignore it if this is a separate document.
    - `id` string
    - `url` string, uri
    - `title` string, nullable
    - `matched_on` 'title' | 'client_path'
    - `last_changed_at` integer: Unix epoch seconds the earlier page last changed.
    - `last_changed_at_iso` string, nullable
    - `minutes_ago` integer
    - `client` object
      - `slug` string
      - `name` string
    - `can_merge` boolean: Whether POST /sites/{newId}/merge would fold the earlier page into this one now.
    - `hint` string: The same facts in a sentence, safe to repeat.
  - `merged_into` object, nullable: On GET /sites/{idOrSlug} for a page with delete_reason `merged`: the page that holds its content now, and that its link opens.
    - `id` string
    - `url` string, uri
    - `title` string, nullable

## Other responses

- `404`: Resource not found.

---

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