Layers
Partner APIGetting started

Quickstart (no repo, no store listing)

The hobby-project path — manual SDK drop-in, one sdk-apps call, no ingest. Under five minutes.

View as Markdown

The GitHub quickstart needs a repo. The mobile / App Store quickstart needs a store listing. If you have neither — a local prototype, a weekend build, a Godot side project, an internal tool, anything where you're not shipping to a store yet and don't want to point Layers at a codebase — skip both and wire the SDK in by hand.

You lose the auto-derived brand context (you'll fill that in yourself on the project). You keep everything else: events flow into the Layers pipeline, your app_id is valid against every downstream endpoint, and you can upgrade to the ingest paths later without rotating anything.

This page uses steps 1, 2, 3, and 6 from the main quickstart verbatim — start there for whoami, project creation, and job polling. Come back here for the ingest-less SDK wiring.

Prerequisites

  • Steps 1–3 of the main quickstart complete. You have $LAYERS_API_KEY and $PROJECT_ID.
  • You are OK setting the brand context by hand later via PATCH /v1/projects/:id (we can't derive it without code or a listing).

1. Register the SDK app — no bundleId, no ingest

POST /v1/projects/:projectId/sdk-apps without bundleId registers a bare SDK app tied to your project. No scrape, no clone, no workflow — just a row that gives you an app_id you can drop into the SDK.

curl -X POST "https://api.layers.com/v1/projects/$PROJECT_ID/sdk-apps" \
  -H "X-Api-Key: $LAYERS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "platform": "web",
    "displayName": "Acme Prototype"
  }'
import { randomUUID } from "node:crypto";

const res = await fetch(
  `https://api.layers.com/v1/projects/${projectId}/sdk-apps`,
  {
    method: "POST",
    headers: {
      "X-Api-Key": process.env.LAYERS_API_KEY!,
      "Content-Type": "application/json",
      "Idempotency-Key": randomUUID(),
    },
    body: JSON.stringify({
      platform: "web",
      displayName: "Acme Prototype",
    }),
  },
);
const { app_id } = await res.json();
import os, uuid, requests

res = requests.post(
    f"https://api.layers.com/v1/projects/{project_id}/sdk-apps",
    headers={
        "X-Api-Key": os.environ["LAYERS_API_KEY"],
        "Content-Type": "application/json",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "platform": "web",
        "displayName": "Acme Prototype",
    },
)
app = res.json()
app_id = app["app_id"]

Response (201 Created):

{
  "app_id": "app_7ffb9410eb0eb848264f8a",
  "platform": "web",
  "displayName": "Acme Prototype",
  "projectId": "254a4ce1-f4ca-42b1-9e36-17ca45ef3d39",
  "createdAt": "2026-04-20T12:00:00.000Z"
}

Save app_id — every SDK event carries it.

2. Fire a test event

Confirm the pipeline works. POST /v1/sdk-events accepts one or many events; we use one here.

curl -X POST https://in.layers.com/l/events \
  -H "Content-Type: application/json" \
  -d '{
    "app_id": "'"$APP_ID"'",
    "events": [{
      "event": "layers_test_event",
      "user_id": "demo-user-1",
      "timestamp": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'"
    }]
  }'
await fetch("https://in.layers.com/l/events", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    app_id,
    events: [
      {
        event: "layers_test_event",
        user_id: "demo-user-1",
        timestamp: new Date().toISOString(),
      },
    ],
  }),
});
import datetime, requests

requests.post(
    "https://in.layers.com/l/events",
    json={
        "app_id": app_id,
        "events": [
            {
                "event": "layers_test_event",
                "user_id": "demo-user-1",
                "timestamp": datetime.datetime.utcnow().isoformat() + "Z",
            }
        ],
    },
)

The ingest endpoint returns 202 before the event hits storage — events are batched asynchronously. Confirm arrival via GET /v1/projects/:projectId/events/users/demo-user-1 a minute later.

3. Fill in brand context by hand

Without an ingest to derive it, set the brand block on the project yourself. Downstream generation (content, influencer narratives, ad creative) reads from this block exactly like the ingest paths populate it.

curl -X PATCH "https://api.layers.com/v1/projects/$PROJECT_ID" \
  -H "X-Api-Key: $LAYERS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "brand": {
      "appName": "Acme Prototype",
      "tagline": "A weekend experiment.",
      "audience": "hobby developers, curious tinkerers",
      "brandVoice": "casual, self-aware, concise",
      "keywords": ["prototype", "weekend build"]
    }
  }'

That's it. You have a project, an SDK app, a live event, and a brand block. You can upgrade to the GitHub or App Store ingest later without rotating the app_id — the ingest merges into the existing project.

See also

On this page