# POST /v1/projects/:projectId/sdk-apps (/docs/api/reference/sdk-apps/create-sdk-app)



<Endpoint method="POST" path="/v1/projects/:projectId/sdk-apps" auth="Bearer" scope="projects:write" phase="1" />

Creates an SDK app and creates its ingest API key. An SDK app is the binding between a platform build (iOS bundle, Android package, web domain) and the Layers event pipeline - once installed, the client SDK sends events to `in.layers.com/l/events` authenticated with the key returned here.

<Callout type="error">
  The API key in the response is **shown once**. Store it immediately - Layers does not display it again. Rotation is available via [`PATCH /v1/projects/:projectId/sdk-apps/:appId`](/docs/api/reference/sdk-apps/patch-sdk-app).
</Callout>

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

<Parameters
  title="Headers"
  rows="[
  { name: 'Idempotency-Key', type: 'string (UUID)', required: true, description: 'See [Idempotency](/docs/api/operational/idempotency). Recommended on every POST.' },
]"
/>

<Parameters
  title="Body"
  rows="[
  { name: 'name', type: 'string', required: true, description: 'Human-readable app name, 1–128 chars.' },
  { name: 'platform', type: 'string', required: true, description: 'Target platform.', enum: ['ios', 'android', 'web', 'react-native', 'flutter', 'expo', 'nextjs', 'vite', 'react-router'] },
  { name: 'bundleId', type: 'string', description: 'iOS bundle identifier, required when platform is ios.' },
  { name: 'androidPackage', type: 'string', description: 'Android package name, required when platform is android.' },
  { name: 'webDomain', type: 'string', description: 'Apex domain for web / Next.js / Vite / React Router installs, required when platform is one of those.' },
]"
/>

## Example request [#example-request]

<Tabs items="['curl', 'TypeScript', 'Python']">
  <Tab value="curl">
    ```bash
    curl https://api.layers.com/v1/projects/prj_9cb958b5-11b5-4e30-8675-5d075d52da7c/sdk-apps \
      -H "Authorization: Bearer lp_..." \
      -H "Idempotency-Key: 2f0e1c88-4b1d-4ac1-bc0a-5e9f6d8a7b10" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Acme Coffee iOS",
        "platform": "ios",
        "bundleId": "com.acmecoffee.ios"
      }'
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    const app = await layers.sdkApps.create(
      "9cb958b5-11b5-4e30-8675-5d075d52da7c",
      {
        name: "Acme Coffee iOS",
        platform: "ios",
        bundleId: "com.acmecoffee.ios",
      },
      { idempotencyKey: crypto.randomUUID() }
    );
    // Store app.apiKey now - it won't be shown again.
    ```
  </Tab>

  <Tab value="Python">
    ```python
    app = layers.sdk_apps.create(
        project_id="prj_9cb958b5-11b5-4e30-8675-5d075d52da7c",
        name="Acme Coffee iOS",
        platform="ios",
        bundle_id="com.acmecoffee.ios",
        idempotency_key=str(uuid.uuid4()),
    )
    # Store app["apiKey"] now - it won't be shown again.
    ```
  </Tab>
</Tabs>

## Response [#response]

<Response status="201" description="Created">
  ```json
  {
    "appId": "app_8ffb9410eb0eb848264f8a65",
    "name": "Acme Coffee iOS",
    "platform": "ios",
    "bundleId": "com.acmecoffee.ios",
    "androidPackage": null,
    "webDomain": null,
    "ingestEndpoint": "https://in.layers.com/l/events",
    "capi": {},
    "createdAt": "2026-04-18T19:25:22Z",
    "lastRotatedAt": "2026-04-18T19:25:22Z",
    "lastEventAt": null,
    "apiKey": "sk_app_8eRq...k2P"
  }
  ```

  `capi` is an empty object until you enable per-platform forwarding via `PATCH`. `lastRotatedAt` is set to `createdAt` on first creation.
</Response>

<Callout type="warn">
  `apiKey` is returned only on creation and rotation. If you lose it, rotate via `PATCH` to create a new one - the previous key is invalidated immediately.
</Callout>

## Errors [#errors]

| Status | Code                   | When                                                                                                                                             |
| ------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| 422    | `VALIDATION`           | `:projectId` is not a UUID, `platform` unknown, or missing platform-specific identifier (`bundleId`, `androidPackage`, `webDomain`).             |
| 401    | `UNAUTHENTICATED`      | Missing or invalid key.                                                                                                                          |
| 403    | `FORBIDDEN_SCOPE`      | Key lacks `projects:write`.                                                                                                                      |
| 404    | `NOT_FOUND`            | Project does not exist in the key's organization.                                                                                                |
| 409    | `IDEMPOTENCY_CONFLICT` | Same `Idempotency-Key` replayed with a different body.                                                                                           |
| 409    | `CONFLICT`             | SDK app with the supplied `(platform, bundleId/androidPackage/webDomain)` already exists.                                                        |
| 422    | `VALIDATION_FAILED`    | Body includes `appId` (resource ids are server-minted — see [Idempotency](/docs/api/operational/idempotency#resource-ids-are-server-generated)). |
| 429    | `RATE_LIMITED`         | Write budget exhausted.                                                                                                                          |

## See also [#see-also]

* [`GET /v1/projects/:projectId/sdk-apps/:appId/install-spec`](/docs/api/reference/sdk-apps/install-spec) - deterministic install snippet
* [`PATCH /v1/projects/:projectId/sdk-apps/:appId`](/docs/api/reference/sdk-apps/patch-sdk-app) - rotate key, update CAPI
* [SDK events](/docs/api/reference/telemetry/events) - read what the SDK sends
