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



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

Kicks off a job that resolves an iOS bundle ID and/or an Android package against the App Store and Play Store, scrapes the listing (title, subtitle, screenshots, keywords, reviews digest), and merges the result into the project's `brandContext`. Use it the moment the user tells you what app they built — it's the fastest path from zero context to Layers being useful.

Returns `202` with a job envelope; poll [`GET /v1/jobs/:jobId`](/docs/api/reference/jobs/get-job) for completion. Most scrapes finish in \~5s; non-US storefronts and apps with large review corpora may take longer.

The store scraper has a 5-failure / 7-day circuit breaker per app. If an app repeatedly fails validation (wrong bundle ID, delisted, geo-blocked), the resulting job will fail with `SCRAPE_FAILED` until the cooldown ends.

<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: 'iosBundleId', type: 'string', description: 'Apple bundle ID (e.g. `com.acmecoffee.ios`). At least one of `iosBundleId` / `androidPackage` / `appStoreUrl` is required.' },
  { name: 'androidPackage', type: 'string', description: 'Android package name (e.g. `com.acmecoffee.android`).' },
  { name: 'appStoreUrl', type: 'string', description: 'Direct App Store / Play Store URL — used in lieu of explicit bundle IDs.' },
  { name: 'countryCode', type: 'string', description: 'ISO 3166-1 alpha-2 storefront code.', default: 'us' },
]"
/>

## 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/appstore \
      -H "Authorization: Bearer lp_live_01HX9Y6K7EJ4T2_4QZpN..." \
      -H "Idempotency-Key: d4e71b62-7f08-4dc9-9d2c-8f7e2b9c4411" \
      -H "Content-Type: application/json" \
      -d '{
        "iosBundleId": "com.acmecoffee.ios",
        "androidPackage": "com.acmecoffee.android"
      }'
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    const result = await layers.projects.ingestAppStore(
      "9cb958b5-11b5-4e30-8675-5d075d52da7c",
      {
        iosBundleId: "com.acmecoffee.ios",
        androidPackage: "com.acmecoffee.android",
      },
      { idempotencyKey: crypto.randomUUID() }
    );
    ```
  </Tab>

  <Tab value="Python">
    ```python
    result = layers.projects.ingest_appstore(
        project_id="9cb958b5-11b5-4e30-8675-5d075d52da7c",
        ios_bundle_id="com.acmecoffee.ios",
        android_package="com.acmecoffee.android",
        idempotency_key=str(uuid.uuid4()),
    )
    ```
  </Tab>
</Tabs>

## Response [#response]

<Response status="202" description="Accepted — queued">
  ```json
  {
    "jobId": "job_01JS5V8KX5JFK1YQ4G2YZQ4XEP",
    "kind": "appstore_ingest",
    "status": "running",
    "stage": "scraping",
    "projectId": "9cb958b5-11b5-4e30-8675-5d075d52da7c",
    "locationUrl": "/v1/jobs/job_01JS5V8KX5JFK1YQ4G2YZQ4XEP",
    "startedAt": "2026-04-18T19:25:42.187Z"
  }
  ```

  Poll `locationUrl` until `status` is `completed` or `failed`. The job result payload (scraped listings + merged `brandContext`) is on the job record itself; the merged `brandContext` is also written back to the project.
</Response>

## Errors [#errors]

| Status | Code              | When                                                                                     |
| ------ | ----------------- | ---------------------------------------------------------------------------------------- |
| 422    | `VALIDATION`      | None of `iosBundleId`, `androidPackage`, or `appStoreUrl` supplied; malformed bundle ID. |
| 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/website`](/docs/api/reference/projects/ingest-website) — ingest from a marketing URL
* [`POST /v1/projects/:projectId/ingest/github`](/docs/api/reference/github/ingest-github) — clone repo + install SDK
* [`GET /v1/jobs/:jobId`](/docs/api/reference/jobs/get-job) — poll the 202 case
