Endpoints
Profiles
Manage the persona library a Set Profile task dresses accounts in: profiles, groups, and both generators.
Everything the Profiles page does, over HTTP: build the persona library by hand, organize it into groups, and fill it with either generator.
Two scopes cover the domain. profiles.read for the listings, profiles.write for everything that changes a profile, a group, or asks for a batch. Groups share them, because a group is just a bucket of profiles.
A profile is a Profile name, a Summary, a Country and an Avatar. A Set Profile task takes one profile (or picks from a group) and writes it onto a Steam account. See Steam accounts for the task side.
Check your generation allowance
Read the month's headroom before you ask for a batch, so you size the request instead of discovering the ceiling by being refused.
/api/v1/profiles/generate/allowanceAI allowance, batch ceilings, and whether a batch is already running.
Requires profiles.read.
curl https://dashboard.steamlabs.dev/api/v1/profiles/generate/allowance \
-H "Authorization: Bearer $STEAMLABS_API_KEY"{
"enabled": true,
"monthly_allowance": 500,
"remaining_this_month": 187,
"max_batch": 187,
"random_max_batch": 1000,
"resets_at": "2026-08-01T00:00:00+00:00",
"generation_in_flight": false
}| Field | Type | Description |
|---|---|---|
enabled |
bool | Whether AI generation is switched on for this deployment at all. false means the AI endpoint refuses everyone |
monthly_allowance |
int or null | AI profiles you may generate per calendar month. null is unlimited, 0 is none |
remaining_this_month |
int or null | What is left of that allowance. null is unlimited |
max_batch |
int | The largest count the AI endpoint accepts right now: the server's per-batch bound clamped to remaining_this_month |
random_max_batch |
int | The largest count the random endpoint accepts. Not affected by the allowance |
resets_at |
string | When the allowance refills: midnight on the first of next month, UTC |
generation_in_flight |
bool | true while one of your batches is pending or processing. Both generators refuse a second one |
Generate profiles with AI
Queue a batch of AI-written personas. This is the only call in the domain that spends something you cannot get back.
/api/v1/profiles/generate/aiQueue an AI generation batch. Returns 202.
Requires profiles.write.
An Idempotency-Key header is required. A timed-out request that you retry without one would generate (and charge for) a second batch with no way to tell. See Bulk operations.
| Field | Type | Description |
|---|---|---|
count |
int | How many profiles to generate. Minimum 1, maximum is the max_batch from the allowance endpoint. Above it you get 422 on count |
group_ids |
array | Groups every generated profile joins. Must be your own group ids |
include_summary |
bool | Write a profile description. Default true |
include_country |
bool | Give each profile a country. Default true |
generate_avatars |
bool | Render an avatar image per profile. Default false |
custom_prompt |
string | Replaces the default text prompt. Up to 4,000 characters |
style_hints |
string | Steers the avatar look, for example neon portraits. Up to 1,000 characters |
curl -X POST https://dashboard.steamlabs.dev/api/v1/profiles/generate/ai \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"count": 25, "generate_avatars": true, "style_hints": "neon portraits"}'$batch = Http::withToken($apiKey)
->withHeaders(['Idempotency-Key' => (string) Str::uuid()])
->post('https://dashboard.steamlabs.dev/api/v1/profiles/generate/ai', [
'count' => 25,
'generate_avatars' => true,
'style_hints' => 'neon portraits',
])
->json();const response = await fetch('https://dashboard.steamlabs.dev/api/v1/profiles/generate/ai', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({ count: 25, generate_avatars: true }),
});
const batch = await response.json();batch = requests.post(
"https://dashboard.steamlabs.dev/api/v1/profiles/generate/ai",
headers={
"Authorization": f"Bearer {api_key}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={"count": 25, "generate_avatars": True},
).json()The answer is 202, with the batch attached:
{
"task_id": "019fb44a-3d10-71c8-9c07-4a2f1b6d9e33",
"accounts_affected": 0,
"profiles_requested": 25,
"generation": {
"id": "019fb44a-3d10-71c8-9c07-4a2f1b6d9e33",
"method": "ai",
"status": "pending",
"is_finished": false,
"requested_count": 25,
"generated_count": 0,
"generate_avatars": true,
"options": {
"include_summary": true,
"include_country": true,
"group_ids": [],
"style_hints": "neon portraits"
},
"error": null,
"created_at": "2026-07-30T14:02:11+00:00",
"updated_at": "2026-07-30T14:02:11+00:00"
}
}accounts_affected is 0 because this queues no per-account work. profiles_requested is the count that was actually accepted, which can be lower than you asked for: the server re-clamps to your headroom at dispatch time.
The monthly cap
AI generation is capped per calendar month, and the allowance refills at midnight on the first. It is not a rolling 30 days and not your subscription anniversary.
The number comes from your plan's AI profile allowance, and an admin can override it per user (support does this to park or extend an account). Whichever applies, GET /api/v1/profiles/generate/allowance reports the one that binds.
Four refusals sit in front of a batch, and only one of them is a validation error:
| Situation | Answer |
|---|---|
| AI generation is off for the deployment | 403 ai_generation_disabled |
| You hold no plan, or your plan carries no AI allowance | 403 plan_limit_reached with plan.upgrade_url |
| The allowance exists and this month's is spent | 403 plan_limit_reached, message naming the refill date |
| One of your batches is already pending or processing | 409 generation_in_progress |
count is above this month's headroom |
422 on count |
Generate random profiles
Queue a batch from the local wordlist engine. Same pipeline, same batch record, no AI.
/api/v1/profiles/generate/randomQueue an algorithmic generation batch. Returns 202.
Requires profiles.write.
Idempotency-Key is accepted and honoured, but not required. This call is free, so a mandatory header would only cost you a 400 for no benefit.
| Field | Type | Description |
|---|---|---|
count |
int | How many profiles to generate. 1 to random_max_batch (1,000 by default) |
group_ids |
array | Groups every generated profile joins. Must be your own group ids |
include_summary |
bool | Write a short bio. Default true. Some profiles stay blank on purpose, like real ones |
include_country |
bool | Give each profile a country. Default true |
generate_avatars |
bool | Render a simple geometric avatar per profile, locally. Default false |
curl -X POST https://dashboard.steamlabs.dev/api/v1/profiles/generate/random \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"count": 500, "generate_avatars": true}'{
"task_id": "019fb44b-8e51-7220-a1d4-6f9c02b7e884",
"accounts_affected": 0,
"profiles_requested": 500,
"generation": {
"id": "019fb44b-8e51-7220-a1d4-6f9c02b7e884",
"method": "random",
"status": "pending",
"is_finished": false,
"requested_count": 500,
"generated_count": 0,
"generate_avatars": true,
"options": {
"include_summary": true,
"include_country": true,
"group_ids": [],
"style_hints": null
},
"error": null,
"created_at": "2026-07-30T14:06:40+00:00",
"updated_at": "2026-07-30T14:06:40+00:00"
}
}The only refusals are 409 generation_in_progress and a 422 on count.
List generations
Your generation batches, newest first. This is how you follow a batch you just queued.
/api/v1/profiles/generationsYour generation batches, paginated.
Requires profiles.read.
| Parameter | Values |
|---|---|
status |
pending, processing, completed, partial, failed |
method |
ai, random |
page, per_page |
See Pagination and filtering |
curl "https://dashboard.steamlabs.dev/api/v1/profiles/generations?status=processing" \
-H "Authorization: Bearer $STEAMLABS_API_KEY"{
"data": [
{
"id": "019fb44a-3d10-71c8-9c07-4a2f1b6d9e33",
"method": "ai",
"status": "processing",
"is_finished": false,
"requested_count": 25,
"generated_count": 11,
"generate_avatars": true,
"options": {
"include_summary": true,
"include_country": true,
"group_ids": ["019fb430-1c22-73a4-9f0e-2b7c5d1e8a44"],
"style_hints": "neon portraits"
},
"error": null,
"created_at": "2026-07-30T14:02:11+00:00",
"updated_at": "2026-07-30T14:03:58+00:00"
}
],
"meta": { "page": 1, "per_page": 50, "total": 1, "last_page": 1 }
}A batch ends at completed, partial (some profiles were written, the rest failed) or failed. is_finished is true for all three, so poll that rather than comparing status strings. error carries the reason a batch stopped short.
Get one generation
/api/v1/profiles/generations/{id}One generation batch, with its live progress.
Requires profiles.read.
curl https://dashboard.steamlabs.dev/api/v1/profiles/generations/019fb44a-3d10-71c8-9c07-4a2f1b6d9e33 \
-H "Authorization: Bearer $STEAMLABS_API_KEY"Same shape as one row of the listing. Profiles appear in your library as the batch writes them, so generated_count climbs while it runs.
Reset use counts in bulk
Put a selection of profiles back in the pool a Set Profile task picks from.
/api/v1/profiles/bulk/reset-use-countsSet times_used back to zero across a selection.
Requires profiles.write. Idempotency-Key is optional and honoured if sent.
Every bulk endpoint here takes a selection, in one of two shapes.
{ "profile_ids": ["019fb42e-9a61-70d2-818a-f6a56593f3a5", "019fb42e-9a7e-728d-b960-8b4c2162898c"] }An explicit list, capped at 1,000 ids. Above the cap you get 422:
{ "message": "Send at most 1000 profile ids in one request…", "code": "bulk_limit_exceeded", "max": 1000 }{ "filters": { "group_ids": ["019fb430-1c22-73a4-9f0e-2b7c5d1e8a44"], "used": true } }The same filter keys the profiles listing accepts, resolved on the server. Narrow the list with a GET until it looks right, then post that same filter object.
curl -X POST https://dashboard.steamlabs.dev/api/v1/profiles/bulk/reset-use-counts \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filters": {"group_ids": ["019fb430-1c22-73a4-9f0e-2b7c5d1e8a44"]}}'{ "profiles_affected": 240 }Delete profiles in bulk
/api/v1/profiles/bulk/deleteDelete a selection of profiles, and their avatar files.
Requires profiles.write. Idempotency-Key is optional and honoured if sent.
Takes the same selection as the reset endpoint: profile_ids (up to 1,000) or filters, exactly one of them, and never an empty filter object.
curl -X POST https://dashboard.steamlabs.dev/api/v1/profiles/bulk/delete \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filters": {"generation_id": "019fb44b-8e51-7220-a1d4-6f9c02b7e884"}}'const response = await fetch('https://dashboard.steamlabs.dev/api/v1/profiles/bulk/delete', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ filters: { used: false, country: 'NL' } }),
});
const { profiles_affected: deleted } = await response.json();{ "profiles_affected": 118 }Deleting a profile also deletes its avatar file. Accounts that already wear the profile keep what was written to Steam: a profile is a preset, not a live link.
List profiles
/api/v1/profilesYour profile library, paginated and filterable.
Requires profiles.read.
curl "https://dashboard.steamlabs.dev/api/v1/profiles?used=false&country=NL&per_page=1" \
-H "Authorization: Bearer $STEAMLABS_API_KEY"profiles = requests.get(
"https://dashboard.steamlabs.dev/api/v1/profiles",
headers={"Authorization": f"Bearer {api_key}"},
params={"used": "false", "country": "NL", "per_page": 200},
).json()["data"]{
"data": [
{
"id": "019fb42e-9a61-70d2-818a-f6a56593f3a5",
"persona_name": "Ada",
"country": "NL",
"summary": "Mostly playing at night. Add me before you trade.",
"avatar_path": "profile-avatars/pQ7x2mKe91ZbV0aD.jpg",
"avatar_url": "https://dashboard.steamlabs.dev/storage/profile-avatars/pQ7x2mKe91ZbV0aD.jpg",
"times_used": 0,
"generation_id": "019fb44b-8e51-7220-a1d4-6f9c02b7e884",
"groups": [
{ "id": "019fb430-1c22-73a4-9f0e-2b7c5d1e8a44", "name": "EU personas" }
],
"created_at": "2026-07-01T09:15:00+00:00",
"updated_at": "2026-07-30T14:02:11+00:00"
}
],
"meta": { "page": 1, "per_page": 1, "total": 1, "last_page": 1 }
}times_used counts how many times a Set Profile task has worn this profile. It is a plain counter, not a ledger: nothing records which account got which profile.
Filters
| Parameter | Values |
|---|---|
search |
Substring of the profile name |
group_ids |
Group ids, comma separated (?group_ids=a,b) or repeated. Matches a profile in any of them |
country |
An ISO 3166-1 alpha-2 code, for example NL. An unknown code is a 422, not an empty result |
used |
true for profiles used at least once, false for never used |
generation_id |
Only the profiles one generation batch produced |
sort |
persona_name (default), times_used, created_at, updated_at. Prefix with - for descending |
Anything else in sort is a 422, so a typo tells you instead of quietly falling back to the default. The same filter keys are what the bulk endpoints accept inside filters.
Create a profile
/api/v1/profilesAdd one profile to your library. Returns 201.
Requires profiles.write. Idempotency-Key is optional and honoured if sent.
| Field | Type | Description |
|---|---|---|
persona_name |
string | The Steam profile name, 2 to 32 characters |
summary |
string | The profile description shown on the Steam profile page. Up to 8,000 characters |
country |
string | ISO 3166-1 alpha-2 code, from the list Steam's own country dropdown offers |
group_ids |
array | Groups this profile joins. Must be your own group ids, or you get a 422 |
avatar |
file | JPEG, PNG or GIF, up to 1 MB, at least 184x184 pixels |
curl -X POST https://dashboard.steamlabs.dev/api/v1/profiles \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"persona_name": "Ada", "country": "NL", "summary": "Mostly playing at night."}'$profile = Http::withToken($apiKey)
->attach('avatar', file_get_contents('ada.jpg'), 'ada.jpg')
->post('https://dashboard.steamlabs.dev/api/v1/profiles', [
'persona_name' => 'Ada',
'country' => 'NL',
])
->json();Returns 201 with the profile, in the same shape the listing returns.
Steam downscales avatars to 184x184, so anything below that uploads visibly blurry. That is why the minimum is enforced rather than warned about.
Get one profile
/api/v1/profiles/{id}One profile.
Requires profiles.read.
curl https://dashboard.steamlabs.dev/api/v1/profiles/019fb42e-9a61-70d2-818a-f6a56593f3a5 \
-H "Authorization: Bearer $STEAMLABS_API_KEY"Returned bare, with no envelope. A profile that is not yours is a 404, never a 403: a 403 would confirm the id exists.
Update a profile
/api/v1/profiles/{id}Change a profile. Every field is optional.
Requires profiles.write. PUT is accepted and behaves identically.
| Field | Type | Description |
|---|---|---|
persona_name |
string | 2 to 32 characters |
summary |
string | Up to 8,000 characters. null clears it |
country |
string | ISO 3166-1 alpha-2 code. null clears it |
group_ids |
array | Replaces the profile's groups. [] removes it from all of them |
avatar |
file | A new image, replacing the old one (the old file is deleted) |
remove_avatar |
bool | true deletes the current avatar and leaves the profile with none |
Omitted fields are left alone. remove_avatar exists because "sent no avatar" and "take the avatar away" are different intentions, and a PATCH cannot tell them apart from a missing field.
curl -X PATCH https://dashboard.steamlabs.dev/api/v1/profiles/019fb42e-9a61-70d2-818a-f6a56593f3a5 \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"persona_name": "Ada L.", "group_ids": []}'curl -X POST https://dashboard.steamlabs.dev/api/v1/profiles/019fb42e-9a61-70d2-818a-f6a56593f3a5 \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Accept: application/json" \
-F "_method=PATCH" \
-F "[email protected]"Delete a profile
/api/v1/profiles/{id}Delete one profile and its avatar file. Returns 204.
Requires profiles.write.
Its group memberships go with it. Accounts already wearing the profile are untouched.
Reset one use count
/api/v1/profiles/{id}/reset-use-countSet this profile's times_used back to zero.
Requires profiles.write.
curl -X POST https://dashboard.steamlabs.dev/api/v1/profiles/019fb42e-9a61-70d2-818a-f6a56593f3a5/reset-use-count \
-H "Authorization: Bearer $STEAMLABS_API_KEY"Returns the updated profile with "times_used": 0. Worth doing when a Set Profile task is configured not to reuse profiles and you want this one back in the pool.
List profile groups
Groups are named buckets, and a profile can be in several. Point a Set Profile task at a group and it draws from the whole bucket as one pool.
/api/v1/profile-groupsYour groups, with member counts.
Requires profiles.read.
| Parameter | Values |
|---|---|
search |
Substring of the group name |
sort |
name (default), profiles_count, created_at. Prefix with - for descending |
page, per_page |
See Pagination and filtering |
curl "https://dashboard.steamlabs.dev/api/v1/profile-groups?sort=-profiles_count" \
-H "Authorization: Bearer $STEAMLABS_API_KEY"{
"data": [
{
"id": "019fb430-1c22-73a4-9f0e-2b7c5d1e8a44",
"name": "EU personas",
"profiles_count": 240,
"created_at": "2026-07-01T09:15:00+00:00",
"updated_at": "2026-07-20T11:00:00+00:00"
}
],
"meta": { "page": 1, "per_page": 50, "total": 1, "last_page": 1 }
}Create a profile group
/api/v1/profile-groupsCreate a group, optionally with members. Returns 201.
Requires profiles.write. Idempotency-Key is optional and honoured if sent.
| Field | Type | Description |
|---|---|---|
name |
string | Up to 255 characters. Unique across your own groups, so a repeat is a 422 |
profile_ids |
array | Profiles to put in the group at creation. Must be your own profile ids |
curl -X POST https://dashboard.steamlabs.dev/api/v1/profile-groups \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "EU personas", "profile_ids": ["019fb42e-9a61-70d2-818a-f6a56593f3a5"]}'Names are unique per account holder, not globally: two customers can both have a group called "farm".
Get one profile group
/api/v1/profile-groups/{id}One group, with its member count.
Requires profiles.read.
Returned bare, with no envelope. The members are a separate endpoint, because a group can hold tens of thousands of profiles.
Update a profile group
/api/v1/profile-groups/{id}Rename a group, or replace its membership wholesale.
Requires profiles.write. PUT is accepted and behaves identically.
| Field | Type | Description |
|---|---|---|
name |
string | Up to 255 characters, still unique across your groups |
profile_ids |
array | Replaces the group's members. [] empties it. Must be your own profile ids |
curl -X PATCH https://dashboard.steamlabs.dev/api/v1/profile-groups/019fb430-1c22-73a4-9f0e-2b7c5d1e8a44 \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "EU personas (2026)"}'profile_ids here is the declarative half: it states what the group contains. To add or remove a few without touching the rest, use the membership endpoints below.
Delete a profile group
/api/v1/profile-groups/{id}Delete the group. Its profiles are untouched. Returns 204.
Requires profiles.write.
A group is a label, and deleting a label is not a reason to destroy what it labelled. Members survive, ungrouped.
List a group's profiles
/api/v1/profile-groups/{id}/profilesThe profiles in one group, paginated.
Requires profiles.read.
Takes the same filters as the profiles listing (search, group_ids, country, used, generation_id) plus page and per_page. Rows are ordered by profile name.
curl "https://dashboard.steamlabs.dev/api/v1/profile-groups/019fb430-1c22-73a4-9f0e-2b7c5d1e8a44/profiles?used=false" \
-H "Authorization: Bearer $STEAMLABS_API_KEY"{
"data": [
{
"id": "019fb42e-9a61-70d2-818a-f6a56593f3a5",
"persona_name": "Ada",
"country": "NL",
"avatar_path": "profile-avatars/pQ7x2mKe91ZbV0aD.jpg",
"avatar_url": "https://dashboard.steamlabs.dev/storage/profile-avatars/pQ7x2mKe91ZbV0aD.jpg",
"times_used": 0,
"created_at": "2026-07-01T09:15:00+00:00"
}
],
"meta": { "page": 1, "per_page": 50, "total": 1, "last_page": 1 }
}A lighter row than the main listing: no summary and no groups, since you already know one of them.
Attach profiles to a group
/api/v1/profile-groups/{id}/profilesAdd profiles to the group, leaving existing members alone.
Requires profiles.write.
| Field | Type | Description |
|---|---|---|
profile_ids |
array | At least one profile id. Must be your own, or you get a 422 naming the offending index |
curl -X POST https://dashboard.steamlabs.dev/api/v1/profile-groups/019fb430-1c22-73a4-9f0e-2b7c5d1e8a44/profiles \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"profile_ids": ["019fb42e-9a61-70d2-818a-f6a56593f3a5", "019fb42e-9a7e-728d-b960-8b4c2162898c"]}'{ "profiles_attached": 1, "profiles_count": 241 }profiles_attached counts only the ids that were not already in the group, so re-sending a list is safe and never duplicates a membership. profiles_count is the group's size afterwards.
Detach profiles from a group
/api/v1/profile-groups/{id}/profilesRemove profiles from the group. The profiles themselves are kept.
Requires profiles.write. Send profile_ids in the request body, same rules as attaching.
curl -X DELETE https://dashboard.steamlabs.dev/api/v1/profile-groups/019fb430-1c22-73a4-9f0e-2b7c5d1e8a44/profiles \
-H "Authorization: Bearer $STEAMLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"profile_ids": ["019fb42e-9a61-70d2-818a-f6a56593f3a5"]}'{ "profiles_detached": 1, "profiles_count": 240 }200 with a count rather than 204, because this deletes memberships rather than the thing you addressed. Ids that were not in the group simply do not count.
Errors
Beyond the universal codes, this group returns:
| Status | Code | When |
|---|---|---|
403 |
ai_generation_disabled |
AI generation is switched off for the deployment. Nothing you can buy or wait for changes it |
403 |
plan_limit_reached |
Your plan carries no AI profile allowance, or this calendar month's is spent. Carries plan.upgrade_url |
409 |
generation_in_progress |
One of your batches is pending or processing. Wait for is_finished, then retry |
422 |
bulk_limit_exceeded |
More than 1,000 ids in a bulk selection. Carries max |
Everything else follows the shared contract: 404 for a profile, group or generation that is not yours, 422 for validation, 400 idempotency_key_required on the AI generator without its header. See Errors.