HTTP Integration API Reference
A local HTTP API for reading and writing VRCPersona's data from external tools (OBS, overlays, bots, and the like).
License-only feature
Setup
- Open Settings → HTTP Integration tab
- Enable the control server and the data server individually
- Change the ports if needed (default: control 11020 / data 11021)
- Copy the authentication token from “Authentication & Access Control” and set it in your external tool
Both servers bind only to 127.0.0.1 (local access only). Bearer token authentication is required on all requests (see “Authentication” below).
Authentication
All requests to the local HTTP servers require authentication with a Bearer token. Requests without a token, or with a token that doesn't match, are rejected with 401 Unauthorized.
- You can view, copy, and regenerate the token in the app under Settings → HTTP Integration → Authentication & Access Control
- Add an
Authorization: Bearer <token>header to requests from your external tool - Do not share the token with third parties. If you regenerate it, you'll also need to update the settings on the external tool side
Example of putting the copied token in an environment variable:
TOKEN="<token copied from the settings screen>"
curl http://127.0.0.1:11021/api/groups \
-H "Authorization: Bearer $TOKEN" Note: For brevity, the curl examples below omit the Authorization header. In practice, add it to every request.
Using it from a page in a browser
Only when calling from a web page in a browser (JavaScript) do you need to add that page's origin (e.g. http://localhost:3000) to “Allowed Origins” in the settings. Non-browser external tools (OBS, bots, scripts, etc.) don't need to register an origin and work with the token alone.
Data server (read)
Default port: 11021
External tools retrieve VRCPersona's data with GET. The data is refreshed every 5 seconds.
User-Agent header (required)
Requests to the data server's retrieval endpoints (/api/groups / /api/friends / /api/notifications) require a non-empty User-Agent header. Requests with no header, or an empty one, are rejected with 400 Bad Request (/api/health is exempt, as it's for connectivity checks). The control server (11020) does not require a User-Agent.
The User-Agent you specify is passed on to VRChat in the form VRCPersona/<version> (via HTTP API; <your UA>) … whenever VRCPersona queries the VRChat API on your behalf. This is meant to make clear to VRChat which external tool is accessing it via VRCPersona. Please set a value that identifies the tool name and version (e.g. MyOverlay/1.0).
Many HTTP clients, including curl, send a User-Agent by default, so they work without any extra configuration. Only add it explicitly when your client doesn't send a User-Agent, such as the browser's fetch or a custom client.
curl http://127.0.0.1:11021/api/friends \
-H "Authorization: Bearer $TOKEN" \
-H "User-Agent: MyOverlay/1.0" GET /api/groups
Retrieves the list of groups (server groups + local groups).
curl http://127.0.0.1:11021/api/groups Example response:
[
{
"id": "d7569eaf-9673-4d5b-922e-5d868a048593",
"name": "Favorites",
"color": "#1e65d3",
"icon": null,
"storage": "server",
"statusColor": "blue",
"statusMessage": "Everyone welcome",
"memberIds": ["usr_xxx", "usr_yyy"]
},
{
"id": "local_0211fdf8-dbea-4f3b-a5b3-caca9d2c1d80",
"name": "Local Group",
"color": "#c43fe0",
"icon": null,
"storage": "local",
"statusColor": null,
"statusMessage": null,
"memberIds": ["usr_aaa", "usr_bbb"]
}
] | Field | Type | Description |
|---|---|---|
| id | string | Group ID |
| name | string | Group name |
| color | string | null | Group color (HEX) |
| icon | string | null | Group icon |
| storage | string | null | "server" or "local" |
| statusColor | string | null | The status color you set (blue / green / orange / red / null) |
| statusMessage | string | null | The status message you set |
| memberIds | string[] | List of members' VRChat User IDs |
GET /api/friends
Retrieves the list of friends.
curl http://127.0.0.1:11021/api/friends Example response:
[
{
"id": "usr_xxx",
"displayName": "Username",
"status": "green",
"statusDescription": "Hanging out",
"location": "wrld_xxx:12345~friends(usr_yyy)~region(jp)",
"groups": ["d7569eaf-..."],
"priority": 3.1,
"platform": "standalonewindows",
"avatarThumbnail": "https://...",
"userIcon": null,
"minutesAtLocation": 42,
"instanceInfo": {
"worldId": "wrld_xxx",
"worldName": "My World",
"instanceId": "12345~friends(usr_yyy)~region(jp)",
"userCount": 8,
"capacity": 32,
"instanceType": "Friends",
"region": "jp"
}
}
] | Field | Type | Description |
|---|---|---|
| id | string | VRChat User ID |
| displayName | string | Display name |
| status | string | VRChat status (blue / green / orange / red) |
| statusDescription | string | null | Status message |
| location | string | null | Current location (wrld_xxx:instanceId format) |
| groups | string[] | List of group IDs the friend belongs to |
| priority | number | Sort priority (lower comes first) |
| platform | string | null | Platform (standalonewindows / android / web, etc.) |
| avatarThumbnail | string | null | Avatar thumbnail URL |
| userIcon | string | null | User icon URL (automatically populated when retrieved with ?include=raw) |
| minutesAtLocation | number | null | Minutes spent in the current instance |
| instanceInfo | object | null | Instance details (see below) |
instanceInfo
| Field | Type | Description |
|---|---|---|
| worldId | string | World ID |
| worldName | string | World name |
| instanceId | string | Instance ID |
| userCount | number | null | Current headcount |
| capacity | number | null | Capacity |
| instanceType | string | Instance type (Public / Friends / Friends+, etc.) |
| region | string | null | Region (jp / us / eu, etc.) |
Optional information (?include= query parameter)
Specifying comma-separated values with ?include= attaches additional information to each friend.
curl "http://127.0.0.1:11021/api/friends?include=groups"
curl "http://127.0.0.1:11021/api/friends?include=raw,groups,me"
curl "http://127.0.0.1:11021/api/friends?include=groups&fresh=true" | include value | Added field | Description |
|---|---|---|
raw | rawUser | The full JSON of the VRChat user details (bio, tags, dateJoined, etc.) |
groups | vrchatGroupIds | An array of VRChat group IDs the user belongs to |
worlds | userWorldIds | An array of world IDs the user has created |
mutuals | mutualFriends | List of mutual friends |
me | Adds yourself to the start of the array | Includes an "isMe": true flag along with the basic fields (displayName, etc.) |
?fresh=true: Ignores the cache and fetches the latest information from the VRChat API. When omitted, the cache is used (valid for 7 days).
Note: Specifying include triggers a VRChat API query for each friend, so the response may take some time. On a cache hit, the response is immediate.
GET /api/notifications
Retrieves the list of VRChat notifications.
curl http://127.0.0.1:11021/api/notifications Example response:
[
{
"id": "not_xxx",
"type": "invite",
"senderUserId": "usr_xxx",
"senderUsername": "Username",
"message": "",
"details": {
"worldId": "wrld_xxx",
"worldName": "World name",
"inviteMessage": "Come hang out!"
},
"created_at": "2026-03-20T17:52:28.263Z",
"seen": false
}
] GET /api/health
Check that the server is running.
curl http://127.0.0.1:11021/api/health Response:
{"status": "ok"} Control server (write)
Default port: 11020
Change VRCPersona's group status from an external tool.
PUT /api/groups/:groupId/status
Changes a group's status (color and message).
curl -X PUT http://127.0.0.1:11020/api/groups/{groupId}/status \
-H "Content-Type: application/json" \
-d '{"status_color":"blue","status_message":"Streaming"}' Request body:
| Field | Type | Description |
|---|---|---|
| status_color | string | null | "blue" / "green" / "orange" / "red" / null (clear) |
| status_message | string | null | Status message (optional) |
Response: 202 Accepted
{"ok": true} The change is applied asynchronously to the VRCPersona server.
Example of clearing the status:
curl -X PUT http://127.0.0.1:11020/api/groups/{groupId}/status \
-H "Content-Type: application/json" \
-d '{"status_color":null,"status_message":null}' GET /api/health
curl http://127.0.0.1:11020/api/health Notes
- An authentication token is required on all requests (
401if unset or mismatched). Manage the token in the app under “Settings → HTTP Integration → Authentication & Access Control.” - The data server's retrieval endpoints (
/api/groups,/api/friends,/api/notifications) also require aUser-Agentheader (400if empty or absent;/api/healthand the control server are exempt). The value you specify is passed on as the origin when querying VRChat (details) - For
groupId, use theidfield obtained from the data server'sGET /api/groups - Status changes only work for server groups (local groups don't support status sharing)
- The data server's data is refreshed every 5 seconds, so there can be up to a 5-second delay
- When the paid plan becomes inactive, the servers stop automatically
- If a port conflicts with another app, it can be changed in the settings screen (1024–65535)