diff --git a/PluralKit.Core/Models/PKSystem.cs b/PluralKit.Core/Models/PKSystem.cs index 4d770783..3512669b 100644 --- a/PluralKit.Core/Models/PKSystem.cs +++ b/PluralKit.Core/Models/PKSystem.cs @@ -104,6 +104,7 @@ namespace PluralKit.Core p.Add("description_privacy", system.DescriptionPrivacy.ToJsonString()); p.Add("member_list_privacy", system.MemberListPrivacy.ToJsonString()); + p.Add("group_list_privacy", system.GroupListPrivacy.ToJsonString()); p.Add("front_privacy", system.FrontPrivacy.ToJsonString()); p.Add("front_history_privacy", system.FrontHistoryPrivacy.ToJsonString()); diff --git a/PluralKit.Core/Models/Patch/SystemPatch.cs b/PluralKit.Core/Models/Patch/SystemPatch.cs index 486fa778..50802a1b 100644 --- a/PluralKit.Core/Models/Patch/SystemPatch.cs +++ b/PluralKit.Core/Models/Patch/SystemPatch.cs @@ -110,6 +110,9 @@ namespace PluralKit.Core if (privacy.ContainsKey("member_list_privacy")) patch.DescriptionPrivacy = patch.ParsePrivacy(privacy, "member_list_privacy"); + if (privacy.ContainsKey("group_list_privacy")) + patch.GroupListPrivacy = patch.ParsePrivacy(privacy, "group_list_privacy"); + if (privacy.ContainsKey("front_privacy")) patch.DescriptionPrivacy = patch.ParsePrivacy(privacy, "front_privacy"); diff --git a/docs/content/.vuepress/config.js b/docs/content/.vuepress/config.js index 739ed9cf..8764b640 100644 --- a/docs/content/.vuepress/config.js +++ b/docs/content/.vuepress/config.js @@ -41,7 +41,6 @@ module.exports = { "/getting-started", "/user-guide", "/command-list", - "/api-documentation", "/privacy-policy", "/faq", "/tips-and-tricks" @@ -58,6 +57,19 @@ module.exports = { "/staff/compatibility", ] }, + { + title: "API Documentation", + collapsable: false, + children: [ + "/api/changelog", + "/api/reference", + "/api/endpoints", + "/api/models", + "/api/errors", + // "/api/integrations", + "/api/legacy" + ] + }, ["https://discord.gg/PczBt78", "Join the support server"], ] }, diff --git a/docs/content/api/changelog.md b/docs/content/api/changelog.md new file mode 100644 index 00000000..b98dc260 --- /dev/null +++ b/docs/content/api/changelog.md @@ -0,0 +1,33 @@ +--- +title: Changelog +permalink: /api/changelog +--- + +# Version history + +* 2020-07-28 + * The unversioned API endpoints have been removed. +* 2020-06-17 (v1.1) + * The API now has values for granular member privacy. The new fields are as follows: `visibility`, `name_privacy`, `description_privacy`, `avatar_privacy`, `birthday_privacy`, `pronoun_privacy`, `metadata_privacy`. All are strings and accept the values of `public`, `private` and `null`. + * The `privacy` field has now been deprecated and should not be used. It's still returned (mirroring the `visibility` field), and writing to it will write to *all privacy options*. +* 2020-05-07 + * The API (v1) is now formally(ish) defined with OpenAPI v3.0. [The definition file can be found here.](https://github.com/xSke/PluralKit/blob/master/PluralKit.API/openapi.yaml) +* 2020-02-10 + * Birthdates with no year can now be stored using `0004` as a year, for better leap year support. Both options remain valid and either may be returned by the API. + * Added privacy set/get support, meaning you will now see privacy values in authed requests and can set them. +* 2020-01-08 + * Added privacy support, meaning some responses will now lack information or return 403s, depending on the specific system and member's privacy settings. +* 2019-12-28 + * Changed behaviour of missing fields in PATCH responses, will now preserve the old value instead of clearing + * This is technically a breaking change, but not *significantly* so, so I won't bump the version number. +* 2019-10-31 + * Added `proxy_tags` field to members + * Added `keep_proxy` field to members + * Deprecated `prefix` and `suffix` member fields, will be removed at some point (tm) +* 2019-07-17 + * Added endpoint for querying system by account + * Added endpoint for querying message contents +* 2019-07-10 **(v1)** + * First specified version +* (prehistory) + * Initial release diff --git a/docs/content/api/endpoints.md b/docs/content/api/endpoints.md new file mode 100644 index 00000000..ad378dbf --- /dev/null +++ b/docs/content/api/endpoints.md @@ -0,0 +1,261 @@ +--- +name: Endpoints +permalink: /api/endpoints +--- + +# Endpoints + +The base URL for the PluralKit API is `https://api.pluralkit.me/v2`. Endpoint URLs should be added to the base URL to get a full URL to query. + +--- +## Systems + +*`systemRef` can be a system's short (5-character) ID, a system's UUID, the ID of a Discord account linked to the system, or the string `@me` to refer to the currently authenticated system.* + +### Get System + +GET `/systems/{systemRef}` + +Returns a [system object](/api/models#system-model). + +### Update System + +PATCH `/systems/{systemRef}` + +Takes a partial [system object](/api/models#system-model). + +Returns a [system object](/api/models#system-model). + +### Get System Guild Settings + +GET `/systems/@me/guilds/{guild_id}` + +Returns a [system guild settings](/api/models#system-guild-settings) object. + +::: note +You must already have updated per-guild settings for your system in the target guild before being able to get or update them from the API. +::: + +### Update System Guild Settings + +PATCH `/systems/@me/guilds/{guild_id}` + +Takes a partial [system guild settings](/api/models#system-guild-settings) object. + +Returns a [system guild settings](/api/models#system-guild-settings) object on success. + +--- +## Members + +*`memberRef` can be a member's short (5-character ID) or a member's UUID.* + +### Get System Members + +GET `/systems/{systemRef}/members` + +Returns a list of [member objects](/api/models#member-model). + +### Create Member + +POST `/members` + +Takes a partial [member object](/api/models#member-model) as input. Key `name` is required. + +Returns a [member object](/api/models#member-model) on success. + +### Get Member + +GET `/members/{memberRef}` + +Returns a [member object](/api/models#member-model). + +### Update Member + +PATCH `/members/{memberRef}` + +Takes a partial [member object](/api/models#member-model) as input. + +Returns a [member object](/api/models#member-model) on success. + +### Delete Member + +DELETE `/members/{memberRef}` + +Returns 204 No Content on success. + +### Get Member Groups + +GET `/members/{memberRef}/groups` + +### Add Member To Groups + +PUT `/members/{memberRef}/groups` + +::: warn +Not all HTTP implementations support PUT requests with a body. If yours does not, consider using the [Add Member To Group](#add-member-to-group) endpoint instead. +::: + +### Remove Member From Groups + +DELETE `/members/{memberRef}/groups` + +::: warn +Not all HTTP implementations support DELETE requests with a body. If yours does not, consider using the [Remove Member From Group](#remove-member-from-group) endpoint instead. +::: + +### Get Member Guild Settings + +GET `/members/{memberRef}/guilds/{guild_id}` + +Returns a [member guild settings](/api/models#member-guild-settings) object. + +::: note +You must already have updated per-guild settings for a member in the target guild before being able to get or update them from the API. +::: + +### Update Member Guild Settings + +PATCH `/members/{memberRef}/guilds/{guild_id}` + +Takes a partial [member guild settings](/api/models#member-guild-settings) object. + +Returns a [member guild settings](/api/models#member-guild-settings) object on success. + +--- +## Groups + +*`groupRef` can be a group's short (5-character ID) or a group's UUID.* + +### Get System Groups + +GET `/systems/{systemRef}/groups` + +Returns a list of [group objects](/api/models/#group-model). + +### Create Group + +POST `/groups` + +Takes a partial [group object](/api/models#group-model) as input. Key `name` is required. + +Returns a [group object](/api/models#group-model) on success, or an error object on failure. + +### Get Group + +GET `/groups/{groupRef}` + +Returns a [group object](/api/models/#group-model). + +### Update Group + +PATCH `/groups/{groupRef}` + +Takes a partial [group object](/api/models#group-model) as input. + +Returns a [group object](/api/models#group-model) on success, or an error object on failure. + +### Delete Group + +DELETE `/groups/{groupRef}` + +Returns 204 No Content on success. + +### Get Group Members + +GET `/groups/{groupRef}/members` + +### Add Member To Group + +PUT `/groups/{groupRef}/members/{memberRef}` + +### Add Members To Group + +PUT `/groups/{groupRef}/members` + +::: warn +Not all HTTP implementations support PUT requests with a body. If yours does not, consider using the [Add Member To Group](#add-group-member) endpoint instead. +::: + +### Remove Member From Group + +DELETE `/groups/{groupRef}/members/{memberRef}` + +### Remove Members From Group + +DELETE `/groups/{groupRef}/members` + +::: warn +Not all HTTP implementations support DELETE requests with a body. If yours does not, consider using the [Remove Member From Group](#remove-member-from-group) endpoint instead. +::: + + +--- +## Switches + +*`switchRef` must be a switch's UUID. On POST/PATCH/DELETE endpoints, `systemRef` must be `@me`.* + +### Get System Switches + +GET `/systems/{systemRef}/switches` + +Query String Parameters + +|key|type|description| +|---|---|---| +|before|timestamp|date to get latest switch from (inclusive)| +|limit|int|number of switches to get| + +Returns a [switch object](/api/models#switch-model) containing a list of IDs. + +### Get Current System Fronters + +GET `/systems/{systemRef}/fronters` + +Returns a [switch object](/api/models#switch-model) containing a list of member objects. + +### Create Switch + +POST `/systems/{systemRef}/switches` + +JSON Body Parameters + +|key|type|description| +|---|---|---| +|?timestamp|datetime*|when the switch started| +|members|list of strings**|members present in the switch (or empty list for switch-out)| + +* Defaults to "now" when missing. + +** Can be short IDs or UUIDs. + +### Get Switch + +GET `/systems/{systemRef}/switches/{switchRef}` + +Returns a [switch object](/api/models#switch-model) containing a list of member objects. + +### Update Switch + +PATCH `/systems/{systemRef}/switches/{switchRef}` + +JSON Body Parameters + +|key|type|description| +|---|---|---| +|timestamp|datetime|when the switch started| + +Returns a [switch object](/api/models#switch-model) containing a list of member objects on success. + +### Update Switch Members + +PATCH `/systems/{systemRef}/switches/{switchRef}/members` + +Takes a list of member short IDs or UUIDs as input. + +Returns a [switch object](/api/models#switch-model) containing a list of member objects on success. + +### Delete Switch + +DELETE `/systems/{systemRef}/switches/{switchRef}` + +Returns 204 No Content on success. \ No newline at end of file diff --git a/docs/content/api/errors.md b/docs/content/api/errors.md new file mode 100644 index 00000000..c4d5ec97 --- /dev/null +++ b/docs/content/api/errors.md @@ -0,0 +1,57 @@ +--- +title: Errors and Status Codes +permalink: /api/errors +--- + +# Errors and Status Codes + +When something goes wrong, the API will send back a 4xx HTTP status code, along with a JSON object describing the error. + +### Error Response Model + +|key|type|description| +|---|---|---| +|code|int|numerical error code| +|message|string|description of the error| +|?errors|map of keys to error objects*|details on the error| +|?retry_after|int|if this is a rate limit error, the number of milliseconds after which you can retry the request| + +* Only returned for model parsing errors. Values can be individual error objects, or arrays of error objects. + +### Error Object + +|key|type|description| +|---|---|---| +|message|string|error description| +|?max_length|int|if this is an error indicating a key is too long, the maximum allowed length for the key| +|?actual_length|int|if this is an error indicating a key is too long, the length of the provided value| + +## JSON error codes + +|code|HTTP response code|meaning| +|---|---|---| +|0|500|Internal server error, try again later| +|0|400|Bad Request (usually invalid JSON)| +|0|401|Missing or invalid Authorization header| +|20001|404|System not found.| +|20002|404|Member not found.| +|20003|404|Group not found.| +|20004|404|Message not found.| +|20005|404|Switch not found.| +|20005|404|Switch not found, switch associated with different system, or unauthorized to view front history.| +|20006|404|No system guild settings found for target guild.| +|20007|404|No member guild settings found for target guild.| +|30001|403|Unauthorized to view member list| +|30002|403|Unauthorized to view group list| +|30003|403|Unauthorized to view group member list| +|30004|403|Unauthorized to view current fronters.| +|30005|403|Unauthorized to view front history.| +|30006|403|Target member is not part of your system.| +|30007|403|Target group is not part of your system.| +|30008|403|$Member '{memberRef}' is not part of your system.| +|30009|403|$Group '{groupRef}' is not part of your system.| +|40002|400|Missing autoproxy member for member-mode autoproxy.| +|40003|400|Duplicate members in member list.| +|40004|400|Member list identical to current fronter list.| +|40005|400|Switch with provided timestamp already exists.| +|40006|400|Invalid switch ID.| diff --git a/docs/content/api-documentation.md b/docs/content/api/legacy.md similarity index 83% rename from docs/content/api-documentation.md rename to docs/content/api/legacy.md index 84102551..46b4f2f2 100644 --- a/docs/content/api-documentation.md +++ b/docs/content/api/legacy.md @@ -1,47 +1,13 @@ --- -title: API documentation -description: PluralKit's API documentation. -permalink: /api +title: Legacy API documentation +permalink: /api/legacy --- -**2020-05-07**: [The PluralKit API is now documented on Swagger.](https://app.swaggerhub.com/apis-docs/xSke/PluralKit/1.1) -Accompanying it is an [OpenAPI v3.0 definition](https://github.com/xSke/PluralKit/blob/master/PluralKit.API/openapi.yaml). It's mostly complete, but is still subject to change - so don't go generating API clients and mock servers with it quite yet. It may still be useful, though :) +# Legacy API documentation -# API documentation - -PluralKit has a basic HTTP REST API for querying and modifying your system. -The root endpoint of the API is `https://api.pluralkit.me/v1/`. - -#### Authorization header token example -``` -Authorization: z865MC7JNhLtZuSq1NXQYVe+FgZJHBfeBCXOPYYRwH4liDCDrsd7zdOuR45mX257 -``` - -Endpoints will always return all fields, using `null` when a value is missing. On `PATCH` endpoints, -missing fields from the JSON request will be ignored and preserved as is, but on `POST` endpoints will -be set to `null` or cleared. - -Endpoints taking JSON bodies (eg. most `PATCH` and `PUT` endpoints) require the `Content-Type: application/json` header set. - -## Community API Libraries - -The following API libraries have been created by members of our community. Please contact the developer of each library if you need support. - -- **Python:** *PluralKit.py* ([PyPI](https://pypi.org/project/pluralkit/) | [Docs](https://pluralkit.readthedocs.io/en/latest/source/quickstart.html) | [Source code](https://github.com/almonds0166/pluralkit.py)) -- **JavaScript:** *pkapi.js* ([npmjs](https://npmjs.com/package/pkapi.js) | [Docs](https://github.com/greysdawn/pk.js/wiki) | [Source code](https://github.com/greysdawn/pk.js)) -- **Golang:** *pkgo* (install: `go get github.com/starshine-sys/pkgo` | [Docs (godoc)](https://godocs.io/github.com/starshine-sys/pkgo) | [Docs (pkg.go.dev)](https://pkg.go.dev/github.com/starshine-sys/pkgo) | [Source code](https://github.com/starshine-sys/pkgo)) - -Do let us know in the support server if you made a new library and would like to see it listed here! - -## Authentication -Authentication is done with a simple "system token". You can get your system token by running `pk;token` using the -Discord bot, either in a channel with the bot or in DMs. Then, pass this token in the `Authorization` HTTP header -on requests that require it. Failure to do so on endpoints that require authentication will return a `401 Unauthorized`. - -Some endpoints show information that a given system may have set to private. If this is a specific field -(eg. description), the field will simply contain `null` rather than the true value. If this applies to entire endpoint -responses (eg. fronter, switches, member list), the entire request will return `403 Forbidden`. Authenticating with the -system's token (as described above) will override these privacy settings and show the full information. +::: warning +This is the documentation for v1 of the PluralKit API. Please use v2 going forwards - v1 is deprecated and will be removed eventually. +::: ## Models The following three models (usually represented in JSON format) represent the various objects in PluralKit's API. @@ -536,32 +502,4 @@ The returned system and member's privacy settings will be respected, and as such "metadata_privacy": "private" } } -``` - -## Version history -* 2020-07-28 - * The unversioned API endpoints have been removed. -* 2020-06-17 (v1.1) - * The API now has values for granular member privacy. The new fields are as follows: `visibility`, `name_privacy`, `description_privacy`, `avatar_privacy`, `birthday_privacy`, `pronoun_privacy`, `metadata_privacy`. All are strings and accept the values of `public`, `private` and `null`. - * The `privacy` field has now been deprecated and should not be used. It's still returned (mirroring the `visibility` field), and writing to it will write to *all privacy options*. -* 2020-05-07 - * The API (v1) is now formally(ish) defined with OpenAPI v3.0. [The definition file can be found here.](https://github.com/xSke/PluralKit/blob/master/PluralKit.API/openapi.yaml) -* 2020-02-10 - * Birthdates with no year can now be stored using `0004` as a year, for better leap year support. Both options remain valid and either may be returned by the API. - * Added privacy set/get support, meaning you will now see privacy values in authed requests and can set them. -* 2020-01-08 - * Added privacy support, meaning some responses will now lack information or return 403s, depending on the specific system and member's privacy settings. -* 2019-12-28 - * Changed behaviour of missing fields in PATCH responses, will now preserve the old value instead of clearing - * This is technically a breaking change, but not *significantly* so, so I won't bump the version number. -* 2019-10-31 - * Added `proxy_tags` field to members - * Added `keep_proxy` field to members - * Deprecated `prefix` and `suffix` member fields, will be removed at some point (tm) -* 2019-07-17 - * Added endpoint for querying system by account - * Added endpoint for querying message contents -* 2019-07-10 **(v1)** - * First specified version -* (prehistory) - * Initial release +``` \ No newline at end of file diff --git a/docs/content/api/models.md b/docs/content/api/models.md new file mode 100644 index 00000000..ac771c13 --- /dev/null +++ b/docs/content/api/models.md @@ -0,0 +1,122 @@ +--- +title: Models +permalink: /api/models +--- + +# Models + +A question mark (`?`) next to the *key name* means the key is optional - it may be omitted in API responses. A question mark next to the *key type* means the key is nullable - API responses may return `null` for that key, instead of the specified type. + +In PATCH endpoints, all keys are optional. However, providing an object with no keys (or no valid keys) will result in 500 internal server error. + +Privacy objects (`privacy` key in models) contain values "private" or "public". Patching a privacy value to `null` will set to public. If you do not have access to view the privacy object of the member, the value of the `privacy` key will be null, rather than the values of individual privacy keys. + +#### Notes on IDs + +Every PluralKit entity has two IDs: a short (5-character) ID and a longer UUID. The short ID is unique across the resource (a member can have the same short ID as a system, for example), while the UUID is consistent for the lifetime of the entity and globally unique across the bot. + +### System model + +|key|type|notes| +|---|---|---| +|id|string|| +|uuid|string|| +|name|string|100-character limit| +|description|?string|1000-character limit| +|tag|string|| +|avatar_url|?string|256-character limit, must be a publicly-accessible URL| +|banner|?string|256-character limit, must be a publicly-accessible URL| +|color|string|6-character hex code, no `#` at the beginning| +|created|datetime|| +|timezone|string|defaults to `UTC`| +|privacy|?system privacy object|| + +* System privacy keys: `description_privacy`, `member_list_privacy`, `group_list_privacy`, `front_privacy`, `front_history_privacy` + +### Member model + +|key|type|notes| +|---|---|---| +|id|string|| +|uuid|string|| +|name|string|100-character limit| +|display_name|?string|100-character limit| +|color|string|6-character hex code, no `#` at the beginning| +|birthday|?string|`YYYY-MM-DD` format, 0004 hides the year| +|pronouns|?string|100-character-limit| +|avatar_url|?string|256-character limit, must be a publicly-accessible URL| +|banner|?string|256-character limit, must be a publicly-accessible URL| +|description|?string|1000-character limit| +|created|?datetime|| +|keep_proxy|boolean|| +|privacy|?member privacy object|| + +* Member privacy keys: `visibility`, `name_privacy`, `description_privacy`, `birthday_privacy`, `pronoun_privacy`, `avatar_privacy`, `metadata_privacy` + +#### ProxyTag object + +| Key | Type | +| ------ | ------- | +| prefix | ?string | +| suffix | ?string | + +### Group model + +|key|type|notes| +|---|---|---| +|id|string|| +|uuid|string|| +|name|string|100-character limit| +|display_name|?string|100-character limit| +|description|?string|1000-character limit| +|icon|?string|256-character limit, must be a publicly-accessible URL| +|banner|?string|256-character limit, must be a publicly-accessible URL| +|color|string|6-character hex code, no `#` at the beginning| +|privacy|?group privacy object|| + +* Group privacy keys: `description_privacy`, `icon_privacy`, `list_privacy`, `visibility` + +### Switch model + +|key|type|notes| +|---|---|---| +|timestamp|datetime|| +| members | list of id/Member | Is sometimes in plain ID list form (eg. `GET /systems/:id/switches`), sometimes includes the full Member model (eg. `GET /systems/:id/fronters`). | + +### Message model + +|key|type|notes| +|---|---|---| +|timestamp|datetime|| +|id|snowflake|The ID of the message sent by the webhook. Encoded as string for precision reasons.| +|original| snowflake|The ID of the (now-deleted) message that triggered the proxy. Encoded as string for precision reasons.| +|sender|snowflake|The user ID of the account that triggered the proxy. Encoded as string for precision reasons.| +|channel|snowflake|The ID of the channel the message was sent in. Encoded as string for precision reasons.| +|system|full System object|The system that proxied the message.| +|member|full Member object|The member that proxied the message.| + +### System guild settings model + +|key|type|notes| +|---|---|---| +|proxying_enabled|boolean|| +|autoproxy_mode|autoproxy mode enum|| +|autoproxy_member|?string|must be set if autoproxy_mode is `member`| +|tag|?string|79-character limit| +|tag_enabled|boolean|| + +#### Autoproxy mode enum + +|key|description| +|---|---| +|off|autoproxy is disabled| +|front|autoproxy is set to the first member in the current fronters list, or disabled if the current switch contains no members| +|latch|autoproxy is set to the last member who sent a proxied message in the server| +|member|autoproxy is set to a specific member (see `autoproxy_member` key)| + +### Member guild settings model + +|key|type|notes| +|---|---|---| +|display_name|?string|100-character limit| +|avatar_url|?string|256-character limit, must be a publicly-accessible URL| diff --git a/docs/content/api/reference.md b/docs/content/api/reference.md new file mode 100644 index 00000000..6001780e --- /dev/null +++ b/docs/content/api/reference.md @@ -0,0 +1,48 @@ +--- +title: Reference +permalink: /api/reference +--- + +# API Reference + +PluralKit has a basic HTTP REST API for querying and modifying your system. +The root endpoint of the API is `https://api.pluralkit.me/v2/`. + +#### Authorization header token example +``` +Authorization: z865MC7JNhLtZuSq1NXQYVe+FgZJHBfeBCXOPYYRwH4liDCDrsd7zdOuR45mX257 +``` + +Endpoints will always return all fields, using `null` when a value is missing. On `PATCH` endpoints, +missing fields from the JSON request will be ignored and preserved as is, but on `POST` endpoints will +be set to `null` or cleared. + +For models that have them, the keys `id`, `uuid` and `created` are **not** user-settable. + +Endpoints taking JSON bodies (eg. most `PATCH` and `PUT` endpoints) require the `Content-Type: application/json` header set. + +## Authentication +Authentication is done with a simple "system token". You can get your system token by running `pk;token` using the +Discord bot, either in a channel with the bot or in DMs. Then, pass this token in the `Authorization` HTTP header +on requests that require it. Failure to do so on endpoints that require authentication will return a `401 Unauthorized`. + +Some endpoints show information that a given system may have set to private. If this is a specific field +(eg. description), the field will simply contain `null` rather than the true value. If this applies to entire endpoint +responses (eg. fronter, switches, member list), the entire request will return `403 Forbidden`. Authenticating with the +system's token (as described above) will override these privacy settings and show the full information. + +## Rate Limiting + +By default, there is a per-IP limit of 2 requests per second across the API. If you exceed this limit, you will get a 429 response code with a [rate limit error](#) body ..... + +todo: this isn't implemented yet. + +## Community API Libraries + +The following API libraries have been created by members of our community. Please contact the developer of each library if you need support. + +- **Python:** *PluralKit.py* ([PyPI](https://pypi.org/project/pluralkit/) | [Docs](https://pluralkit.readthedocs.io/en/latest/source/quickstart.html) | [Source code](https://github.com/almonds0166/pluralkit.py)) +- **JavaScript:** *pkapi.js* ([npmjs](https://npmjs.com/package/pkapi.js) | [Docs](https://github.com/greysdawn/pk.js/wiki) | [Source code](https://github.com/greysdawn/pk.js)) +- **Golang:** *pkgo* (install: `go get github.com/starshine-sys/pkgo` | [Docs (godoc)](https://godocs.io/github.com/starshine-sys/pkgo) | [Docs (pkg.go.dev)](https://pkg.go.dev/github.com/starshine-sys/pkgo) | [Source code](https://github.com/starshine-sys/pkgo)) + +Do let us know in the support server if you made a new library and would like to see it listed here!