Layers
Partner APIAPI referenceProjects

POST /v1/projects/:projectId/ingest/appstore

Scrape App Store and Play Store listings and merge into brand context.

View as Markdown
POST/v1/projects/:projectId/ingest/appstore
Phase 1stableidempotent
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.

Path
  • projectId
    stringrequired
    Project ID.
Headers
  • Idempotency-Key
    string (UUID)optional
    Replays within 24h.
Body
  • iosBundleId
    stringoptional
    Apple bundle ID (e.g. `com.acmecoffee.ios`). At least one of `iosBundleId` / `androidPackage` / `appStoreUrl` is required.
  • androidPackage
    stringoptional
    Android package name (e.g. `com.acmecoffee.android`).
  • appStoreUrl
    stringoptional
    Direct App Store / Play Store URL — used in lieu of explicit bundle IDs.
  • countryCode
    stringoptionaldefault: us
    ISO 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

202Accepted — queued
{
  "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

StatusCodeWhen
422VALIDATIONNone of iosBundleId, androidPackage, or appStoreUrl supplied; malformed bundle ID.
401UNAUTHENTICATEDMissing or invalid key.
403FORBIDDEN_SCOPEKey lacks ingest:write.
404NOT_FOUNDProject does not exist.
429RATE_LIMITEDWrite budget or external-scrape budget exhausted.

See also

On this page