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
missingids.
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."
}
}
}