# PATCH /v1/projects/:projectId/ads-content/:id (/docs/api/reference/metrics/patch-ads-content)



<Endpoint method="PATCH" path="/v1/projects/{projectId}/ads-content/{id}" auth="Bearer" scope="ads:write" phase="1" />

Pins a creative as `include` or `exclude`, bypassing the automatic `organicScore >= 4.0` gate. Use `include` to keep a creative in rotation even after its score decays below threshold. Use `exclude` to pull a creative off the eligible list immediately, regardless of how well it is scoring.

This is the only `ads-content` mutation available today. Everything else about the creative — score, performance, promotions — is derived. The `override` field is the single human-writable lever.

<Parameters
  title="Path"
  rows="[
  { name: 'projectId', type: 'string (UUID)', required: true, description: 'Project containing the creative.' },
  { name: 'id', type: 'string (UUID)', required: true, description: 'adsContentId — from GET /ads-content.' },
]"
/>

<Parameters
  title="Body"
  rows="[
  { name: 'override', type: 'string | null', required: true, description: 'Set the override. null clears it and returns to score-based gating.', enum: ['include', 'exclude', null] },
  { name: 'note', type: 'string', description: 'Short context saved alongside the override. Visible in the audit log; max 280 chars.' },
]"
/>

## Example request [#example-request]

<Tabs items="['curl', 'TypeScript', 'Python']">
  <Tab value="curl">
    ```bash
    curl -X PATCH https://api.layers.com/v1/projects/prj_01HX9Y7K8M2P4RSTUV56789AB/ads-content/01HXC9MNPQRSTUVWXYZAB12345 \
      -H "Authorization: Bearer lp_live_01HX9Y6K7EJ4T2_4QZpN..." \
      -H "Content-Type: application/json" \
      -d '{ "override": "exclude", "note": "Legal flagged the background music" }'
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    const res = await fetch(
      `https://api.layers.com/v1/projects/${projectId}/ads-content/${adsContentId}`,
      {
        method: "PATCH",
        headers: {
          Authorization: `Bearer ${apiKey}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ override: "exclude", note: "Legal flagged background music" }),
      },
    );
    const updated = await res.json();
    ```
  </Tab>

  <Tab value="Python">
    ```python
    import httpx

    r = httpx.patch(
        f"https://api.layers.com/v1/projects/{project_id}/ads-content/{ads_content_id}",
        headers={"Authorization": f"Bearer {api_key}"},
        json={"override": "exclude", "note": "Legal flagged background music"},
    )
    updated = r.json()
    ```
  </Tab>
</Tabs>

## Response [#response]

<Response status="200" description="OK — returns the updated ads_content row.">
  ```json
  {
    "adsContentId": "01HXC9MNPQRSTUVWXYZAB12345",
    "sourceType": "content_container",
    "sourceId": "01HXC8A2B3C4D5E6F7G8H9J0K1",
    "platformPostId": null,
    "scoringPool": "generated",
    "organicScore": 3.2,
    "organicPerformance": null,
    "adPerformance": null,
    "scoredAt": "2026-04-18T12:00:00Z",
    "scoringVersion": 3,
    "eligibility": { "isEligible": false, "reason": "excluded by override" },
    "override": "exclude",
    "overrideNote": "Legal flagged background music",
    "overrideSetBy": "lp_live_HHMRNJ2AHYJ6WZJ4 (api_key_id)",
    "overrideSetAt": "2026-04-18T19:22:05Z",
    "activePromotions": []
  }
  ```
</Response>

<Response status="400" description="Invalid override value or empty body.">
  ```json
  {
    "error": {
      "code": "VALIDATION",
      "message": "Invalid patch body.",
      "requestId": "req_..."
    }
  }
  ```
</Response>

<Response status="404" description="ads_content row not found." />

## What override does [#what-override-does]

`override` is consulted before `organicScore` in every place Layers picks creatives to promote:

* `"include"` — eligible regardless of score, pool, or decay. Use this to pin a seasonal hero, a creative legal has cleared, or a winner you want to keep running after its decay curve dips below `4.0`.
* `"exclude"` — ineligible regardless of score. Use this to retire a creative from future ad selection. Ads currently running against it are **not** paused — ad pause decisions are made from live performance, not from override. Pause them in the platform's ad manager.
* `null` — clears the override. Eligibility follows the score again.

## Notes [#notes]

* Idempotent: PATCHing the same override twice is a no-op with respect to downstream selection.
* `:id` is the raw UUID returned in `adsContentId` from the list endpoint — pass it back without any partner prefix.
* `overrideSetBy` stores the API key prefix and the api\_key id that made the change (`"<prefix> (<api_key_id>)"`). Audit who-did-what via [`GET /v1/audit`](/docs/api/reference/audit-log/list) with `subjectType=ads_content`.
* Override does **not** change `organicScore`. Clearing the override restores score-based eligibility as if you had never touched it.
* Excluding a creative on one platform excludes it across all platforms. There is no per-platform override.

## See also [#see-also]

* [`GET /v1/projects/:projectId/ads-content`](/docs/api/reference/metrics/ads-content) — scored creatives and current overrides
* [`GET /v1/projects/:projectId/top-performers`](/docs/api/reference/metrics/top-performers) — ranked list that respects overrides
* [Publish to learn](/docs/api/guides/publish-to-learn) — the feedback loop override plugs into
