Skip to main content

API reference

This reference is bundled with the website. The live OpenAPI document describes the deployed backend. Download this version.

Base URL: https://aviseek.vrchatlegends.com/api

GET /discord/status​

Your Discord connection and Linked Role checks

Requires an AviSeek session. Returns only safe identity, sync status and the five server-derived checks; never OAuth tokens.

Responses​

  • 200: Connection status.
  • 401: Sign in required.

POST /discord/connect​

Start Discord OAuth

Requires an AviSeek session and same-origin Origin header. Body: {returnTo: "dashboard"} or {returnTo: "linked-roles"}. Sets a short-lived HttpOnly state cookie and returns a Discord authorization URL.

Responses​

  • 200: Authorization URL.

GET /discord/callback​

Discord OAuth callback

Used by Discord after consent. Requires the same AviSeek session and state cookie as the connection request. Register this exact website URL in the Discord Developer Portal.

Responses​

  • 303: Redirects to the Discord dashboard or Linked Roles page.

GET /discord/guilds​

Discord servers you can administer

Requires a connected Discord account. Lists only servers the user owns or has Administrator permission in. Manage Server alone does not qualify.

Responses​

  • 200: Eligible servers.

GET /discord/guilds/{id}​

Server bot settings and permitted channels

Rechecks current Discord Administrator permission and bot membership. Returns an invite when the bot is absent.

Parameters​

  • id (path, required): {"type":"string"}

Responses​

  • 200: Server configuration.
  • 403: Administrator permission required.

PUT /discord/guilds/{id}​

Update bot settings for your server

Same-origin authenticated request. Rechecks Administrator permission and effective bot channel permissions. Body requires enabled (boolean), private (boolean), channel (snowflake string or null). Changes apply only to this server.

Parameters​

  • id (path, required): {"type":"string"}

Request body​

{
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"enabled",
"private",
"channel"
],
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean"
},
"private": {
"type": "boolean"
},
"channel": {
"type": [
"string",
"null"
]
}
}
}
}
}
}

Responses​

  • 200: Saved settings.
  • 403: Administrator permission required.

POST /discord/sync​

Sync your Linked Role checks

Same-origin authenticated request. Values come from AviSeek, not the request body. Identity and Patreon claims expire after 24 hours and are cleared on the next successful sync. Discord evaluates eligibility; members join Linked Roles in Discord.

Responses​

  • 200: Updated connection.

DELETE /discord/connection​

Disconnect Discord

Same-origin authenticated request. Clears role eligibility before revoking and deleting the encrypted OAuth grant. On a Discord outage, retry or remove the connection in Discord.

Responses​

  • 200: Disconnected.

GET /avatars/search​

Search the index

Tokenized match over name, author, description, VRChat tags and AviSeek tags. A term we hold little for triggers one cached lookup against VRC Nexus.

Parameters​

  • q (query): Search text. {"type":"string"}
  • author (query): Filter by creator name or id. {"type":"string"}
  • tag (query): Filter by a VRChat tag or an AviSeek tag. {"type":"string"}
  • platform (query): pc, quest, or ios. {"type":"string","enum":["pc","quest","ios"]}
  • sort (query): Result order. {"type":"string","enum":["recent","oldest","name","popular"],"default":"recent"}
  • limit (query): 1-60. {"type":"integer","default":24,"maximum":60}
  • offset (query): Result offset. {"type":"integer","default":0}

Responses​

  • 200: Matching avatars.
  • 429: Rate limited.

GET /avatars/tags​

Most common tags

Parameters​

  • limit (query): 1-60. {"type":"integer","default":24}

Responses​

  • 200: Tag list.

GET /avatars/trending​

Most viewed avatars

Parameters​

  • limit (query): 1-20. {"type":"integer","default":5}

Responses​

  • 200: Trending avatars.

GET /avatars/popular-searches​

Top five search terms

Responses​

  • 200: Search terms.

POST /bot/interactions​

Receive signed Discord interactions

Discord-only webhook. Configure https://aviseek.vrchatlegends.com/api/bot/interactions in the Developer Portal. Requires X-Signature-Ed25519 and X-Signature-Timestamp with the exact signed JSON body. Opening this URL with GET shows endpoint notes; it does not validate the Discord integration.

Responses​

  • 200: Interaction acknowledgement.
  • 401: Invalid signature.
  • 503: Discord public key is not configured.

POST /avatars/submit​

Queue an avatar for indexing

Public endpoint, no login or API key required. Send one avatarId or an avatarIds array of 1 to 50 IDs or avatar URLs. At most 100 submitted items per minute per IP (100 requests for single submissions), including invalid and duplicate items. A batch returns HTTP 200 with accepted/rejected counts and ordered per-item results; check each result status and code. Single duplicates return 409. Queue capacity is 1000; batch overflow items receive queue_full, while a single submission gets HTTP 503. Retry-After advises when to retry. Accepted items use the existing paced background worker and shared VRChat safety budget. Private and blocked avatars remain excluded.

Request body​

{
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"avatarId": {
"type": "string",
"example": "avtr_00000000-1111-2222-3333-444444444444"
},
"avatarIds": {
"type": "array",
"minItems": 1,
"maxItems": 50,
"items": {
"type": "string"
},
"example": [
"avtr_00000000-1111-2222-3333-444444444444",
"avtr_11111111-2222-3333-4444-555555555555"
]
}
},
"oneOf": [
{
"required": [
"avatarId"
],
"not": {
"required": [
"avatarIds"
]
}
},
{
"required": [
"avatarIds"
],
"not": {
"required": [
"avatarId"
]
}
}
]
}
}
}
}

Responses​

  • 200: Single submission queued, or batch processed with ordered results containing index, id, status, position or rejection code.
  • 400: Not a valid avatar id.
  • 403: Avatar is blocked from submission.
  • 409: Avatar is already indexed or queued.
  • 429: 100 submitted items per minute exceeded.
  • 503: Verification queue full. Retry later.

GET /stats​

Index totals

Responses​

  • 200: Catalog counters.

GET /health​

Liveness

Responses​

  • 200: Service is up.

GET /account/summary​

Identity, settings and usage

Responses​

  • 200: Account summary.
  • 401: Not signed in.

GET /account/settings​

Read AviSeek preferences

Responses​

  • 200: Settings.

PUT /account/settings​

Update AviSeek preferences

Partial update. Unknown keys are ignored and every value is clamped to a valid option.

Request body​

{
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"defaultPlatform": {
"type": "string"
},
"defaultSort": {
"type": "string"
},
"resultsPerPage": {
"type": "integer"
},
"compactGrid": {
"type": "boolean"
},
"reduceMotion": {
"type": "boolean"
},
"openLinksInNewTab": {
"type": "boolean"
},
"showAiTags": {
"type": "boolean"
}
}
}
}
}
}

Responses​

  • 200: Updated settings.

GET /favorites​

Saved avatars

Responses​

  • 200: Collection.

DELETE /favorites​

Clear the collection

Responses​

  • 200: Cleared.

PUT /favorites/{id}​

Save an avatar

Parameters​

  • id (path, required): {"type":"string"}

Responses​

  • 200: Saved.

DELETE /favorites/{id}​

Unsave an avatar

Parameters​

  • id (path, required): {"type":"string"}

Responses​

  • 200: Removed.

POST /favorites/groups​

Create a favorite group

A group is presentation only: the collection stays one list and each avatar belongs to at most one group, so renaming or deleting a group never unsaves anything.

Request body​

{
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"icon": {
"type": "string",
"description": "One of the fixed icon names returned by GET /favorites."
}
}
}
}
}
}

Responses​

  • 200: Created.
  • 400: Bad name, duplicate, or group limit reached.

PATCH /favorites/groups/{groupId}​

Rename a group or change its icon

Parameters​

  • groupId (path, required): {"type":"string"}

Request body​

{
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"icon": {
"type": "string"
}
}
}
}
}
}

Responses​

  • 200: Updated.
  • 404: Unknown group.

DELETE /favorites/groups/{groupId}​

Delete a group

Its avatars stay in the collection, just without a group.

Parameters​

  • groupId (path, required): {"type":"string"}

Responses​

  • 200: Deleted.
  • 404: Unknown group.

PUT /favorites/{id}/group​

Move a saved avatar into a group

Parameters​

  • id (path, required): {"type":"string"}

Request body​

{
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"groupId": {
"type": "string",
"nullable": true,
"description": "null removes it from its group."
}
}
}
}
}
}

Responses​

  • 200: Moved.
  • 404: Unknown group, or the avatar is not saved.

PUT /likes/{id}​

Like an avatar

Parameters​

  • id (path, required): {"type":"string"}

Responses​

  • 200: Liked.

DELETE /likes/{id}​

Unlike an avatar

Parameters​

  • id (path, required): {"type":"string"}

Responses​

  • 200: Unliked.

GET /creator/avatars​

Your avatars with analytics

Ownership comes from the VRChat account verified on your VRChat Legends profile. Returns 409 when no verified link exists.

Parameters​

  • days (query): Length of the daily series, 7-90. {"type":"integer","default":30}

Responses​

  • 200: Avatars and analytics.
  • 409: No verified VRChat link.

PUT /creator/avatars/{id}/visibility​

Unlist or relist your own avatar

Parameters​

  • id (path, required): {"type":"string"}

Request body​

{
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"listed": {
"type": "boolean"
}
}
}
}
}
}

Responses​

  • 200: Visibility updated.

GET /insights/overview​

Site-wide analytics

Totals for the window (impressions, views, outbound clicks, wears, likes, favorites), the same for the window before it, percent trend per counter, a daily series, and short leaderboards (views, trending, click-through, likes). Cached 30 seconds.

Parameters​

  • days (query): Window length, 7-90. {"type":"integer","default":30}

Responses​

  • 200: Overview.

GET /insights/avatars​

Every listed avatar, ranked by a metric

Avatars with recorded activity come first in the chosen order; the quiet rest of the catalog follows newest first, so a text search still finds an avatar with zero views. Each row carries lifetime totals, window totals, the previous window, percent trend, rates (ctr, click-through, like rate, save rate) and a 14 day view sparkline.

Parameters​

  • q (query): Match against name, creator, id and tags. {"type":"string"}
  • author (query): Creator name or id. {"type":"string"}
  • platform (query): pc, quest, or ios. {"type":"string","enum":["pc","quest","ios"]}
  • sort (query): Ranking. {"type":"string","enum":["views","trending","impressions","outbound","ctr","likes","favorites","newest"],"default":"views"}
  • days (query): Window length, 7-90. {"type":"integer","default":30}
  • limit (query): 1-100. {"type":"integer","default":30,"maximum":100}
  • offset (query): Result offset. {"type":"integer","default":0}

Responses​

  • 200: One page.

GET /insights/avatars/{id}​

Deep dive on one avatar

The explorer row plus a daily series (every counter), audience breakdowns for views and outbound clicks (referring site, device, country, hour, site surface, platform, search terms), rank and percentile against every avatar with activity, peak days, activity span with daily averages, and how many listed avatars the creator has.

Parameters​

  • id (path, required): {"type":"string"}
  • days (query): Window length, 7-90. {"type":"integer","default":30}

Responses​

  • 200: Avatar insights.
  • 404: Not listed.

GET /avatars/all​

Every listed avatar, paginated

For developers mirroring the catalog. Pages of up to 1000; follow nextOffset until it is null. Cached two minutes.

Parameters​

  • offset (query): Result offset. {"type":"integer","default":0}
  • limit (query): 1-1000. {"type":"integer","default":1000,"maximum":1000}
  • sort (query): Order. {"type":"string","enum":["recent","popular","name"],"default":"recent"}

Responses​

  • 200: One page.

GET /avatars/events​

Live avatar events (server-sent events)

A text/event-stream. Moderation frames are {"type":"blocked"|"unblocked","id":"avtr_...","at":"..."}. Counter frames are {"type":"counts","id":"avtr_...","views":123,"likes":45,"at":"..."}. Use the exact counter values rather than incrementing locally.

Responses​

  • 200: Event stream.

POST /avatars/{id}/track​

Count a view or outbound click

Anonymous. The referring site, device family, CDN country and search term are bucketed for the insights charts; no identifiers are stored.

Parameters​

  • id (path, required): {"type":"string"}

Request body​

{
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"event": {
"type": "string",
"enum": [
"view",
"outbound"
]
},
"source": {
"type": "string",
"description": "Where on the site the click happened, e.g. search, trending, favorites."
},
"platform": {
"type": "string"
},
"term": {
"type": "string",
"description": "The search text that surfaced the avatar, if any."
}
}
}
}
}
}

Responses​

  • 200: Counted.

GET /world/ping​

Liveness plus limits

Responses​

  • 200: Alive.

GET /world/endpoints​

Every world route with its parameters

Responses​

  • 200: Endpoint table.

GET /world/stats​

Catalog counts

Responses​

  • 200: Counters.

GET /world/client​

Public compact world data, up to 120 avatars in one download

Parameters​

  • q (query): Search text {"type":"string"}
  • author (query): Creator name or id {"type":"string"}
  • offset (query): Result offset {"type":"integer","default":0}

Responses​

  • 200: Public catalog, supporters, stats. No account data.

GET /world/search​

Search the catalog

Same index as /avatars/search, with long pages (up to 500) and a nextOffset cursor. Every avatar carries id, name, authorName, authorId, imageUrl, tags, aiTags, platforms, views and likes.

Parameters​

  • q (query): Search text. {"type":"string"}
  • author (query): Creator name or id. {"type":"string"}
  • tag (query): VRChat or AviSeek tag. {"type":"string"}
  • platform (query): pc, quest, or ios. {"type":"string","enum":["pc","quest","ios"]}
  • sort (query): Order. {"type":"string","enum":["popular","recent","oldest","name"],"default":"popular"}
  • limit (query): 1-500. {"type":"integer","default":50,"maximum":500}
  • offset (query): Result offset. {"type":"integer","default":0}

Responses​

  • 200: Avatar list.

GET /world/list​

Browse without a query

Parameters​

  • sort (query): Order. {"type":"string","enum":["popular","recent","oldest","name"],"default":"popular"}
  • platform (query): pc, quest, or ios. {"type":"string"}
  • limit (query): 1-500. {"type":"integer","default":50,"maximum":500}
  • offset (query): Result offset. {"type":"integer","default":0}

Responses​

  • 200: Avatar list.

GET /world/all​

Every visible avatar, 1000 per page

Parameters​

  • offset (query): Result offset. {"type":"integer","default":0}
  • limit (query): 1-1000. {"type":"integer","default":1000}

Responses​

  • 200: Avatar list.

GET /world/trending​

Rising this week

Parameters​

  • limit (query): 1-500. {"type":"integer","default":24}

Responses​

  • 200: Avatar list.

GET /world/promoted​

Paid avatar placements

Parameters​

  • limit (query): 1-50. {"type":"integer","default":24}

Responses​

  • 200: Avatar list.

GET /world/promoted/groups​

Paid VRChat group placements

Parameters​

  • limit (query): 1-50. {"type":"integer","default":12}

Responses​

  • 200: Group list.

GET /world/random​

A different handful each call

Parameters​

  • limit (query): 1-500. {"type":"integer","default":24}
  • platform (query): pc, quest, or ios. {"type":"string"}

Responses​

  • 200: Avatar list.

GET /world/avatars​

Several avatars by id

Parameters​

  • ids (query): Comma separated avtr_ ids, up to 500. {"type":"string"}

Responses​

  • 200: Avatar list plus missing ids.

GET /world/avatar/{id}​

One avatar (counts a view)

Parameters​

  • id (path, required): {"type":"string"}

Responses​

  • 200: Avatar.
  • 404: Unknown avatar.

GET /world/tags​

Most used tags

Parameters​

  • limit (query): 1-200. {"type":"integer","default":50}

Responses​

  • 200: Tags with counts.

GET /world/searches​

Popular search terms

Parameters​

  • limit (query): 1-50. {"type":"integer","default":10}

Responses​

  • 200: Terms.

GET /world/leaderboard​

Richest accounts

Parameters​

  • limit (query): 1-50. {"type":"integer","default":10}

Responses​

  • 200: Rank, username, balance, avatar.

GET /world/supporters​

Supporters board

Active Patreon members of VRChat Legends with their tier title. Cached ten minutes.

Parameters​

  • limit (query): 1-200. {"type":"integer","default":50}

Responses​

  • 200: Name and tier per supporter.

GET /world/admins​

Display names the world may treat as staff

Responses​

  • 200: Admin list.

GET /world/bundle​

Several sections in one call

Parameters​

  • include (query): Comma separated: stats, trending, promoted, promotedGroups, random, tags, searches, leaderboard, admins, supporters. {"type":"string"}
  • limit (query): Rows per list section. {"type":"integer","default":24}

Responses​

  • 200: { sections: { name: payload } }.
  • 400: No valid section named.

Shared schemas​

{
"schemas": {
"Avatar": {
"type": "object",
"properties": {
"id": {
"type": "string",
"example": "avtr_00000000-1111-2222-3333-444444444444"
},
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"authorId": {
"type": "string"
},
"authorName": {
"type": "string"
},
"authorVrclUrl": {
"type": [
"string",
"null"
],
"description": "Public VRChat Legends profile, when the creator has a verified one."
},
"imageUrl": {
"type": "string",
"description": "VRChat CDN thumbnail. Empty when we have no image we are allowed to serve."
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "Tags as published by VRChat."
},
"aiTags": {
"type": "array",
"items": {
"type": "string"
},
"description": "Tags AviSeek derived from the name and description, drawn from a fixed vocabulary."
},
"platforms": {
"type": "array",
"items": {
"type": "string",
"enum": [
"pc",
"quest",
"ios"
]
}
},
"performance": {
"type": "object",
"additionalProperties": {
"type": "string",
"enum": [
"Excellent",
"Good",
"Medium",
"Poor",
"Very Poor"
]
},
"description": "Known per-platform ratings keyed by pc, quest or ios. Missing means unknown."
},
"addedAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"views": {
"type": "integer"
},
"likes": {
"type": "integer"
}
}
},
"Error": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
},
"WorldList": {
"type": "object",
"properties": {
"ok": {
"type": "boolean"
},
"now": {
"type": "string",
"format": "date-time"
},
"total": {
"type": "integer"
},
"count": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"nextOffset": {
"type": [
"integer",
"null"
],
"description": "Pass as `offset` for the next page; null when done."
},
"avatars": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Avatar"
}
}
}
}
},
"securitySchemes": {
"sessionCookie": {
"type": "apiKey",
"in": "cookie",
"name": "aviseek_session",
"description": "Set by signing in with VRChat Legends."
}
}
}