POST /v1/projects/:projectId/keywords/refresh
Kick off a refresh of the project's curated TikTok hashtag bank.
/v1/projects/:projectId/keywords/refresh- Auth
- Bearer
- Scope
- projects:write
Kicks off Layers' keyword research agent against the project's current appDescription and upserts the result into the project's keyword bank when it lands. The route is async — it returns a job envelope immediately; the agent runs in the background for 4 – 5 minutes end-to-end.
Partners typically fire this fire-and-forget and poll GET /v1/projects/:projectId/keywords until refreshedAt advances (or hashtags.length > 0 on first refresh). The job envelope is there if you want stage-level visibility.
When to call
Most partners never need to call this endpoint directly — Layers auto-triggers keyword research from POST /v1/projects (when appDescription is supplied) and from PATCH /v1/projects/:id (when appDescription changes). The auto-trigger is fire-and-forget; observe the result via GET /v1/projects/:projectId/keywords.
Reach for this endpoint when you want:
- A jobId for stage-level observability. The auto-trigger doesn't surface a job envelope; this endpoint does.
- A manual re-run if the auto-trigger failed (rare —
GET /keywordsreturnsrefreshedAt: nulland an empty bank when it never landed). - A forced refresh that doesn't depend on
appDescriptionchanging.
Send an Idempotency-Key header so a retry on connection error replays the cached job envelope instead of starting a second run.
Precondition
projects.app_description must be set. Without it, the route returns 422 VALIDATION with missingFields: ["appDescription"]. Set it on the project first via POST /v1/projects or PATCH /v1/projects/:id.
Body
Empty body. The handler reads appDescription from the project row.
curl -X POST https://api.layers.com/v1/projects/$PROJECT_ID/keywords/refresh \
-H "Authorization: Bearer $LAYERS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'Response
{
"jobId": "job_01HZX...",
"kind": "project_keywords_refresh",
"status": "running",
"projectId": "prj_01HX...",
"startedAt": "2026-05-11T19:02:11.959Z",
"locationUrl": "/v1/jobs/job_01HZX..."
}Errors
| Status | Code | When |
|---|---|---|
| 422 | VALIDATION | Project has no appDescription. Response carries missingFields + a remedy hint. |
| 401 | UNAUTHENTICATED | Missing or invalid key. |
| 403 | FORBIDDEN_SCOPE | Key lacks projects:write. |
| 404 | NOT_FOUND | Project not in this org. |
Job-level failures (the agent failed mid-run) surface on GET /v1/jobs/:jobId as status: "failed" with a structured error. Safe to retry with the same Idempotency-Key.
Notes
- Expected end-to-end latency is 4 – 5 minutes. The agent runs an iterative keyword-expansion + scoring loop and consults third-party TikTok data for popularity signals.
- The agent filters aggressively — banks of 0 hashtags happen when the project's
appDescriptionis too vague or off-domain. Tighten the description and call refresh again. - Curated hashtags meet score, view-count, and post-count floors. Anything below is dropped before the bank lands.
See also
- List keywords
- Source recommendations
- Concepts → Jobs — how to poll the job envelope