Layers
Partner APIAPI referenceGitHub

POST /v1/projects/:id/ingest/github

Clone a repo, analyze it, and open a PR that instruments the codebase with the Layers SDK.

View as Markdown
POST/v1/projects/:id/ingest/github
Phase 1stableidempotent
Auth
Bearer
Scope
ingest:write

Runs the SDK-install workflow end-to-end: clones the repo into an isolated sandbox, detects the target platform (27+ supported — iOS, Android, React Native, Flutter, Expo, Next.js, etc.), generates a tailored patch that wires up the Layers SDK, and opens a PR on a new branch. The PR body includes the inferred brand context that Layers writes back to the project (appName, tagline, audience, brandVoice, keywords, and so on).

This is an async operation. The endpoint returns 202 with a jobId; poll GET /v1/jobs/:jobId for stage transitions and the final result. Stages are: cloninganalyzinggenerating_sdk_patchopening_prfinalizing. Total wall time is usually 90–240 seconds depending on repo size.

Path
  • id
    stringrequired
    Project ID to attach the resulting SDK app to.
Headers
  • Idempotency-Key
    string (UUID)optional
    Replays within 24h. Recommended — ingest is expensive.
Body
  • repoFullName
    stringrequired
    Repo identifier in owner/name form, e.g. acme-coffee/ios-app.
  • branch
    stringoptionaldefault: repository default branch
    Branch to fork from.
  • openPR
    booleanoptionaldefault: true
    If false, the workflow stops after the patch is generated and does not open a PR.
  • platforms
    string[]optional
    Shorthand alias for `sdkConfig.includePlatforms` — restrict instrumentation to specific platforms.
  • sdkConfig
    objectoptional
    Overrides for SDK app name, platforms, and event allowlist. See below.
  • sdkConfig.appName
    stringoptional
    Overrides the inferred SDK app name.
  • sdkConfig.includePlatforms
    string[]optional
    Restrict instrumentation to specific platforms. One of ios, android, web, react-native, flutter, expo.
  • sdkConfig.eventAllowlist
    string[]optional
    If provided, only these event names are forwarded to CAPI.
  • prTitle
    stringoptionaldefault: "chore: instrument with Layers SDK"
    Override the PR title.
  • prBody
    stringoptional
    Additional markdown appended to the PR body.

The body is strict — keys outside the list above are rejected with 422 VALIDATION.

Example request

curl https://api.layers.com/v1/projects/9cb958b5-11b5-4e30-8675-5d075d52da7c/ingest/github \
  -H "Authorization: Bearer lp_live_01HX9Y6K7EJ4T2_4QZpN..." \
  -H "Idempotency-Key: 9b2e4d1c-3f2a-4d66-bf31-1a2b3c4d5e60" \
  -H "Content-Type: application/json" \
  -d '{
    "repoFullName": "acme-coffee/ios-app",
    "branch": "main",
    "sdkConfig": {
      "includePlatforms": ["ios"],
      "eventAllowlist": ["app_open", "purchase_success", "subscribe_start"]
    }
  }'
const { jobId } = await layers.ingest.github(
  "9cb958b5-11b5-4e30-8675-5d075d52da7c",
  {
    repoFullName: "acme-coffee/ios-app",
    branch: "main",
    sdkConfig: {
      includePlatforms: ["ios"],
      eventAllowlist: ["app_open", "purchase_success", "subscribe_start"],
    },
  },
  { idempotencyKey: crypto.randomUUID() }
);

const result = await layers.jobs.waitForCompletion(jobId);
console.log(result.result.prUrl);
response = layers.ingest.github(
    project_id="9cb958b5-11b5-4e30-8675-5d075d52da7c",
    repo_full_name="acme-coffee/ios-app",
    branch="main",
    sdk_config={
        "includePlatforms": ["ios"],
        "eventAllowlist": ["app_open", "purchase_success", "subscribe_start"],
    },
    idempotency_key=str(uuid.uuid4()),
)

result = layers.jobs.wait_for_completion(response["jobId"])
print(result["result"]["prUrl"])

Response

202Accepted — ingest started
{
  "jobId": "01JS5V8KX5JFK1YQ4G2YZQ4XEP",
  "kind": "project_ingest_github",
  "status": "running",
  "stage": "cloning",
  "projectId": "9cb958b5-11b5-4e30-8675-5d075d52da7c",
  "repoFullName": "acme-coffee/ios-app",
  "locationUrl": "/v1/jobs/01JS5V8KX5JFK1YQ4G2YZQ4XEP",
  "startedAt": "2026-04-18T19:20:11.243Z"
}

Terminal job payload once GET /v1/jobs/:jobId reports "status": "completed":

{
  "jobId": "01JS5V8KX5JFK1YQ4G2YZQ4XEP",
  "kind": "project_ingest_github",
  "status": "completed",
  "finishedAt": "2026-04-18T19:22:58Z",
  "result": {
    "prUrl": "https://github.com/acme-coffee/ios-app/pull/482",
    "prNumber": 482,
    "branch": "layers/install-sdk",
    "sdkAppId": "app_a1b2c3d4e5f6",
    "brandContext": {
      "appName": "Acme Coffee",
      "appDescription": "Pre-order your neighborhood coffee shop's daily brew.",
      "tagline": "Skip the line, keep the ritual.",
      "audience": "Urban professionals, 25–45",
      "icp": "Mobile-first coffee loyalists",
      "brandVoice": "Friendly, unfussy, a little wry",
      "keywords": ["coffee", "order ahead", "mobile payments"],
      "primaryLanguage": "en",
      "logoUrl": "https://media.layers.com/.../logo.png"
    },
    "contextPatchedFields": ["appName", "appDescription", "tagline", "audience", "keywords", "brandVoice"]
  }
}

Errors

The initial 202 can fail with:

StatusCodeWhen
422VALIDATION:id is not a UUID, body shape invalid, or GitHub installation cannot access repoFullName (repo not granted to the App).
401UNAUTHENTICATEDMissing or invalid key.
403FORBIDDEN_SCOPEKey lacks ingest:write.
404NOT_FOUNDProject does not exist, or no GitHub installation is registered.
409CONFLICTIdempotency replay with a different body.
429RATE_LIMITEDLong-running-starts budget exhausted.

Terminal job failures surface as status: "failed" with error.codeCREDENTIAL_INVALID, PLATFORM_ERROR, CIRCUIT_OPEN, INTERNAL. error.data.platformCode + error.data.platformMessage carry GitHub's raw error when applicable.

See also

On this page