Skip to main content
POST https://api.humalike.com/v1/personas/actions/generate
Generate a population of richly detailed, humanlike personas from one natural-language prompt. You describe who you want and how many — nothing else — and get back a population that holds together like a real one: believable individuals whose traits vary and correlate the way they would in the real world, each member internally consistent. You never declare fields, distributions, or formats. A single prompt such as “10 League of Legends players from around the globe” is enough on its own: the API does the hard modeling of what makes that population realistic and returns one that actually looks the part — the right mix of regions and ranks, with the detail (like higher ranks playing more) that a believable population has. It hands that model back alongside the personas as a blueprint (see The population), so what it inferred is yours to inspect, not a black box. This endpoint is asynchronous. The POST returns 200 OK right away with an id; you then GET the population repository route with that id until it is ready. Generation grounds the request in real-world data and writes a persona for each member, so it can run for minutes — see the note on grounding below.

Authorization

Authorization
string
required
Your bearer token: Bearer <token>. See Authentication.

Request body

The request is intentionally minimal. These three fields are the entire contract — there is nothing else to send.
prompt
string
required
A non-empty natural-language description of who to generate. This is the only signal the API needs; it infers the field structure from the prompt.
count
integer
default:"1"
How many personas to generate. Must be at least 1. Large counts are capped; a request over the limit returns VALIDATION_ERROR. The population size is this number; there is no separate batch endpoint.
grounding
string
default:"off"
How hard to ground the population in real-world data before generating. One of:
  • off — generate from the prompt alone, with no external lookups.
  • web — enrich the inferred field model with a quick live lookup so distributions and details reflect current real-world data.
  • research — run deeper research before generating, for the strongest real-world grounding and cited sources.
Request
{
  "prompt": "10 League of Legends players from around the globe",
  "count": 10,
  "grounding": "web"
}

Start a population

The POST returns 200 OK straight away. It does not wait for the personas; it gives you an id used with the population repository route.
id
string
The population’s identifier. Use it to poll for the result.
status
string
Always pending in this response — the population has been accepted and queued.
200 OK
{
  "id": "8f2c1a7d-9e2b-4f42-9344-0df65f73e5d1",
  "status": "pending"
}

Grounding and timing

Generation grounds the request in real-world data and then writes a persona for each member, so it does not return on the POST. research grounding runs deeper lookups and can take minutes, and time grows with count. That is why this endpoint hands back an id to poll rather than holding the connection open. Poll on an interval of a few seconds and do not block a user-facing request on a research population.

Poll for the result

GET https://api.humalike.com/v1/personas/repositories/Population/by-id/{id}
Call GET with the returned id until status is succeeded or failed.
id
string
The population’s identifier, the same value you started.
status
string
Where the population is in its lifecycle. One of:
  • pending — accepted and queued; generation has not started yet.
  • running — grounding and generation are under way; progress updates.
  • succeeded — finished; result holds the population.
  • failed — finished; error contains a stable failure category.
pending and running are not terminal — keep polling. succeeded and failed are terminal — stop polling.
progress
object
How far generation has got, present while running. produced is how many personas are written so far and total is how many were requested.
result
object
The generated population, present only when status is succeeded. Its shape is described under The population below.
error
string
A stable failure category such as provider_error. Present only when status is failed.
While the population is still building, the poll reports running and carries a progress object so you can show how far along it is:
200 OK (running)
{
  "id": "8f2c1a7d-9e2b-4f42-9344-0df65f73e5d1",
  "status": "running",
  "progress": { "produced": 4, "total": 10 }
}

The population

When status is succeeded, the poll carries the population under result: the rendered personas, the inferred blueprint the personas were sampled from, and fidelity reports showing how closely the realized population matches that blueprint. The fields below describe the shape of that result. Each persona is dynamic. There is no fixed persona shape: the blueprint declares the field set, and every persona carries exactly those fields as a flat fields map of name to string value. For the League of Legends prompt the API might infer region, rank, main_role, hours_per_week, name, and backstory; a different prompt yields a different field set.
personas
object[]
The generated personas. Each one carries the blueprint’s fields plus a ready-to-use system_prompt and a formatted markdown sheet — all three are always present.
blueprint
object
The inferred field model the population was sampled from — the API’s reasoning about what makes this population realistic, returned so you can inspect it and read the field set the personas use.
diversity
object
How varied the population is. Present for multi-persona populations.
marginals
object[]
Per-field fidelity: for each root categorical field, how closely the realized population matches the blueprint’s marginal distribution.
When the population succeeds, this object is the value of result ({ "id": ..., "status": "succeeded", "result": <the object below> }):
200 OK (succeeded)
{
  "id": "8f2c1a7d-9e2b-4f42-9344-0df65f73e5d1",
  "status": "succeeded",
  "result": {
    "personas": [
      {
        "persona_id": "p_01",
        "fields": {
          "region": "KR",
          "rank": "Challenger",
          "main_role": "mid",
          "hours_per_week": "48",
          "name": "Jiwoo 'Hae' Park",
          "backstory": "Grew up in a Seoul PC bang and never left the rift; hit Challenger at 19 and now scrims before dawn chasing an academy contract."
        },
        "system_prompt": "You are Jiwoo 'Hae' Park, a Korean Challenger mid main...",
        "markdown": "# Jiwoo 'Hae' Park\n\n_Korean Challenger mid main..._"
      },
      {
        "persona_id": "p_02",
        "fields": {
          "region": "BR",
          "rank": "Silver",
          "main_role": "support",
          "hours_per_week": "7",
          "name": "Lucas Ferreira",
          "backstory": "Plays a few nights a week after his shift; mains enchanters and pings missing more than he should."
        },
        "system_prompt": "You are Lucas Ferreira, a casual Silver support...",
        "markdown": "# Lucas Ferreira\n\n_Casual Silver support..._"
      }
    ],
    "blueprint": {
      "domain": "lol_player",
      "order": ["region", "rank", "main_role", "hours_per_week"],
      "fields": [
        {
          "name": "region",
          "kind": "categorical",
          "description": "server region the player queues on",
          "parents": [],
          "categorical": { "weights": { "NA": 0.2, "EUW": 0.25, "KR": 0.15, "BR": 0.15, "CN": 0.25 } },
          "conditionals": []
        },
        {
          "name": "rank",
          "kind": "categorical",
          "description": "ranked solo/duo tier",
          "parents": [],
          "categorical": {
            "weights": { "Bronze": 0.2, "Silver": 0.3, "Gold": 0.25, "Platinum": 0.15, "Diamond": 0.07, "Challenger": 0.03 }
          },
          "ordered_values": ["Bronze", "Silver", "Gold", "Platinum", "Diamond", "Challenger"],
          "conditionals": []
        },
        {
          "name": "main_role",
          "kind": "categorical",
          "description": "preferred position",
          "parents": [],
          "categorical": { "weights": { "top": 0.2, "jungle": 0.2, "mid": 0.2, "adc": 0.2, "support": 0.2 } },
          "conditionals": []
        },
        {
          "name": "hours_per_week",
          "kind": "numeric",
          "description": "hours played per week",
          "parents": ["rank"],
          "conditionals": [
            { "when": { "rank": "Bronze" }, "numeric": { "min": 1, "max": 20, "mean": 6, "sd": 4, "integer": true } },
            { "when": { "rank": "Challenger" }, "numeric": { "min": 30, "max": 80, "mean": 50, "sd": 10, "integer": true } }
          ]
        },
        {
          "name": "name",
          "kind": "text",
          "description": "the player's display name, fitting their region",
          "parents": ["region"],
          "conditionals": []
        },
        {
          "name": "backstory",
          "kind": "text",
          "description": "a short paragraph on how they play and why",
          "parents": ["rank", "main_role"],
          "conditionals": []
        }
      ],
      "constraints": [
        { "name": "hours_nonneg", "lhs": "hours_per_week", "op": ">=", "rhs": "0" }
      ],
      "rationale": "Region, rank, and role are independent roots; play time rises with rank; name and backstory are written per persona.",
      "sources": ["Public ranked-distribution figures for the current season."]
    },
    "diversity": {
      "max_pairwise_similarity": 0.38,
      "mean_pairwise_similarity": 0.17,
      "duplicate_pairs": 0
    },
    "marginals": [
      {
        "attribute": "rank",
        "cells": [
          { "key": "Bronze", "requested": 0.2, "achieved": 0.2 },
          { "key": "Silver", "requested": 0.3, "achieved": 0.3 },
          { "key": "Gold", "requested": 0.25, "achieved": 0.2 },
          { "key": "Platinum", "requested": 0.15, "achieved": 0.2 },
          { "key": "Diamond", "requested": 0.07, "achieved": 0.1 },
          { "key": "Challenger", "requested": 0.03, "achieved": 0.0 }
        ],
        "total_variation_distance": 0.1
      }
    ]
  }
}
(personas and marginals are truncated above; a real population contains count personas and one manifest per root categorical field.)

When generation fails

If generation fails after the population starts, the poll still returns 200 OK with status: "failed" and an error instead of a result:
200 OK (failed)
{
  "id": "8f2c1a7d-9e2b-4f42-9344-0df65f73e5d1",
  "status": "failed",
  "error": "provider_error"
}
Branch on the poll’s status and error, not on the HTTP status of the poll — a failed population is reported with 200 OK. Start a new population to retry; do not keep polling a failed one.

Errors

Errors arrive in two places: a bad request is rejected at the POST, and a failure during generation surfaces on the poll. The POST returns one of these before any population is started:
StatusCodeWhen
400VALIDATION_ERRORcount is over the configured batch limit.
401UNAUTHORIZEDThe bearer token is missing, invalid, or expired.
402PAYMENT_REQUIREDYour credit balance can’t cover the request. See Credits and billing.
422validation_failedA required field is missing or has an invalid type/value.
A rejected POST returns the standard error envelope. For example, an empty prompt:
422 Unprocessable Entity
{
  "error": {
    "code": "validation_failed",
    "message": "request validation failed",
    "details": [{ "loc": ["prompt"], "msg": "String should have at least 1 character", "type": "string_too_short" }]
  }
}
Once the POST returns 200, a later failure appears on the poll as status: "failed" with error: "provider_error" (a stable, opaque failure category). Start a fresh population to retry. See Errors for request errors.

Example

Start the population, then poll its repository route until it reaches a terminal status and read the personas from result.
# Start the population — returns 200 with an id.
curl https://api.humalike.com/v1/personas/actions/generate \
  -H "Authorization: Bearer $HUMALIKE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "10 League of Legends players from around the globe",
    "count": 10,
    "grounding": "web"
  }'

# Poll the population (repeat until status is "succeeded" or "failed").
curl https://api.humalike.com/v1/personas/repositories/Population/by-id/8f2c1a7d-9e2b-4f42-9344-0df65f73e5d1 \
  -H "Authorization: Bearer $HUMALIKE_TOKEN"
Because the field set is inferred per prompt, read the keys you need from each persona’s fields map rather than hard-coding them — inspect blueprint.fields to see which fields a given population carries. The diversity and marginals reports are populated only for multi-persona populations (count > 1); a single persona omits them.

Next