POST /v1/projects/:projectId/sdk-apps
Provision an SDK app for a project. Returns the ingest endpoint and API key once.
/v1/projects/:projectId/sdk-apps- Auth
- Bearer
- Scope
- projects:write
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.
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.
When the GitHub ingest flow POST /v1/projects/:id/ingest/github completes, it already provisioned an SDK app for you — use this endpoint only when the user is installing the SDK by hand, via a second platform, or when you need a new key.
projectIdstringrequiredProject ID.
Idempotency-Keystring (UUID)optionalReplays within 24h.
appIdstringoptionalPartner-created SDK app ID. If omitted, the server generates one.namestringrequiredHuman-readable app name, 1–128 chars.platformstringrequiredTarget platform.One of:ios,android,web,react-native,flutter,expobundleIdstringoptionaliOS bundle identifier, required when platform is ios.androidPackagestringoptionalAndroid package name, required when platform is android.webDomainstringoptionalApex domain for web installs, required when platform is web.
Example request
curl https://api.layers.com/v1/projects/9cb958b5-11b5-4e30-8675-5d075d52da7c/sdk-apps \
-H "Authorization: Bearer lp_live_01HX9Y6K7EJ4T2_4QZpN..." \
-H "Idempotency-Key: 2f0e1c88-4b1d-4ac1-bc0a-5e9f6d8a7b10" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Coffee iOS",
"platform": "ios",
"bundleId": "com.acmecoffee.ios"
}'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.app = layers.sdk_apps.create(
project_id="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.Response
{
"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.
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.
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 | CONFLICT | SDK app with the supplied appId or (platform, bundleId/androidPackage/webDomain) already exists. |
| 429 | RATE_LIMITED | Write budget exhausted. |
See also
GET /v1/projects/:projectId/sdk-apps/:appId/install-spec— deterministic install snippetPATCH /v1/projects/:projectId/sdk-apps/:appId— rotate key, update CAPI- SDK events — read what the SDK sends