vrchatapi/specification

Document Group API

Foorack opened this issue · 10 comments

Written by @ariesclark and @Miner28. Moving to an Issue to better track information across PR's.

Group API

Research Stage

List User Groups GET /users/<user-id>/groups

Response (example)

[
  {
    "id": "gmem_<uuid>",
    "groupId": "grp_<uuid>",
    "memberVisibility": "visible",
    "mutualGroup": true,
    "name": "...",
    "shortCode": "...",
    "discriminator": "0000",
    "description": "...",
    "iconUrl": "https://api.vrchat.cloud/api/1/file/file_<uuid>/1/file",
    "bannerUrl": "https://assets.vrchat.com/www/groups/default_banner.png",
    "privacy": "default",
    "ownerId": "usr_<uuid>",
    "memberCount": 1
  }
]

Create Group POST /groups

Body (example)

{
  "name": "...",
  "shortCode": "...",
  "description": "",
  "joinState": "open",
  "privacy": "default",
  "roleTemplate": "managedFree",
  "bannerId": null,
  "iconId": null
}

name must be at least 3 characters.
shortCode must be at least 3 characters.
joinState one of closed, invite, request, open.
privacy one of default, private.
roleTemplate one of default, managedFree, managedInvite, managedRequest.

Response

Group Object


Get Group GET groups/<group-id>

Query parameters

includeRoles optional boolean, default false.

Response (example)

{
  "id": "grp_<uuid>",
  "name": "...",
  "shortCode": "...",
  "discriminator": "0000",
  "description": "My exciting new group. It's pretty nifty!",
  "iconUrl": "https://assets.vrchat.com/www/groups/default_icon.png",
  "bannerUrl": "https://assets.vrchat.com/www/groups/default_banner.png",
  "privacy": "default",
  "ownerId": "<user-id>",
  "links": [],
  "languages": [],
  "iconId": null,
  "bannerId": null,
  "memberCount": 1,
  "memberCountSyncedAt": "2022-12-01T01:50:50.918Z",
  "isVerified": false,
  "joinState": "open",
  "tags": [],
  "galleries": [],
  "createdAt": "2022-12-01T01:50:50.918Z",
  "initialRoleIds": [
    "grol_<uuid>"
  ],
  "updatedAt": "2022-12-01T01:50:50.918Z",
  "membershipStatus": "member",
  "myMember": {
    "id": "gmem_<uuid>",
    "groupId": "grp_<uuid>",
    "userId": "usr_<uuid>",
    "isRepresenting": false,
    "roleIds": [
      "grol_<uuid>"
    ],
    "joinedAt": "2022-12-01T01:50:50.918Z",
    "membershipStatus": "member",
    "visibility": "visible",
    "isSubscribedToAnnouncements": true,
    "createdAt": "2022-12-01T01:50:50.918Z",
    "managerNotes": "",
    "has2FA": false,
    "permissions": [
      "*"
    ]
  }
}

Update Group PUT groups/<group-id>

Body

{
  "name": "...",
  "shortCode": "...",
  "description": "...",
  "joinState": "open",
  "language": [],
  "links": [],
  "rules": null
}

Response

Group Object


Delete Group DELETE groups/<group-id>

Response

{
  "success": {
    "message": "Group deleted!",
    "status_code": 200
  }
}

List Group Galleries GET /groups/<group-id>/galleries/<group-gallery-id>

Query parameters

looks like the website sends a duplicate parameter galleryId
typical pagination: n, offset.

Response (example)

[
  {
    "id": "ggim_<uuid>",
    "groupId": "grp_<uuid>",
    "galleryId": "ggal_<uuid>",
    "fileId": "file_<uuid>",
    "imageUrl": "https://api.vrchat.cloud/api/1/file/file_<uuid>/1/file",
    "createdAt": "2022-12-01T09:02:57.166Z",
    "submittedByUserId": "usr_<uuid>",
    "approved": true,
    "approvedByUserId": "usr_<uuid>",
    "approvedAt": "2022-12-01T09:02:57.166Z"
  }
]

List Group Roles GET /groups/<group-id>/roles

Response (example)

[
  {
    "id": "grol_<uuid>",
    "groupId": "grp_<uuid>",
    "name": "Group Owner",
    "description": "The owner of this group.",
    "isSelfAssignable": false,
    "permissions": [
      "*"
    ],
    "isManagementRole": true,
    "requiresTwoFactor": false,
    "requiresPurchase": false,
    "order": 0,
    "createdAt": "2022-12-01T09:55:53.794Z",
    "updatedAt": "2022-12-01T09:55:53.794Z"
  }
]

Create Group Role POST /groups/<group-id>/roles

Body (example)

{
  "id": "...",
  "name": "...",
  "description": "",
  "isSelfAssignable": true,
  "permissions": []
}

Response

Group Role Object


Update Group Role PUT /groups/<group-id>/roles/<group-role-id>

Body

assuming same properties as creation.

Response

Array of existing role objects.


Delete Group Role DELETE /groups/<group-id>/roles/<group-role-id>

Body (example)

{
  "id": "...",
  "name": "...",
  "description": "",
  "isSelfAssignable": true,
  "permissions": []
}

Response

Array of existing role objects.


List Group Invites GET /groups/<group-id>/invites

Query parameters

typical pagination: n, offset.

Response (example)

[
  {
    "id": "gmem_<uuid>",
    "groupId": "grp_<uuid>",
    "userId": "usr_<uuid>",
    "isRepresenting": false,
    "user": {
      "id": "usr_<uuid>",
      "displayName": "...",
      "thumbnailUrl": "https://api.vrchat.cloud/api/1/file/file_<uuid>/1/file",
      "iconUrl": ""
    },
    "roleIds": [],
    "joinedAt": null,
    "membershipStatus": "invited",
    "visibility": "visible",
    "isSubscribedToAnnouncements": true,
    "createdAt": "2022-12-01T09:45:00.813Z",
    "bannedAt": null,
    "managerNotes": ""
  }
]

Create Group Invite POST /groups/<group-id>/invites

Body (example)

{
  "userId": "usr_<uuid>",
  "confirmOverrideBlock": true
}

Delete Group Invite DELETE /groups/<group-id>/invites/<user-id>

Response

{
  "success": {
    "message": "... has been uninvited.",
    "status_code": 200
  }
}

List Group Requests GET /groups/<group-id>/requests

Query parameters

typical pagination: n, offset.


List Group Members GET /groups/<group-id>/members

Query parameters

typical pagination: n, offset.

Response (example)

[
  {
    "id": "gmem_<uuid>",
    "groupId": "grp_<uuid>",
    "userId": "usr_<uuid>"
    "isRepresenting": false,
    "user": {
      "id": "usr_<uuid>",
      "displayName": "...",
      "thumbnailUrl": "https://api.vrchat.cloud/api/1/image/file_<uuid>",
      "iconUrl": "https://api.vrchat.cloud/api/1/file/file_<uuid>/1"
    },
    "roleIds": [
      "grol_<uuid>",
    ]
  }
]

List Group Audit Log GET /groups/<group-id>/auditLogs

Query parameters

typical pagination: n, offset.

Response (example)

{
  "results": [
    {
      "id": "gaud_<uuid>",
      "created_at": "2022-12-01T09:31:16.297Z",
      "groupId": "grp_<uuid>",
      "actorId": "usr_<uuid>",
      "actorDisplayName": "...",
      "targetId": "grp_<uuid>",
      "eventType": "group.create",
      "description": "Group test created by <displayName>.",
      "data": {
        "name": "...",
        "shortCode": "...",
        "description": "...",
        "rules": null,
        "bannerId": null,
        "iconId": "file_<uuid>",
        "joinState": "open",
        "privacy": "default",
        "ownerId": "usr_<uuid>",
        "iconVersion": 1,
        "galleries": [
          {
            "_id": "ggal_<uuid>",
            "_created_at": "2022-12-01T09:31:16.297Z",
            "_updated_at": "2022-12-01T09:31:16.297Z",
            "name": "Banner and Icon",
            "description": "A gallery to store your group's banner and icon. You can edit this any way you like later.",
            "membersOnly": true
          }
        ]
      }
    }
  ],
  "totalCount": 1,
  "hasNext": false
}

List Group Permissions GET /groups/<group-id>/permissions

Response (example)

[
  {
    "name": "group-members-manage",
    "displayName": "Manage Group Member Data",
    "help": "Allows role to view all members and edit data about them.",
    "isManagementPermission": true,
    "allowedToAdd": true
  }
]

Get Group Announcement GET /groups/<group-id>/announcement

Response (example)

Empty object when no announcement or

{
  "id": "gpos_<uuid>",
  "groupId": "grp_<uuid>",
  "authorId": "usr_<uuid>",
  "title": "a",
  "text": "a",
  "imageId": null,
  "imageUrl": null,
  "createdAt": "2022-12-01T09:12:48.487Z",
  "updatedAt": "2022-12-01T09:12:48.487Z"
}

Set Group Announcement POST /groups/<group-id>/announcement

Body

{
  "text": "...",
  "title": "...",
  "imageId": null,
  "sendNotification": false
}

Response

Group Announcement Object


List Group Instances GET /groups/<group-id>/instances


Delete Join Request DELETE /groups/<group-id>/requests


Create Join Request POST /groups/<group-id>/join


List Group Role Templates GET /groups/roleTemplates


Feedback API? /users/<user-id>/feedback?contentId=<group-id>&metadata=true


POST /feedbacks/group/<group-id>

{
   "type":"report",
   "reasons":[
      "inappropriate",
      "abusive_disruptive",
      "malicious",
      "hateful"
   ],
   "locations":[
      "group_name",
      "group_description",
      "image_banner_gallery_icon",
      "group_user_behaviour"
   ],
   "description":"a"
}

RESPONSE

{
   "id":"feedback_<uuid>",
   "type":"report",
   "reasons":[
      "inappropriate",
      "abusive_disruptive",
      "malicious",
      "hateful"
   ],
   "locations":[
      "group_name",
      "group_description",
      "image_banner_gallery_icon",
      "group_user_behaviour"
   ],
   "description":"Report comment",
   "commenterId":"usr_<uuid>",
   "commenterName":"<displayname>",
   "contentId":"grp_<uuid>",
   "contentType":"group",
   "contentName":"test",
   "tags":[
      
   ]
}

PUT /groups/<groupId>/requests/<userId>

{
  "action":"accept" | "reject"
}

ACCEPT RESPONSE

{
    "success": {
        "message": "Successfully accepted <display-name> request.",
        "status_code": 200
    }
}

REJECT RESPONSE

{
    "success": {
        "message": "Successfully rejected <display-name> request.",
        "status_code": 200
    }
}

Name and shortcode follow standard sanitation functions:

{
	"error": {
		"message": "name did not pass sanitization‚ shortCode did not pass sanitization",
		"status_code": 400
	}
}

/groups/<group-id>/galleries

{name: "E1", description: "E2", membersOnly: false}

name - required

Keeping this open as groups/<groupId>/instances is missing, but that is broken.

And /groups/strictsearch?query=abc

All GroupMember examples in the documentation have "membershipStatus": "member". The GroupMember examples displayed for the respective endpoints should be updated to display:

  • When in response to a join request or representing a user who requested to join, but whose request hasn't yet been approved: "membershipStatus": "requested".
  • When in response to requesting the list of invited members who haven't yet accepted their invites ("Sent" in the website): "membershipStatus": "invited"
  • When the user is banned (in addition to the ban date): "membershipStatus": "banned"

(all of these have been tested.)

Maybe one more thing: They changed recently that you can create multiple announcements, so Endpoint /groups/{groupId}/announcement is not used by VRC anymore (at least on website) instead they have new Posts Apis:

Read Posts

GET /groups/{groupId}/posts?n=10&offset=0&publicOnly=false
Reponse looks like:

{
    "total": 2,
    "posts": [
        {
            "id": "not_163d458f-f9a5-4ff8-9ce7-5879777997b4",
            "groupId": "grp_03c380ba-812a-4b3a-ae65-f0da222fc103",
            "authorId": "usr_362cdd9f-3b2d-4ff5-afa7-65a2d3f8435d",
            "editorId": null,
            "visibility": "public",
            "roleIds": [],
            "title": "asd",
            "text": "asd",
            "imageId": null,
            "imageUrl": null,
            "createdAt": "2024-03-31T14:07:53.188Z",
            "updatedAt": "2024-03-31T14:07:53.188Z"
        },
        {
            "id": "not_ec21137e-2271-4e8a-8216-f0a008033f1d",
            "groupId": "grp_03c380ba-812a-4b3a-ae65-f0da222fc103",
            "authorId": "usr_362cdd9f-3b2d-4ff5-afa7-65a2d3f8435d",
            "editorId": null,
            "visibility": "group",
            "roleIds": [],
            "title": "test",
            "text": "test please ignore",
            "imageId": null,
            "imageUrl": null,
            "createdAt": "2024-03-31T14:07:37.504Z",
            "updatedAt": "2024-03-31T14:07:37.504Z"
        }
    ]
}

To Create a new Post:

POST /groups/{groupId}/posts
Body:

{
    "text":"test",
    "title":"Yeah I am sry need one more test",
    "imageId":null,
    "sendNotification":true,
    "roleIds":["grol_a1be5571-4a46-42c7-ae84-7a1e1262ee68","grol_e1ec0583-a87b-4b46-a0e0-86b4dfc1361b"],
    "visibility":"group"
}

Response:

{
    "id": "not_a9f5f5d8-c1c7-4d1c-a228-f1b6140d29b5",
    "groupId": "grp_03c380ba-812a-4b3a-ae65-f0da222fc103",
    "authorId": "usr_362cdd9f-3b2d-4ff5-afa7-65a2d3f8435d",
    "editorId": null,
    "visibility": "group",
    "roleIds": [
        "grol_a1be5571-4a46-42c7-ae84-7a1e1262ee68",
        "grol_e1ec0583-a87b-4b46-a0e0-86b4dfc1361b"
    ],
    "title": "Yeah I am sry need one more test",
    "text": "test",
    "imageId": null,
    "imageUrl": null,
    "createdAt": "2024-03-31T14:11:41.923Z",
    "updatedAt": "2024-03-31T14:11:41.923Z"
}

Delete Post

DELETE /groups/{groupId}/posts/not_a9f5f5d8-c1c7-4d1c-a228-f1b6140d29b5

Reponse:

{
    "success": {
         "message":"Group post was deleted!",
         "status_code":200
     }
}

Fields:

  • visibility can be "public" or "group" - can be filtered on get request with publicOnly=true
  • roleIds can contain Ids only if visibility is "group" - therefore post is only shown inside group to defined roles
  • don't know what editorId is on Reponses....