# POST /v1/projects/:projectId/influencers (/docs/api/reference/influencers/create-influencer)



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

Starts an `influencer_create` job. The influencer is created in `status: "pending"` while the persona + portrait render. The response is a [job envelope](/docs/api/concepts/jobs) — poll `/v1/jobs/:jobId` for terminal state, then read the influencer at `/v1/influencers/:influencerId`.

The body is intentionally tiny. Layers generates the display name, portrait, and persona attributes from the project's brand context plus the optional hints you supply. There is no `name` field on this request — read it back from `GET /v1/influencers/:id` after the job lands.

<Callout type="info">
  Most partners never need to call this endpoint directly — Layers auto-creates a first influencer in the background as soon as you supply `appDescription` on [`POST /v1/projects`](/docs/api/reference/projects/create-project). Reach for this endpoint when the customer needs **additional** personas (different `gender`, `ageRange`, or `prompt` vibe) for variation across content batches.
</Callout>

## Idempotency [#idempotency]

Send an `Idempotency-Key` header so retries replay the same response. The influencer's id is server-generated and returned in the accept envelope — persist it and map it back to your entity. You cannot supply your own id.

## Path parameters [#path-parameters]

<Parameters
  rows="[
  { name: 'projectId', type: 'string (uuid)', required: true, description: 'The project this influencer belongs to.' },
]"
/>

## Body [#body]

Every field is optional. When omitted, Layers falls back to the project's defaults — most notably, `gender` falls back to the project's `targetGender` so the generated persona reflects the customer's audience.

<Parameters
  title="Body"
  rows="[
  { name: 'gender', type: 'string', description: 'Drives persona and rendering. Required for content generation, so always carries a real value on the row. Server default chain when omitted: project\'s `targetGender` if `female` / `male`, else `female`.', enum: ['male', 'female', 'non_binary'] },
  { name: 'ageRange', type: 'string', description: 'Canonical age band keyed to the renderer\'s persona presets. Same enum as the in-product dialog. Defaults to `young_adult` when omitted.', enum: ['teen', 'young_adult', 'adult', 'mid_adult', 'mature', 'senior'] },
  { name: 'prompt', type: 'string', description: 'Optional free-text hint that steers visual generation — e.g. `70s rocker vibe`, `soft librarian energy`, `barista energy`. 1-60 chars. Omit for a generic persona based on `gender` + `ageRange` alone.' },
]"
/>

Voice (`brandVoice`) and language are inherited from the project and not accepted here. To override per persona after creation, use [`PATCH /v1/influencers/:id`](/docs/api/reference/influencers/patch-influencer).

## Request [#request]

<Tabs items="['curl', 'TypeScript', 'Python']">
  <Tab value="curl">
    ```sh title="terminal"
    curl -X POST https://api.layers.com/v1/projects/{projectId}/influencers \
      -H "Authorization: Bearer $LAYERS_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: 8d2f1a3e-0b4c-4a11-9f7e-33c0a2c1bd55" \
      -d '{
        "gender": "female",
        "ageRange": "young_adult",
        "prompt": "barista energy, soft morning light"
      }'
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts title="create-influencer.ts"
    const res = await fetch(
      `https://api.layers.com/v1/projects/${projectId}/influencers`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.LAYERS_API_KEY}`,
          'Content-Type': 'application/json',
          'Idempotency-Key': crypto.randomUUID(),
        },
        body: JSON.stringify({
          gender: 'female',
          ageRange: 'young_adult',
          prompt: 'barista energy, soft morning light',
        }),
      },
    );
    const { jobId, influencerId } = await res.json();
    ```
  </Tab>

  <Tab value="Python">
    ```py title="create_influencer.py"
    import os, uuid, httpx

    r = httpx.post(
        f"https://api.layers.com/v1/projects/{project_id}/influencers",
        headers={
            "Authorization": f"Bearer {os.environ['LAYERS_API_KEY']}",
            "Content-Type": "application/json",
            "Idempotency-Key": str(uuid.uuid4()),
        },
        json={
            "gender": "female",
            "ageRange": "young_adult",
            "prompt": "barista energy, soft morning light",
        },
    )
    job = r.json()
    ```
  </Tab>
</Tabs>

You can also send an empty body — every field is optional. Layers picks gender/age/prompt defaults from the project.

## Responses [#responses]

<Response status="202" description="Job accepted. The influencer exists with status=pending; poll the job for completion.">
  ```json
  {
    "jobId": "job_01HXZ9G7KMV2QX8Y1S5RJW3B7T",
    "kind": "influencer_create",
    "status": "running",
    "influencerId": "inf_4a8e1bc2...",
    "locationUrl": "/v1/jobs/job_01HXZ9G7KMV2QX8Y1S5RJW3B7T"
  }
  ```
</Response>

<Response status="422" description="Validation failed. Sending name, brandVoice, language, or other unknown fields returns 422 — the create surface is intentionally tiny.">
  ```json
  {
    "error": {
      "code": "VALIDATION",
      "message": "Invalid body.",
      "requestId": "req_...",
      "details": { "fieldErrors": { "name": ["Unrecognized key — name is server-generated."] } }
    }
  }
  ```
</Response>

## Notes [#notes]

<Callout type="info">
  Creation is async. The influencer starts in `status: "pending"`, then moves through `training` to `ready`. The display name, portrait, and persona attributes are generated server-side; partners cannot supply reference images.
</Callout>

* **Safe to reference immediately.** You can use the `influencerId` in [`POST /v1/projects/:projectId/content/slideshow-remix`](/docs/api/reference/content/slideshow-remix) the moment this call returns. Content generation waits until the influencer is ready.
* **Status values.** `pending`, `training`, `ready`, `failed`.
* **Idempotency.** Set an `Idempotency-Key` header so a retry replays the same accept envelope. The influencer id is server-generated; you cannot pass your own.

## Errors [#errors]

| Code              | When                                                                                   |
| ----------------- | -------------------------------------------------------------------------------------- |
| `VALIDATION`      | Unknown field (e.g. `name`, `brandVoice`, `language`), enum out of range, or bad JSON. |
| `NOT_FOUND`       | Project id not in this org.                                                            |
| `FORBIDDEN_SCOPE` | Key lacks `influencers:write`.                                                         |

## See also [#see-also]

* [The jobs envelope](/docs/api/concepts/jobs)
* [Clone an influencer](/docs/api/reference/influencers/clone-influencer)
* [Patch an influencer](/docs/api/reference/influencers/patch-influencer) — override `brandVoice` or `language` after creation
