# Quickstart (no GitHub repo) (/docs/api/getting-started/quickstart-mobile)



The default [quickstart](/docs/api/getting-started/quickstart) wires up the Layers SDK by cloning a GitHub repo, reading the code, and opening a PR. That path assumes (a) you have a repo, and (b) we can build it — which excludes a lot of real apps: Godot and Unity projects, React Native apps with custom native bridges, enterprise repos behind VPNs, indie apps shipped straight from a laptop, and any team not willing to hand us a token.

For those cases, skip the GitHub step. Point Layers at the **App Store listing** instead. The scraper extracts the same brand context we'd derive from the code — app name, subtitle, screenshots, keywords, review themes — and merges it into the project. You lose the auto-generated SDK PR; you keep everything else.

This page only covers the divergent step. Steps 1, 2, 3, and 6 of the main quickstart work the same way — start there for the whoami + create-project + polling boilerplate. Come back here for the ingest step.

## Prerequisites [#prerequisites]

* The customer's app is live in the App Store, Play Store, or both.
* You have an **App Machina layer** installed on the project. App Store ingest routes to the `aso-audit` workflow, which writes its result into that layer — without it, the call 422s with `missing_app_machina_layer`.
* Steps 1 and 3 of the [main quickstart](/docs/api/getting-started/quickstart) complete — you have `$LAYERS_API_KEY` and a `$PROJECT_ID`.

## The divergent step: App Store ingest [#the-divergent-step-app-store-ingest]

Replace step 4 + 5 of the main quickstart with a single call:

<Tabs items="['curl', 'TypeScript', 'Python']">
  <Tab value="curl">
    ```bash
    curl -X POST "https://api.layers.com/v1/projects/$PROJECT_ID/ingest/appstore" \
      -H "X-Api-Key: $LAYERS_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: $(uuidgen)" \
      -d '{
        "iosBundleId": "com.acmecoffee.ios",
        "androidPackage": "com.acmecoffee.android",
        "countryCode": "us"
      }'
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    import { randomUUID } from "node:crypto";

    const res = await fetch(
      `https://api.layers.com/v1/projects/${projectId}/ingest/appstore`,
      {
        method: "POST",
        headers: {
          "X-Api-Key": process.env.LAYERS_API_KEY!,
          "Content-Type": "application/json",
          "Idempotency-Key": randomUUID(),
        },
        body: JSON.stringify({
          iosBundleId: "com.acmecoffee.ios",
          androidPackage: "com.acmecoffee.android",
          countryCode: "us",
        }),
      },
    );
    const { jobId, locationUrl } = await res.json();
    ```
  </Tab>

  <Tab value="Python">
    ```python
    import os, uuid, requests

    res = requests.post(
        f"https://api.layers.com/v1/projects/{project_id}/ingest/appstore",
        headers={
            "X-Api-Key": os.environ["LAYERS_API_KEY"],
            "Content-Type": "application/json",
            "Idempotency-Key": str(uuid.uuid4()),
        },
        json={
            "iosBundleId": "com.acmecoffee.ios",
            "androidPackage": "com.acmecoffee.android",
            "countryCode": "us",
        },
    )
    envelope = res.json()
    job_id = envelope["jobId"]
    ```
  </Tab>
</Tabs>

Response:

```json
{
  "jobId": "01JS5V8KX5JFK1YQ4G2YZQ4XEP",
  "kind": "appstore_ingest",
  "status": "running",
  "stage": "scraping",
  "projectId": "9cb958b5-11b5-4e30-8675-5d075d52da7c",
  "locationUrl": "/v1/jobs/01JS5V8KX5JFK1YQ4G2YZQ4XEP",
  "startedAt": "2026-04-18T19:25:42.187Z"
}
```

Poll `locationUrl` exactly like the main quickstart's step 6 — same envelope, same terminal states. Most scrapes land in under 5 seconds; expect longer for non-US storefronts or apps with a large review corpus.

## Body fields [#body-fields]

At least one of `iosBundleId`, `androidPackage`, or `appStoreUrl` is required. You can send more than one and we'll scrape each storefront.

| Field            | Type   | Notes                                                                                              |
| ---------------- | ------ | -------------------------------------------------------------------------------------------------- |
| `iosBundleId`    | string | Reverse-DNS iOS bundle (e.g. `com.acmecoffee.ios`).                                                |
| `androidPackage` | string | Android package name (e.g. `com.acmecoffee.android`).                                              |
| `appStoreUrl`    | string | Direct store URL. Use this for Godot/Unity exports where the bundle/package is awkward to look up. |
| `countryCode`    | string | ISO 3166-1 alpha-2. Defaults to `us`.                                                              |

## What comes back [#what-comes-back]

On completion, `result.brandContext` is merged into the project and written to the App Machina layer. You can read the final shape via `GET /v1/projects/:projectId`:

```json
{
  "id": "9cb958b5-11b5-4e30-8675-5d075d52da7c",
  "name": "Acme Coffee",
  "brand": {
    "appName": "Acme Coffee",
    "tagline": "Skip the line. Sip the best.",
    "audience": "coffee-first iOS users, 22–40, urban US",
    "brandVoice": "warm, casual, a little snobby about beans",
    "keywords": ["mobile ordering", "loyalty", "specialty coffee"]
  }
}
```

That `brand` block is the same shape the GitHub ingest produces. Downstream — content generation, influencer narratives, ad creative — consume it the same way. The only thing missing versus the GitHub path is the auto-generated SDK install PR; for Godot/Unity you wire the SDK in manually ([SDK docs](/docs/api/guides/onboard-customer)).

## What to do when the scrape fails [#what-to-do-when-the-scrape-fails]

The scraper has a **5-failure / 7-day circuit breaker per app**. Repeated failures (wrong bundle ID, delisted app, geo-blocked storefront) trip the breaker and subsequent calls return `SCRAPE_FAILED` until the cooldown ends.

Typical failure modes:

* **`VALIDATION` 422 with `reason: "missing_app_machina_layer"`** — install the App Machina layer on the project first; re-run.
* **`SCRAPE_FAILED`** on the job — the bundle/package doesn't resolve on that storefront. Double-check the `countryCode` matches where the app is actually listed.
* **`NOT_FOUND`** on the project — `$PROJECT_ID` doesn't belong to your org.

## Godot / Unity / non-Node-repo specifics [#godot--unity--non-node-repo-specifics]

If the app has no Node-buildable code in the repo, the GitHub ingest path has nothing to analyze and will fail before the PR step. Use the App Store path instead — the scraper doesn't care what engine the app is built with, it only reads the store listing.

For the SDK itself, the [onboarding guide](/docs/api/guides/onboard-customer) has manual install recipes. The short version: register the customer's `iosBundleId` / `androidPackage` via `POST /v1/projects/:id/sdk-apps`, pull `installSpec` off the response, and drop the platform-native bootstrap code into the binary by hand.

## See also [#see-also]

* [Quickstart (GitHub path)](/docs/api/getting-started/quickstart) — shared steps 1, 2, 3, 6
* [`POST /v1/projects/:projectId/ingest/appstore`](/docs/api/reference/projects/ingest-appstore) — full endpoint reference
* [Onboard a customer](/docs/api/guides/onboard-customer) — longer walk-through including manual SDK install
* [Jobs](/docs/api/concepts/jobs) — the polling envelope
