# Configure auto-pilot engagement (/docs/api/guides/configure-auto-pilot-engagement)



## What you'll build [#what-youll-build]

A running auto-pilot for the comments under a customer's posts: a first-comment that drops at publish, and auto-replies on incoming comments. Both are tuned to the project's brand voice, and both pass through a moderation gate before anything goes out.

This is a configuration flow — you read the current engagement config, patch the fields you want, and the running cadence picks it up on the next post. There's no separate job or workflow call.

<Callout type="warn">
  Only the v1 engagement template shape is exposed today. Sending v2 fields like `replies.tone`, `replies.maxPerPostPerHour`, or `firstComment.strategy` returns `403 FORBIDDEN_FENCE`. Those are planned. The template itself supports them today; the API doesn't widen until then.
</Callout>

<Steps>
  <Step>
    ## Read the current engagement config [#read-the-current-engagement-config]

    Every project has a Social Engagement layer. Calling GET fetches the v1 config shape — any v2 fields currently set in the database are stripped from the response.

    <Endpoint method="GET" path="/v1/projects/:projectId/engagement" scope="engagement:write" phase="1" />

    ```bash
    curl "https://api.layers.com/v1/projects/$PROJECT_ID/engagement" \
      -H "Authorization: Bearer $LAYERS_API_KEY"
    ```

    <Response status="200">
      ```json
      {
        "enabled": false,
        "firstComment": {
          "targets": [],
          "commentTemplate": ""
        },
        "replyToComments": {
          "targets": [],
          "autoReplyDelay": "PT3M"
        }
      }
      ```
    </Response>

    Defaults are empty and `enabled: false` — nothing ships until you turn it on.
  </Step>

  <Step>
    ## Enable first-comments [#enable-first-comments]

    First-comments drop automatically within a few seconds of publish. Typical use: call-to-action that didn't fit the caption, app link, "DM for the recipe", etc.

    <Endpoint method="PATCH" path="/v1/projects/:projectId/engagement" scope="engagement:write" phase="1" />

    <Tabs items="['curl', 'TypeScript', 'Python']">
      <Tab value="curl">
        ```bash
        curl -X PATCH "https://api.layers.com/v1/projects/$PROJECT_ID/engagement" \
          -H "Authorization: Bearer $LAYERS_API_KEY" \
          -H "Content-Type: application/json" \
          -d '{
            "enabled": true,
            "firstComment": {
              "targets": ["tiktok", "instagram"],
              "commentTemplate": "Grab the app — link in bio. First order is on us ☕️"
            }
          }'
        ```
      </Tab>

      <Tab value="TypeScript">
        ```ts
        const res = await fetch(
          `https://api.layers.com/v1/projects/${projectId}/engagement`,
          {
            method: "PATCH",
            headers: {
              Authorization: `Bearer ${process.env.LAYERS_API_KEY}`,
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              enabled: true,
              firstComment: {
                targets: ["tiktok", "instagram"],
                commentTemplate: "Grab the app — link in bio. First order is on us ☕️",
              },
            }),
          },
        );
        ```
      </Tab>

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

        requests.patch(
            f"https://api.layers.com/v1/projects/{project_id}/engagement",
            headers={
                "Authorization": f"Bearer {os.environ['LAYERS_API_KEY']}",
                "Content-Type": "application/json",
            },
            json={
                "enabled": True,
                "firstComment": {
                    "targets": ["tiktok", "instagram"],
                    "commentTemplate": "Grab the app — link in bio. First order is on us ☕️",
                },
            },
        )
        ```
      </Tab>
    </Tabs>

    `commentTemplate` is a literal string — no interpolation today. The same template drops under every post on every target platform. If you need per-post customization, pass `firstCommentOverride` on the schedule call instead (see [Schedule](/docs/api/guides/onboard-customer#schedule-the-first-post)).

    **If you see `VALIDATION` with `data.field: "targets"`**, you passed a platform the project doesn't have a distribution layer for. Run `GET /v1/projects/:id` and check `layers[]` — only platforms with a distribution layer can be engagement targets.
  </Step>

  <Step>
    ## Enable auto-replies [#enable-auto-replies]

    Auto-replies respond to incoming comments on published posts, passing every reply through a moderation gate before it goes live.

    ```bash
    curl -X PATCH "https://api.layers.com/v1/projects/$PROJECT_ID/engagement" \
      -H "Authorization: Bearer $LAYERS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "replyToComments": {
          "targets": ["tiktok", "instagram"],
          "autoReplyDelay": "PT5M"
        }
      }'
    ```

    `autoReplyDelay` is an ISO-8601 duration (`PT30S`, `PT5M`, `PT1H`) — short enough to feel responsive, long enough that replies don't stack up on the post's top shelf. `PT3M` is the default and works for most accounts.

    Replies are LLM-generated, tuned to the project's `brandContext.brandVoice`, and filtered by a safety layer before posting. Nothing ships that fails moderation — the comment is silently skipped and logged for review. Escalating negative-sentiment comments to human review is planned.
  </Step>

  <Step>
    ## Turn it off [#turn-it-off]

    Set `enabled: false` to stop everything. First-comments drop, auto-replies drop, in-flight replies cancel.

    ```bash
    curl -X PATCH "https://api.layers.com/v1/projects/$PROJECT_ID/engagement" \
      -H "Authorization: Bearer $LAYERS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "enabled": false }'
    ```

    Use the master switch rather than emptying the `targets` arrays — it's faster to restore, and it keeps the config intact so you can re-enable without re-patching everything.
  </Step>
</Steps>

## What you can't do yet [#what-you-cant-do-yet]

The planned v2 template widens the shape with these fields:

* `firstComment.strategy` — pick between `literal` (today's behavior) and `generated` (LLM-written per post from brand voice + caption).
* `replies.tone` — override the project's default brand voice for replies specifically.
* `replies.maxPerPostPerHour` — rate cap for auto-replies on a single post.
* `replies.ignoreCommentsMatching` — regex allowlist of comment shapes to skip.
* `replies.escalateNegativeSentiment` — route negative-sentiment comments to a human review queue instead of auto-replying.

If you try to PATCH with any of these today, you get `403 FORBIDDEN_FENCE` and the body names the offending field. Don't work around it by sending them through a different path — the database layer enforces the fence too.

## What's next [#whats-next]

<Cards>
  <Card title="Onboard a customer" href="/docs/api/guides/onboard-customer" description="Connect social accounts and ship the first post before turning on engagement." />

  <Card title="Publish-to-learn loop" href="/docs/api/guides/publish-to-learn" description="Measure how engagement is affecting reach." />
</Cards>
