Layers
Partner APIAPI referenceContent

GET /v1/projects/:projectId/content/hooks

Fetch a brand-adapted bank of hook strings ready to pass into generate-content.

View as Markdown
GET/v1/projects/:projectId/content/hooks
Phase 1stable
Auth
Bearer
Scope
content:read

Returns a fresh bank of short hook strings adapted to the project's brand voice and language. Present these to your end-user, let them pick one, and pass the chosen string as hook on POST /v1/projects/:projectId/content. The string is consumed verbatim — the line-break marker (the two-character escape \n) and any emoji are preserved.

Setup/payoff breaks are encoded as the literal two-character sequence \n (backslash + n), not a real newline (0x0a). If you display the string directly without replacing \n you'll see the escape on screen. Replace before rendering:

hook.replace(/\\n/g, "\n")

Same brand-voice resolution and language resolution as the in-app hooks picker — Layers' copy agent reads the project's brandVoice + primaryLanguage (or the wired influencer's overrides) and writes a bank to those constraints. Fresh-on-every-call; re-fetch to show the user a different bank.

Path parameters

  • projectId
    string (uuid)required
    The project to generate hooks for.

Preconditions

The project must have app_name and app_description populated — both anchor the brand-adapted prompt. If either is empty the endpoint returns 422 VALIDATION with details.missingFields[] so you know exactly which PATCH /v1/projects/:id field to populate (or which ingest endpoint to run).

Voice and language resolution

Two-tier — same as the in-app picker:

  1. Influencer wired to the project's first Social Content layer (config.customInfluencerId). When present, that influencer's brand_voice and primary_language win.
  2. Project defaults otherwise — projects.brand_voice, projects.primary_language.

Hard fallbacks (authentic voice, en language) protect against legacy null rows.

Request

terminal
curl https://api.layers.com/v1/projects/{projectId}/content/hooks \
  -H "Authorization: Bearer $LAYERS_API_KEY"
list-hooks.ts
const res = await fetch(
  `https://api.layers.com/v1/projects/${projectId}/content/hooks`,
  { headers: { 'Authorization': `Bearer ${process.env.LAYERS_API_KEY}` } },
);
const { hooks } = await res.json();
list_hooks.py
import os, httpx

r = httpx.get(
    f"https://api.layers.com/v1/projects/{project_id}/content/hooks",
    headers={"Authorization": f"Bearer {os.environ['LAYERS_API_KEY']}"},
)
hooks = r.json()["hooks"]

Responses

200Bank of hook strings.
{
  "hooks": [
    "wait for it...\\nthis simple habit changed everything about my mindset 🧠",
    "POV: you finally stopped doom-scrolling and started doing this instead",
    "the 5-minute morning routine I wish I started 10 years ago"
  ]
}

Each string is ready to pass verbatim as hook on POST /v1/projects/:projectId/content. The setup/payoff break is the literal two-character escape \n (shown above as \\n in JSON-escaped form) — convert it to a real newline before rendering. Emoji round-trip exactly as returned.

422Project missing brand context required for hook generation.
{
  "error": {
    "code": "VALIDATION",
    "message": "Project needs app_name and app_description before hooks can be generated.",
    "requestId": "req_...",
    "details": {
      "missingFields": ["app_description"],
      "remedy": "Run POST /v1/projects/:id/ingest/{website,appstore,github} or PATCH /v1/projects/:id with the missing fields."
    }
  }
}

Notes

Hooks are not persisted on the project — every call returns a fresh bank. The picker is stateless: there is no "previously shown hooks" history, no de-dup across calls, no list endpoint for past banks. If you want to remember which hook a user chose, save it on your end before passing it to generate.

  • No filters or pagination. A bank ships per call (20+ strings). Re-fetch for a new bank.
  • Free. No credit cost; only content:read scope.

Errors

CodeWhen
VALIDATIONProject missing app_name and/or app_description.
NOT_FOUNDProject id not in this org.
FORBIDDEN_SCOPEKey lacks content:read.
INTERNALHook agent failed mid-run. Safe to retry.

See also

On this page