# POST /v1/projects/:projectId/ingest/website (/docs/api/reference/projects/ingest-website)



<Endpoint method="POST" path="/v1/projects/:projectId/ingest/website" auth="Bearer" scope="ingest:write" phase="1" />

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`](/docs/api/reference/jobs/get-job) for completion; the final `brandContext` lands on the project itself. Most sites finish in under 10 seconds.

<Parameters
  title="Path"
  rows="[
  { name: 'projectId', type: 'string', required: true, description: 'Project ID.' },
]"
/>

<Parameters
  title="Headers"
  rows="[
  { name: 'Idempotency-Key', type: 'string (UUID)', description: 'Replays within 24h.' },
]"
/>

<Parameters
  title="Body"
  rows="[
  { name: 'url', type: 'string', required: true, description: 'Absolute http(s) URL. Must resolve and return HTML.' },
  { name: 'followLinks', type: 'boolean', description: 'If true, the crawler follows in-domain links to enrich the extraction.', default: 'false' },
  { name: 'maxPages', type: 'integer', description: 'Cap on pages fetched when `followLinks` is true. 1–25.', default: '1' },
]"
/>

## Example request [#example-request]

<Tabs items="['curl', 'TypeScript', 'Python']">
  <Tab value="curl">
    ```bash
    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" }'
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    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).
    ```
  </Tab>

  <Tab value="Python">
    ```python
    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.
    ```
  </Tab>
</Tabs>

## Response [#response]

<Response status="202" description="Accepted — queued">
  ```json
  {
    "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`](/docs/api/reference/jobs/get-job)) until `status` is `completed` or `failed`. On success, the resulting `brandContext` is patched onto the project — re-fetch the project to read it.
</Response>

## Errors [#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 [#see-also]

* [`POST /v1/projects/:projectId/ingest/appstore`](/docs/api/reference/projects/ingest-appstore) — ingest App Store / Play Store listing
* [`POST /v1/projects/:projectId/ingest/github`](/docs/api/reference/github/ingest-github) — clone repo + install SDK
* [`PATCH /v1/projects/:projectId`](/docs/api/reference/projects/patch-project) — set brand context directly
