---
title: "Validate a Line Item"
method: POST
path: "/context/campaign-plans/{slug}/line-items/{lineItemId}/validate"
tags: ["Line Items"]
---

# Validate a Line Item

`POST /context/campaign-plans/{slug}/line-items/{lineItemId}/validate`

## Request

- Base URL: `https://api.adcrunch.dev`
- URL: `https://api.adcrunch.dev/context/campaign-plans/{slug}/line-items/{lineItemId}/validate`
- Auth: HTTP bearer

Validates one Line Item. Use this call when you want to validate one row, and not to approve the whole plan.

Validation is the gate for execution. It changes nothing on any provider. To execute the line, you also need the `mutation:write` permission.

This call checks nothing about the content of the line. An incomplete line validates, and execution makes its own checks. Read the readiness of the line to see them.

A call on a line that is already validated succeeds and changes nothing. The Campaign Plan does not move: one validated line does not approve the Campaign Plan.

## Path parameters

- `lineItemId` string, required
- `slug` string, required

## Request body

- object
  - `base_revision` integer, required: The revision last read via GET. This is what makes it a validation **of the content that was read**.

## Response `200`

The Line Item, now `validated`. Validating an already-validated line is a no-op rather than an error, and the `revision` stays where it was. `executions` lists what the line created, the same as a read of its Campaign Plan.

- object
  - `advertiserId` string, nullable, required
  - `ageMax` number, nullable, required
  - `ageMin` number, nullable, required: Each bound is independently optional, so "35+" is expressible.
  - `budget` number, nullable, required: Whole units of the **plan’s** currency.
  - `campaignPlanId` string, required
  - `channel` 'meta' | 'tiktok' | 'snapchat' | 'google_search' | 'google_pmax' | 'google_display' | 'youtube' | 'programmatic_display' | 'linkedin' | 'x', required: The planner’s word for what is being bought, not a provider name. A plan may name a channel we cannot execute.
  - `countries` string[], required
  - `createdAt` number, required: Milliseconds since the Unix epoch, UTC.
  - `createdBy` string, required
  - `effectiveEndDate` string, nullable, required: The line’s own bound where it has one, else the plan’s.
  - `effectiveStartDate` string, nullable, required: A calendar date, `YYYY-MM-DD`.
  - `endDate` string, nullable, required: A calendar date, `YYYY-MM-DD`.
  - `executable` boolean, required: Whether this channel could be executed at all today. Derived from our capabilities, never a promise that it will be.
  - `executions` object[], required: What this line has spawned. Never a claim that it is *finished* — nobody declared how many objects a line should produce.
    - `advertiserId` string, required
    - `createdAt` number, required: Milliseconds since the Unix epoch, UTC.
    - `entityId` string, required: The provider’s own id, unprefixed, so it joins to `entities`.
    - `entityType` string, required: The provider’s word for the level — `campaign`, `adset`, `ad`.
    - `id` string, required
    - `mutationWorkflowId` string, nullable, required: The durable execution that performed the write, when known.
    - `provider` string, required
  - `gender` 'all' | 'men' | 'women', nullable, required
  - `id` string, required
  - `objective` 'awareness' | 'engagement' | 'leads' | 'sales' | 'traffic' | 'app_promotion', required
  - `personaId` string, nullable, required: Explains the targeting; never supplies its values.
  - `provider` string, nullable, required: Who sells this channel, or null when nobody we integrate with does.
  - `revision` number, required: Increments on every write. Send it back as `base_revision` to edit; a stale one answers 409.
  - `startDate` string, nullable, required: A calendar date, `YYYY-MM-DD`.
  - `status` 'draft' | 'validated', required: `validated` is the gate on execution.
  - `unit` 'total' | 'daily', required: How `budget` reads, derived from the effective end date. Present → a total for the period; absent → a daily rate. A surface must print this beside the amount.
  - `updatedAt` number, nullable, required: Milliseconds since the Unix epoch, UTC.

## Other responses

- `400`: The request does not match the schema of this operation: a field is missing or has the wrong type, or the body is not valid JSON. `error` is `invalid_request`, and `issues` names each field. Nothing was changed.
- `401`: No API key, or one that does not resolve. See the security scheme. `error` is `unauthorized`.
- `402`: The organization has no active subscription. An owner subscribes at `paywallUrl`. `error` is `organization_unentitled`.
- `403`: The caller does not hold `campaign_plan:write`. `error` is `forbidden`.
- `404`: No such plan, or no Line Item with that id on it. `error` is `not_found`.
- `409`: The Line Item moved since you read it. `error` is `revision_mismatch`. This is what makes it a validation **of the content you read**.

---

[API](https://skmtc.dev/adcrunch/apis/adcrunch-api.md) · [All operations](https://skmtc.dev/adcrunch/apis/adcrunch-api/llms.txt) · [OpenAPI document](https://skmtc.dev/adcrunch/apis/adcrunch-api/revisions/a420ba80914f?raw)
