POST /v1/projects/:projectId/ingest/appstore
Scrape App Store and Play Store listings and merge into brand context.
/v1/projects/:projectId/ingest/appstore- Auth
- Bearer
- Scope
- ingest:write
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 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.
projectIdstringrequiredProject ID.
Idempotency-Keystring (UUID)optionalReplays within 24h.
iosBundleIdstringoptionalApple bundle ID (e.g. `com.acmecoffee.ios`). At least one of `iosBundleId` / `androidPackage` / `appStoreUrl` is required.androidPackagestringoptionalAndroid package name (e.g. `com.acmecoffee.android`).appStoreUrlstringoptionalDirect App Store / Play Store URL — used in lieu of explicit bundle IDs.countryCodestringoptionaldefault: usISO 3166-1 alpha-2 storefront code.
Example request
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"
}'const result = await layers.projects.ingestAppStore(
"9cb958b5-11b5-4e30-8675-5d075d52da7c",
{
iosBundleId: "com.acmecoffee.ios",
androidPackage: "com.acmecoffee.android",
},
{ idempotencyKey: crypto.randomUUID() }
);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()),
)Response
{
"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.
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
POST /v1/projects/:projectId/ingest/website— ingest from a marketing URLPOST /v1/projects/:projectId/ingest/github— clone repo + install SDKGET /v1/jobs/:jobId— poll the 202 case