# 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. We insert the identity row synchronously (`status: "pending"`) and kick off a background workflow for reference-image rendering. 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`.

## Idempotency [#idempotency]

Pass `influencerId` in the body to make creation idempotent on your side. If the id already exists in this project, we return `409 CONFLICT`. Also set an `Idempotency-Key` header — we cache the response for 24 hours.

## Path parameters [#path-parameters]

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

## Body [#body]

<Parameters
  title="Body"
  rows="[
  { name: 'name', type: 'string', required: true, description: 'Display name, 1-128 chars.' },
  { name: 'influencerId', type: 'string (uuid)', description: 'Your id for idempotency. Omit to let us generate one.' },
  { name: 'gender', type: 'string', description: 'Drives voice and rendering.', enum: ['male', 'female', 'nonbinary', 'unspecified'] },
  { name: 'ageRange', type: 'string (max 16)', description: 'Free-form age band, e.g. `25-34`.' },
  { name: 'ethnicity', type: 'string (max 128)', description: 'Free-form ethnicity descriptor.' },
  { name: 'bodyType', type: 'string (max 128)', description: 'Free-form body descriptor.' },
  { name: 'hairColor', type: 'string (max 64)', description: 'Free-form hair color.' },
  { name: 'hairStyle', type: 'string (max 128)', description: 'Free-form hair style.' },
  { name: 'style', type: 'string (max 256)', description: 'Fashion / visual style.' },
  { name: 'vibe', type: 'string (max 256)', description: 'Overall vibe descriptor.' },
  { name: 'voiceDescription', type: 'string (max 1024)', description: 'How they sound on camera.' },
  { name: 'personality', type: 'object', description: 'Optional nested object: `{ traits?: string[], tone?, humor?, formality?, values?: string[], interests?: string[], speakingStyle?, catchphrases?: string[] }`.' },
  { name: 'referenceImages', type: 'object', description: 'Optional `{ assetIds: string[] }` — 1 to 20 media asset ids to seed the identity.' },
]"
/>

## 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 "X-Api-Key: $LAYERS_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: 8d2f1a3e-0b4c-4a11-9f7e-33c0a2c1bd55" \
      -d '{
        "name": "Ava Chen",
        "gender": "female",
        "ageRange": "25-34",
        "style": "minimalist streetwear",
        "vibe": "warm and curious",
        "personality": {
          "traits": ["curious", "warm", "direct"],
          "tone": "conversational",
          "humor": "dry"
        }
      }'
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts title="create-influencer.ts"
    const res = await fetch(
      `https://api.layers.com/v1/projects/${projectId}/influencers`,
      {
        method: 'POST',
        headers: {
          'X-Api-Key': process.env.LAYERS_API_KEY!,
          'Content-Type': 'application/json',
          'Idempotency-Key': crypto.randomUUID(),
        },
        body: JSON.stringify({
          name: 'Ava Chen',
          gender: 'female',
          ageRange: '25-34',
          personality: { traits: ['curious', 'warm', 'direct'] },
        }),
      },
    );
    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={
            "X-Api-Key": os.environ["LAYERS_API_KEY"],
            "Content-Type": "application/json",
            "Idempotency-Key": str(uuid.uuid4()),
        },
        json={
            "name": "Ava Chen",
            "gender": "female",
            "ageRange": "25-34",
            "personality": {"traits": ["curious", "warm", "direct"]},
        },
    )
    job = r.json()
    ```
  </Tab>
</Tabs>

## Responses [#responses]

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

<Response status="409" description="influencerId already exists in this project.">
  ```json
  {
    "error": {
      "code": "CONFLICT",
      "message": "Influencer with this id already exists.",
      "requestId": "req_..."
    }
  }
  ```
</Response>

<Response status="422" description="Validation failed.">
  ```json
  {
    "error": {
      "code": "VALIDATION",
      "message": "Invalid body.",
      "requestId": "req_...",
      "details": { "fieldErrors": { "name": ["Required"] } }
    }
  }
  ```
</Response>

## Notes [#notes]

<Callout type="info">
  Creation is async. The row is created as `status: "pending"` synchronously,
  then the background job transitions it through `training` to `ready`. The
  `referenceImages` array is populated as the job generates them.
</Callout>

* **Safe to reference immediately.** You can use the `influencerId` in [`POST /v1/projects/:projectId/content`](/docs/api/reference/content/generate-content) the moment this call returns — the content workflow blocks until the influencer is ready.
* **Status values.** `draft`, `pending`, `training`, `ready`, `failed`.
* **Reference image seeds.** Pass `referenceImages.assetIds` (media asset ids from [`/v1/projects/:projectId/media`](/docs/api/reference/media/upload-media-inline)) to seed the identity from photos instead of text.

## Errors [#errors]

| Code              | When                                                   |
| ----------------- | ------------------------------------------------------ |
| `VALIDATION`      | Missing/invalid field, enum out of range, or bad JSON. |
| `CONFLICT`        | `influencerId` already exists in this project.         |
| `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)
* [Bind reference images](/docs/api/reference/influencers/reference-images)
