PluralKit/PluralKit.API/openapi.yaml
2020-06-20 16:00:50 +02:00

853 lines
32 KiB
YAML

# Draft version. Refer to the official API documentation for more accurate information.
openapi: 3.0.0
info:
title: PluralKit
version: "1.1"
description: |
This is the API for [PluralKit](https://pluralkit.me/)! :)
The API itself is stable, but this document (the OpenAPI description) is still subject to change, and may be updated, corrected or restructured in the future (as long as it's still coherent with the real API).
# Authentication
Authentication is handled using a "system token". At the moment, the only way
to obtain a system token is to use the `pk;token` command through the Discord bot.
This will generate an opaque string you must pass as the `Authorization` header to API requests.
Many API endpoints are available anonymously, but most of them will hide information from
unauthenticated requests to align with the relevant privacy settings.
# Errors
Errors are just returned as HTTP response codes. Most error responses include a human-readable
error message as the body, but this should not be relied on. Just read the response codes :)
# OpenAPI version history
- **1.1**: Granular member privacy
- **1.0**: (initial definition version)
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0.html
externalDocs:
url: https://pluralkit.me/api
description: For more information, see the official PluralKit API documentation on the website.
servers:
- url: https://api.pluralkit.me/v1
description: Primary API server (v1)
paths:
/s:
get:
summary: Returns your own system.
description: Requires authentication, and will returns the system the token belongs to.
tags: [Systems]
operationId: GetOwnSystem
security:
- TokenAuth: []
responses:
"200":
$ref: "#/components/responses/SystemResponse"
"401":
$ref: "#/components/responses/UnauthorizedError"
patch:
summary: Updates an existing system.
description: Requires authentication, and will update the system the token belongs to.
tags: [Systems]
operationId: UpdateSystem
security:
- TokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/System"
responses:
"200":
description: The system was updated. Returns the updated system object.
content:
application/json:
schema:
$ref: "#/components/schemas/System"
"401":
$ref: "#/components/responses/UnauthorizedError"
/s/{id}:
parameters:
- $ref: "#/components/parameters/SystemID"
get:
summary: Gets a system by its ID.
tags: [Systems]
operationId: GetSystem
description: Partial information may be returned if not authenticated with this system's token.
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/System"
"404":
description: System with the given ID not found.
/s/{id}/members:
parameters:
- $ref: "#/components/parameters/SystemID"
get:
summary: Gets a system's members.
description: |
If the API token does not belong to this system, this list may exclude any private members in the system.
tags: [Systems, Members]
operationId: GetSystemMembers
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/System"
"403":
description: The system's member list is private, and the given token either missing, invalid, or does not correspond to the system.
"404":
description: System with the given ID not found.
/s/{id}/fronters:
# TODO: rename to "latest switch" or something?
parameters:
- $ref: "#/components/parameters/SystemID"
get:
summary: Gets a system's current fronters.
tags: [Systems, Switches]
operationId: GetSystemFronters
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/FullSwitch"
"403":
description: The system's current fronters are private, and the given token is either missing, invalid, or does not correspond to the system.
"404":
description: System with the given ID was either not found, or does not have any switches registered.
/s/{id}/switches:
parameters:
- $ref: "#/components/parameters/SystemID"
get:
summary: Gets a system's switch history.
description: |
Will return the system's switch history, up to 100 entries at a time, in reverse-chronological (latest first) order.
For pagination, see the `before` query parameter.
tags: [Systems, Switches]
operationId: GetSystemSwitches
parameters:
- in: query
name: before
schema:
type: string
format: date-time
description: |
If provided, will only return switches that happened *before* (and not including) this timestamp.
This can be used for pagination by calling the endpoint again with the timestamp of the last switch of the previous response.
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
maxItems: 100
items:
$ref: "#/components/schemas/Switch"
"403":
description: The system's switch history is private, and the given token is either missing, invalid, or does not correspond to the system.
"404":
description: System with the given ID not found.
/s/switches:
post:
summary: Registers a new switch.
tags: [Switches]
operationId: RegisterSwitch
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/Switch"
security:
- TokenAuth: []
responses:
"204":
description: The switch was logged.
"401":
$ref: "#/components/responses/UnauthorizedError"
/m:
post:
summary: Creates a new member in your system.
tags: [Members]
operationId: CreateMember
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/Member"
security:
- TokenAuth: []
responses:
"200":
description: The member was created. Returns the newly created member object.
content:
application/json:
schema:
$ref: "#/components/schemas/Member"
"401":
$ref: "#/components/responses/UnauthorizedError"
/m/{id}:
parameters:
- $ref: "#/components/parameters/MemberID"
get:
summary: Gets a member by their ID.
tags: [Members]
operationId: GetMember
security:
- {}
- TokenAuth: []
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/Member"
"404":
$ref: "#/components/responses/MemberNotFoundError"
patch:
summary: Updates a member.
tags: [Members]
operationId: UpdateMember
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/Member"
security:
- TokenAuth: []
responses:
"200":
description: The member was updated. Returns trhe updated member object.
content:
application/json:
schema:
$ref: "#/components/schemas/Member"
"401":
$ref: "#/components/responses/MemberAuthError"
"404":
$ref: "#/components/responses/MemberNotFoundError"
delete:
summary: Deletes a member.
tags: [Members]
operationId: DeleteMember
security:
- TokenAuth: []
responses:
"200": # TODO: should be 204 (but *is* 200)
description: Member successfully deleted.
"401":
$ref: "#/components/responses/MemberAuthError"
"404":
$ref: "#/components/responses/MemberNotFoundError"
/a/{id}:
parameters:
- in: path
name: id
required: true
description: A Discord user ID.
schema:
$ref: "#/components/schemas/Snowflake"
get:
summary: Gets a system by (one of) its associated Discord accounts.
description: |
Note that it's currently not possible to get a system's registered accounts given a system ID through the API.
Consider this endpoint "one-way".
tags: [Systems, Accounts]
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/System"
"404":
description: The given user ID does not correspond to any systems.
/msg/{id}:
parameters:
- in: path
name: id
required: true
description: |
A Discord message ID.
This may refer to either the original "trigger message" posted by the user,
or to the resulting webhook message posted by PluralKit.
The former may be useful for eg. logging bot integration.
schema:
$ref: "#/components/schemas/Snowflake"
get:
summary: Gets information about a proxied message by its message ID.
tags: [Proxying]
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/Message"
"404":
description: The given message ID does not correspond to a message proxied by PluralKit.
components:
responses:
UnauthorizedError:
description: System token is missing or invalid.
NotOwnSystemError:
description: The given system does not correspond with this token.
MemberAuthError:
# TODO (relevant, ish): This is always returned as 401 but should be split into "invalid token" (401) and "not own member" (403) responses
description: System token is missing, invalid, or the corresponding system does not own the given member.
MemberNotFoundError:
description: A member by the given ID was not found.
SystemResponse:
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/System"
MemberListResponse:
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Member"
parameters:
SystemID:
in: path
name: id
required: true
description: The ID of the system in question.
schema:
$ref: "#/components/schemas/ID"
MemberID:
in: path
name: id
required: true
description: The ID of the member in question.
schema:
$ref: "#/components/schemas/ID"
schemas:
ID:
title: ID (system or member)
type: string
readOnly: true
minLength: 5
maxLength: 5
pattern: "^[a-z]{5}$"
example: "abcde"
description: |
A unique identifier for a system or a member, as a randomly generated string of five lowercase letters.
These IDs are guaranteed to be globally unique for the given model type (a system can have the same ID as a member, but two systems or two members can never share an ID).
The IDs can on rare occasions change - eg. if a profane word is generated and later regenerated, or as a potential future Patreon perk. However, it's still reasonable to assume that the IDs are constant, and that tey won't change without the user's knowledge, so it's safe to store in things like settings menus and config files.
System:
properties:
id:
$ref: "#/components/schemas/ID"
name:
type: string
nullable: true
maxLength: 100
description: The user-provided name of the system.
example: "Boxes of Foxes"
description:
type: string
nullable: true
maxLength: 1000
description: |
The user-provided system description.
May contain rich text in Markdown, including standard Markdown-formatted links, or Discord-formatted emoji/user/channel references.
example: |-
This system is very cool.
It has cool people.
tag:
type: string
maxLength: 78
description: |
The system tag, which is appended to the names (or display names, if set) of members when proxying through the bot.
A space will automatically be inserted between the name and the tag, so no need to include one at the start.
The character limit here is 78, as Discord's name length limit for webhooks is 80 characters. A 78-character system tag is thus the longest tag that can still accomodate a single-letter member name and the separating space.
nullable: true
example: "| BoF"
avatar_url:
type: string
format: url
nullable: true
maxLength: 256
example: "https://i.imgur.com/Abcdefg.png"
description: |
A link to the avatar/icon of the system.
If used for proxying, the image must be at most 1000 pixels in width *and* height, and at most 1 MiB in size. This restriction is on Discord's end and is not verified through the API (it's simply stored as a string).
tz:
type: string
format: timezone
nullable: true
default: UTC
example: America/New_York
description: |
The system's registered time zone as an Olson time zone ID.
This is used in the bot to convert various time stamps in commands on behalf of this system.
created:
type: string
format: date-time
readOnly: true
description: The creation timestamp of the system.
description_privacy:
allOf:
- $ref: "#/components/schemas/PrivacySetting"
- description: |
The system's description privacy setting, either "public" or "private".
If this is set to "private", the field `description` will be returned as `null` on all requests not authorized with this system's token.
In addition, this field will be returned as `null` if the request is not authorized with this system's token.
Because of this, there is no way for an unauthorized user to tell the difference between a private description and a `null` description - this is intentional.
example: public
member_list_privacy:
allOf:
- $ref: "#/components/schemas/PrivacySetting"
- description: |
The system's member list privacy setting, either "public" or "private".
If this is set to "private", this system's member list can not be queried without proper authorization.
In addition, this field will be returned as `null` if the request is not authorized with this system's token.
example: public
front_privacy:
allOf:
- $ref: "#/components/schemas/PrivacySetting"
- description: |
The system's current fronter privacy setting, either "public" or "private".
If this is set to "private", this system's current fronter can not be queried without proper authorization.
In addition, this field will be returned as `null` if the request is not authorized with this system's token.
example: public
front_history_privacy:
allOf:
- $ref: "#/components/schemas/PrivacySetting"
- description: |
The system's front history privacy setting, either "public" or "private".
If this is set to "private", this system's front/switch history can not be queried without proper authorization.
In addition, this field will be returned as `null` if the request is not authorized with this system's token.
example: public
Member:
properties:
id:
$ref: "#/components/schemas/ID"
name:
type: string
maxLength: 100
description: The user-provided name of the member.
example: "Myriad Kit"
display_name:
type: string
maxLength: 100
description: The member's "display name", which will override the member's normal name when proxying.
nullable: true
example: Myriad 'Big Boss' Kit
description:
type: string
maxLength: 1000
description: |
The user-provided description of the member.
May contain rich text in Markdown, including standard Markdown-formatted links, or Discord-formatted emoji/user/channel references.
If this member is private, and the request is not authorized with the member's system token, this field will always be returned as `null`.
example: Myriad is very cool and rad, and they love snuggling.
color:
type: string
format: color
minLength: 6
maxLength: 6
nullable: true
pattern: "^[0-9A-Fa-f]{6}$"
description: |
The member's "color", displayed on member cards, as a 6-character hexadecimal color code (no leading #).
If this member is private, and the request is not authorized with the member's system token, this field will always be returned as `null`.
example: "FF0000"
birthday:
type: string
format: date
example: "2018-07-11"
nullable: true
description: |
The user-provided birthdate of the member.
"Year-less" birthdays are supported. In this case, the year should be set to `0004`, and that specific year should be special-cased and hidden from the user. Previous versions used the year `0001` for the same purpose, and this value may still be both read and written with the API and should be treated the same as `0004`.
The year `0004` was chosen because it is a leap year in the Gregorian calendar, and thus the date `0004-02-29` can be properly represented.
If this member is private, and the request is not authorized with the member's system token, this field will always be returned as `null`.
pronouns:
type: string
maxLength: 100
example: "they/them or xe/xem"
nullable: true
description: |
The user-provided pronouns of the member.
There is no specific schema, just a freeform text field.
If this member is private, and the request is not authorized with the member's system token, this field will always be returned as `null`.
avatar_url:
type: string
format: url
nullable: true
maxLength: 256
example: "https://i.imgur.com/Abcdefg.png"
description: |
A link to the avatar/icon of the member.
If used for proxying, the image must be at most 1000 pixels in width *and* height, and at most 1 MiB in size. This restriction is on Discord's end and is not verified through the API (it's simply stored as a string).
privacy:
allOf:
- $ref: "#/components/schemas/PrivacySetting"
- description: |
This field is deprecated.
At the moment, it's still included in member objects, with a value that mirrors the `visibility` field. Writing to this field will set *every* privacy setting to the written value.
example: public
deprecated: true
visibility:
allOf:
- $ref: "#/components/schemas/PrivacySetting"
- description: |
The member's current visibility, either "public" or "private".
If this is set to "private", the member will not appear in public member lists. It can still be looked up using its 5-character member ID, but will return limited information.
In addition, this field will be returned as `null` if the request is not authorized with this system's token.
example: public
name_privacy:
allOf:
- $ref: "#/components/schemas/PrivacySetting"
- description: |
The member's current name privacy setting, either "public" or "private".
If this is set to "private", the member's returned `name` will be replaced by their `display_name` when applicable.
In addition, this field will be returned as `null` if the request is not authorized with this system's token.
Because of this, there is no way for an unauthorized user to tell the difference between a private name being replaced by the display name, and an empty display name with the name set - this is intentional.
example: public
description_privacy:
allOf:
- $ref: "#/components/schemas/PrivacySetting"
- description: |
The member's current description privacy setting, either "public" or "private".
If this is set to "private", the field `description` will be returned as `null` on all requests not authorized with this system's token.
In addition, this field will be returned as `null` if the request is not authorized with this system's token.
Because of this, there is no way for an unauthorized user to tell the difference between a private description and a `null` description - this is intentional.
example: public
avatar_privacy:
allOf:
- $ref: "#/components/schemas/PrivacySetting"
- description: |
The member's current avatar privacy setting, either "public" or "private".
If this is set to "private", the field `avatar_url` will be returned as `null` on all requests not authorized with this system's token.
In addition, this field will be returned as `null` if the request is not authorized with this system's token.
Because of this, there is no way for an unauthorized user to tell the difference between a private avatar and a `null` avatar - this is intentional.
example: public
pronouns_privacy:
allOf:
- $ref: "#/components/schemas/PrivacySetting"
- description: |
The member's current pronouns privacy setting, either "public" or "private".
If this is set to "private", the field `pronouns` will be returned as `null` on all requests not authorized with this system's token.
In addition, this field will be returned as `null` if the request is not authorized with this system's token.
Because of this, there is no way for an unauthorized user to tell the difference between private pronouns and `null` pronouns - this is intentional.
example: public
birthday_privacy:
allOf:
- $ref: "#/components/schemas/PrivacySetting"
- description: |
The member's current birthday privacy setting, either "public" or "private".
If this is set to "private", the field `birthday` will be returned as `null` on all requests not authorized with this system's token.
In addition, this field will be returned as `null` if the request is not authorized with this system's token.
Because of this, there is no way for an unauthorized user to tell the difference between a private birthday and a `null` birthday - this is intentional.
example: public
metadata_privacy:
allOf:
- $ref: "#/components/schemas/PrivacySetting"
- description: |
The member's current metadata privacy setting, either "public" or "private".
If this is set to "private", the field `created` on all requests not authorized with this system's token.
In addition, this field will be returned as `null` if the request is not authorized with this system's token.
example: public
proxy_tags:
type: array
items:
$ref: "#/components/schemas/ProxyTag"
description: |
An unordered list of the member's proxy tag pairs.
It is valid for a member to have any number of proxy tags, including none at all.
prefix:
type: string
nullable: true
description: |
Previous versions of the API only supported a single proxy tag pair per member.
This field will contain the prefix of the first proxy tag registered, or `null` if missing.
Setting it will write to the first proxy tag's prefix, creating it if not present.
This field is deprecated and will be removed in API v2.
deprecated: true
example: "{{"
suffix:
type: string
nullable: true
description: |
Previous versions of the API only supported a single proxy tag pair per member.
This field will contain the suffix of the first proxy tag registered, or `null` if missing.
Setting it will write to the first proxy tag's suffix, creating it if not present.
This field is deprecated and will be removed in API v2.
deprecated: true
example: "}}"
keep_proxy:
type: boolean
default: false
description: |
Whether or not to include the used proxy tags in proxied messages.
created:
type: string
format: date-time
readOnly: true
description: The creation timestamp of the member. May be returned as `null` depending on the value of `metadata_privacy` and the request authorization.
nullable: true
PrivacySetting:
title: Privacy Setting
type: string
nullable: true
description: A privacy setting for systems or members, either public or private. May occasionally be `null` in cases where you are not authorized to view the privacy setting's state.
enum: [public, private]
example: public
ProxyTag:
title: Proxy Tag
description: |
Represents a proxy tag to match messages on.
A "proxy tag" consists of a prefix and a suffix, and a given proxy tag set matches a string
if that string begins with the prefix and ends with the suffix.
It's often represented to the user with the word `text` between them, eg. `[text]`, `{{text`, and so on.
For example, the proxy tag pair "[" and "]" will match any string \[in square brackets\].
Either the prefix or the suffix may be missing (or both may be present), but it is invalid for
both values to be null.
properties:
prefix:
type: string
nullable: true
description: |
The proxy tag prefix. This is the string that goes *before* the inner text.
An empty prefix is represented as `null` and an empty string will be converted as such.
example: "{{"
maxLength: 100
suffix:
type: string
nullable: true
description: |
The proxy tag suffix. This is the string that goes *after* the inner text.
An empty suffix is represented as `null` and an empty string will be converted as such.
example: "}}"
maxLength: 100
Switch:
title: Logged Switch
properties:
timestamp:
type: string
format: date-time
description: The timestamp the switch was logged.
readOnly: true
members:
type: array
description: |
A list of the IDs of the members in this switch. The order is significant. It is valid for the switch to have no members at all.
items:
$ref: "#/components/schemas/ID"
FullSwitch:
title: Logged Switch (with full Member object)
properties:
timestamp:
type: string
format: date-time
description: The timestamp the switch was logged.
members:
type: array
description: |
A list of the members in this switch. The order is significant. It is valid for the switch to have no members at all.
items:
$ref: "#/components/schemas/Member"
Snowflake:
title: Snowflake (Discord ID)
description: |
A unique identifier used by Discord for its objects (accounts, guilds, channels, messages).
This is internally stored as a 64-bit unsigned integer, but is represented as a string in the API
to accomodate languages that store numbers as floating point values (and thus would not be able to represent it losslessly).
type: string
pattern: "^[0-9]{17,19}"
minLength: 17
maxLength: 19
example: "466378653216014359" # PK's account ID :3
Message:
title: Message Info
description: |
An object containing information about a proxied message, including message, user and channel IDs, as well as the system and member it's related to.
For privacy and performance reasons, this endpoint does not return the *contents* of the original message. This data isn't stored in the database either way - but given the channel and message ID, it can be fetched from Discord's own API.
properties:
timestamp:
type: string
format: date-time
readOnly: true
description: The time the message was proxied.
id:
allOf:
- $ref: "#/components/schemas/Snowflake"
- description: "The ID of the proxied webhook message posted by PluralKit."
example: "123456789012345678"
original:
allOf:
- $ref: "#/components/schemas/Snowflake"
- description: "The ID of the original (now-deleted) trigger message containing the proxy tags."
example: "123456789012345678"
sender:
allOf:
- $ref: "#/components/schemas/Snowflake"
- description: "The ID of the Discord user that sent the trigger message."
example: "123456789012345678"
channel:
allOf:
- $ref: "#/components/schemas/Snowflake"
- description: "The ID of the Discord channel the relevant messages were posted in."
example: "123456789012345678"
system:
allOf:
- $ref: "#/components/schemas/System"
- description: "The system that proxied the message."
member:
allOf:
- $ref: "#/components/schemas/Member"
- description: "The member that proxied the message."
securitySchemes:
TokenAuth:
type: apiKey
in: header
name: Authorization
description: A system token obtained from the `pk;proxy` command in the Discord bot.