POST /v1/projects/:projectId/ingest/website
Scrape a marketing URL and patch the project's brand context.
POST
/v1/projects/:projectId/ingest/websitePhase 1stableidempotent
- Auth
- Bearer
- Scope
- ingest:write
Kicks off a job that fetches a URL, extracts brand signal (name, description, tagline, audience, voice, keywords, logo), and writes the merged result back onto the project's brandContext. Call it when you have a marketing site for the user's app but no GitHub repo — or when the site is richer than the repo README.
Returns 202 with a job envelope. Poll GET /v1/jobs/:jobId for completion; the final brandContext lands on the project itself. Most sites finish in under 10 seconds.
Path
projectIdstringrequiredProject ID.
Headers
Idempotency-Keystring (UUID)optionalReplays within 24h.
Body
urlstringrequiredAbsolute http(s) URL. Must resolve and return HTML.followLinksbooleanoptionaldefault: falseIf true, the crawler follows in-domain links to enrich the extraction.maxPagesintegeroptionaldefault: 1Cap on pages fetched when `followLinks` is true. 1–25.
Example request
curl -X POST https://api.layers.com/v1/projects/9cb958b5-11b5-4e30-8675-5d075d52da7c/ingest/website \
-H "Authorization: Bearer lp_live_01HX9Y6K7EJ4T2_4QZpN..." \
-H "Idempotency-Key: a81c6244-4b2f-4d0a-8b5c-9f6de11aaf22" \
-H "Content-Type: application/json" \
-d '{ "url": "https://acmecoffee.com" }'const job = await layers.projects.ingestWebsite(
"9cb958b5-11b5-4e30-8675-5d075d52da7c",
{ url: "https://acmecoffee.com" },
{ idempotencyKey: crypto.randomUUID() }
);
// Returns immediately with `job.jobId`; poll layers.jobs.get(job.jobId).job = layers.projects.ingest_website(
project_id="9cb958b5-11b5-4e30-8675-5d075d52da7c",
url="https://acmecoffee.com",
idempotency_key=str(uuid.uuid4()),
)
# Poll layers.jobs.get(job.job_id) for completion.Response
202Accepted — queued
{
"jobId": "job_01JS5V8KX5JFK1YQ4G2YZQ4XEP",
"kind": "project_ingest_website",
"status": "running",
"stage": "fetching",
"projectId": "9cb958b5-11b5-4e30-8675-5d075d52da7c",
"url": "https://acmecoffee.com",
"locationUrl": "/v1/jobs/job_01JS5V8KX5JFK1YQ4G2YZQ4XEP",
"startedAt": "2026-04-18T19:25:42.187Z"
}Poll the URL in locationUrl (which mirrors GET /v1/jobs/:jobId) until status is completed or failed. On success, the resulting brandContext is patched onto the project — re-fetch the project to read it.
Errors
| Status | Code | When |
|---|---|---|
| 422 | VALIDATION | Missing or malformed url. |
| 401 | UNAUTHENTICATED | Missing or invalid key. |
| 403 | FORBIDDEN_SCOPE | Key lacks ingest:write. |
| 404 | NOT_FOUND | Project does not exist. |
| 429 | RATE_LIMITED | Write budget or external-scrape budget exhausted. |
See also
POST /v1/projects/:projectId/ingest/appstore— ingest App Store / Play Store listingPOST /v1/projects/:projectId/ingest/github— clone repo + install SDKPATCH /v1/projects/:projectId— set brand context directly