Introduction
Pulsate Specification は Pulsate プロジェクトに関するAPIエンドポイントや設計などの仕様を記述したドキュメントです.
プロジェクトの概要については こちら をご覧ください.
Note
この仕様書は現在開発中の Pulsate v0.1 となります. 今後のバージョンアップにより仕様が変更される可能性があります.
プロジェクト概要
プロジェクトの目標
高速で安全な ActivityPub 実装
-
パフォーマンス重視
-
セキュリティ重視
-
ActivityPub 仕様の重視
実装方針
パフォーマンス・セキュリティの強化
- Misskey や Mastodon よりも高速に動作する次世代の ActivityPub 実装.
- 各APIリクエストに対するレスポンスを始めとするパフォーマンスの向上(タイムラインの読み込み, メディアのレスポンスなど).
- SQL Injection や XSS などのセキュリティに関わる脆弱性の発生可能性を減少させる.
見つかったとしてもすぐに修正できる仕組みづくり.
- パスワードレスログイン, 2FA など.
ActivityPub 実装の重視
- 無駄な機能は実装せずあくまで ActivityPub の実装として忠実に従う.
- MFM など特定のソフトウェア固有の実装に従わない.
- ただし, Mastodon や Misskey などの主要な実装が互いに実装している機能は実装する. (引用など)
クライアント, App 周りのサポートの強化
- ターゲットはモバイルユーザーが大半だと考え, Pulsate ではネイティブアプリとして
iOS App, Android App をリリースする(構想段階).
- PWAはChromium系ブラウザは概ね対応しているが, Firefox や Safari は対応していない.
- また、大部分のユーザーはアプリストアで検索すると考えられるため、マーケティング的な観点からもネイティブアプリの実装は必要.
- コミュニティが開発するサードパーティクライアントも尊重し, API リファレンスなどのリソースを多く用意しコミュニティの開発を支援する.
開発手法の厳格化 / 改善
- semver (セマンティックバージョニング) に従い, 破壊的変更をなるべく少なくする. 破壊的変更が行われる場合は予めコミュニティに通告する.
コミュニティの声を聞く
- 簡単に言えば Discord や GitHub Discussions の採用.
- バグ報告は基本的に インスタンス管理者 , ユーザー から受けるのが多いと考えるため, Discord での報告も受け付ける. (GitHub Issues には Discord Bot を使ってうまく Import する)
プロジェクトコードネーム
Pulsate
- 意味: 脈打つ, 鼓動する; 〈光・音などが〉振動する; ドキドキする; など.
- 由来: indigo la End のアルバム “PULSATE” から.
バージョンコードネーム
v0, v1 - Antenna
v0 (開発途上バージョン), v1 (最初の安定版) のコードネーム.
由来は Mrs. GREEN APPLE の ANTENNA から.
ユーザーストーリー
この文書は, Pulsate プロジェクトの主要成果物である分散型 SNS
サーバーソフトウェア pulsate がエンドユーザーから見たときに pulsate
以外の外部システムも含めてどのように振る舞うべきかを定義する.
pulsate はいくつかのアクターとやり取りすることを想定している. そのため,
ユーザーストーリーで登場するアクターを以下のように定義する.
- ユーザー User:
pulsateがやり取りするなかで最も一般的な, SNS サービスを享受するエンドユーザー.- 別々の User を表すとき, メタ変数として Alice と Bob を用いる.
- モデレーター Moderator:
pulsateおよびその上で運営されるコミュニティを管理するエンドユーザー. Moderator is-a User の関係にある.
各ユーザーストーリーは, 他文書から参照しやすいように ID を割り振っている. ID
の文法構造は US<2桁の連番> としている (US は User Story の略).
US01: ユーザー登録
- 事前条件: そのユーザーが保持するメールアドレスのアカウントが登録されていない.
- 事後条件: そのユーザーが保持するメールアドレスのアカウントが登録されている.
- 主要シナリオ:
- ユーザーが Pulsate へ, メールアドレスを付けて新規登録したいことをリクエストする.
- Pulsate がメールアドレスのメールアカウントへ, 検証メールを送信する.
- ユーザーがそのメールアカウントのメールサービスへ問い合わせて, 検証メールが来たことを確かめる.
- 検証メールの内容の検証コードを取得し, そのコードを付けて新規登録したいことをリクエストする.
- Pulsate がユーザへ, 新規登録に成功したことを応答する.
- 3 で検証メールが届かなかったときの代替シナリオ:
- ユーザーが検証メールの再送信をリクエストする.
- Pulsate がメールアドレスのメールアカウントへ, 検証メールを送信する.
- これで届けば主要シナリオの 4 へ遷移する. 届いていなければ, この代替シナリオの 1 へ遷移する, あるいはユーザー登録を中止する.
- 5 で新規登録に失敗したときの例外シナリオ:
- Pulsate がユーザへ, 新規登録に失敗したことを応答する. このユーザー登録を中止する.
- 制約:
- 短時間に大量のユーザー登録がリクエストされた時に, メール送信サービスのレートリミットに引っかかる可能性が高い.
sequenceDiagram User ->> Pulsate: メールアドレスで登録をリクエスト Pulsate ->> Email: 検証メールを送信 User ->> Email: 検証コードを取得 User ->> Pulsate: コードでメールアカウントを検証 Pulsate -->> User: 登録完了
US02: フォロー/フォロワー
- 事前条件: Alice と Bob が Pulsate 上でユーザー登録を完了している. Alice が Bob をフォローしていない.
- 事後条件: Alice が Bob をフォローしている.
- 主要シナリオ:
- Alice が Pulsate 上で, 未フォローのユーザー Bob の投稿を発見する.
- Alice が Pulsate に, 未フォローのユーザー Bob のアカウント情報を問い合わせる.
- Pulsate が Alice へ, Bob の情報を提供する.
- Alice が Pulsate に, Bob をフォローの対象として追加することをリクエストする.
- Pulsate が, Alice から Bob へのフォローリクエストを記録する.
- Pulsate が Bob へ, Alice からのフォローリクエストを通知する.
- Bob が Pulsate 上で, そのフォローリクエストを承認する.
- Pulsate が, Alice から Bob へフォローの関係があることを記録する.
- Pulsate が Alice へ, フォローリクエストが承認されたことを通知する.
- 7 で Bob が承認しなかった場合の代替シナリオ:
- Pulsate は Alice から Bob へのフォローリクエストを記録し続けて何もしない.
- 4 でフォロー対象にできない場合の例外シナリオ:
- Pulsate が Alice へ, Bob をフォローできないことを応答する.
sequenceDiagram
Alice ->> Pulsate: 未フォローのユーザー Bob を発見
Pulsate -->> Alice: Bob の情報を提供
Alice ->> Pulsate: Bob のフォローを要求
Pulsate ->> Bob: フォローリクエストを通知
alt Bob が Alice からのフォローを承認する場合
Bob ->> Pulsate: リクエストを承認
Pulsate -->> Alice: フォロー完了
else Bob が Alice からのフォローを承認しない場合
Bob ->> Pulsate: リクエストを拒否
Pulsate -->> Pulsate: 何もしない
end
US03: 投稿
- 事前条件: Bob が Pulsate 上でユーザー登録を完了している. Bob が Alice をフォローしている.
- 事後条件: Alice が作成した新たな投稿が, Bob のホームタイムラインに現れる.
- 主要シナリオ:
- Alice が Pulsate へ, 新規投稿の作成をリクエストする.
- Pulsate が新規の投稿を記録する.
- Pulsate が Alice へ, 投稿の作成に成功したことを応答する.
- Pulsate が Bob のタイムラインへ非同期で追加する.
- Bob が Pulsate 上で自身のタイムラインを取得する.
- 2 で新規の投稿の作成に失敗した場合の例外シナリオ:
- Pulsate が Alice へ, 投稿の作成に失敗したことを応答する.
- 制約:
- あるユーザーのフォローされているユーザー数が非常に多い時, そのユーザーが投稿した際にそのユーザーをフォローしているユーザーのホームタイムラインすべてを生成する処理に負荷が集中する可能性が高い.
- Alice は Pulsate 内ではない, 外部の分散型 SNS サービスからの連合と呼ばれる機能で新規投稿を Pulsate へ転送していることがある.
sequenceDiagram participant Alice participant Pulsate participant Bob Note over Alice, Bob: Bob は Alice をフォローしている Alice ->> Pulsate: 投稿を送信 Pulsate -->> Alice: 投稿の成功を報告 Pulsate ->> Bob: 新規投稿のイベントを配送 Bob -->> Pulsate: 新規投稿を取得
US04: ブックマーク (お気に入り)
- 事前条件: ユーザーが Pulsate 上でユーザー登録を完了している. ユーザーがその投稿をブックマークしていない.
- 事後条件: ユーザーがその投稿をブックマークしている.
- 主要シナリオ:
- ユーザーが Pulsate 上で投稿を発見する.
- ユーザーが Pulsate へ, その投稿をブックマークするようにリクエストする.
- Pulsate が, ユーザーがその投稿をブックマークしていることを非同期で記録する.
- ユーザーはブックマークに成功したと見なして Pulsate の利用を続行する.
- 3 で記録に失敗した場合の例外シナリオ:
- ユーザーが先程の投稿がブックマークされていないことに気づく.
- ユーザーが Pulsate へ, その投稿をブックマークするようにリクエストする.
- 制約:
- ユーザーがある投稿をブックマークしているかどうかは, 他のユーザーから秘匿しなければならない.
- 備考:
- ブックマークに失敗したとき, 後でその失敗を通知する仕組みをシナリオに組み込んだほうがよいかもしれない.
US05: 検索
- 事前条件: なし (ユーザー登録なしで利用できる).
- 事後条件: なし (システムの状態を変化させない).
- 主要シナリオ:
- ユーザーが Pulsate へ, クエリ条件を送って検索を開始する.
- Pulsate がユーザーへ, 検索結果を応答する.
- 制約:
- キャッシュや結果整合的非同期処理により, 作成された投稿が検索結果へすぐには反映されないことがある.
- 多くの複雑な検索に対して高速に処理できることが望ましい.
- 備考:
- この検索のインターフェイスについては, SNS としてのユーザー体験を向上させるために熟考が必要である. 多機能, インタラクティブ性, 可読性, 充実したヘルプといった要素を重視することが望ましい.
モデルリファレンス
Pulsate のドメインモデルに共通する仕様と,新しいモデルを追加するパターンをまとめる. 各モジュールのモデルと,Timelineモジュールに含まれるListモデルの詳細は以下を参照すること.
ID
Twitter の Snowflake をベースに, 次のようなビットフィールド (図はビッグエンディアン) からなる 64 ビット整数です.
先頭に時刻を含んでいるため, そのまま時刻順にソートが可能です.
111111111111111111111111111111111111111111 1111111111 111111111111
64 22 12 0
timestamp worker id incremental
各ビットフィールドの意味は以下のとおりです.
| フィールド | ビット範囲 | 意味 |
|---|---|---|
| timestamp | [64, 22) | Pulsate エポックからのミリ秒数. エポックは 2022 年 1 月 1 日 0 時 0 分 0.000 秒. UNIX 時間のミリ秒に 1640995200000 を足すことでこれに変換可能. |
| worker id | [22, 12) | この ID を生成したワーカーの識別子 |
| incremental | [12, 0) | ワーカーにて同一時刻で ID を生成する度に増える値 |
TypeScript 上ではこのような型として表現します. 型引数によりエンティティ間で ID を取り違えるようなミスを防ぎます.
declare const snowflakeNominal: unique symbol;
export type ID<T> = string & { [snowflakeNominal]: T };
生成と利用の方法
モデル側での定義
モデルごとにこのような形でモデル固有のIDの型を定義します
export type AccountID = ID<Account>;
ID生成方法
IDはidパッケージのSnowflakeIDGeneratorクラスを利用して生成します。
Important
このとき、SnowflakeIDGeneratorは必ずコンストラクタで受け取るようにしてください。
export class RegisterService { /** 省略 **/ private readonly snowflakeIDGenerator: SnowflakeIDGenerator; constructor(arg: { /** 省略 **/ idGenerator: SnowflakeIDGenerator; }) { /** 省略 **/ this.snowflakeIDGenerator = arg.idGenerator; }
IDを生成するにはSnowflakeIDGenerator.generate<T>()メソッドを呼び出します。
この時の型引数 T は、生成したいIDの型を入れてください
const idRes = this.snowflakeIDGenerator.generate<AccountID>();
ID生成時にエラーが発生する場合があるので、必ずエラーハンドリングが必要です。
IDの利用方法
テストなどで利用する際にIDを静的に定義したい場合や、APIなどでユーザーからstring形式で受け取ったIDはアサーションすることでIDに変換できます。
const accountID = "31415926535" as AccountID;
認証トークン
JSON Web Token の一種で, ペイロードには 更新トークン など以下の情報を含みます.
更新トークンも JWT なので, JWT がネストしています.
sub: 発行対象のアカウント名iat: 発行時刻 (UNIX エポックの秒数)
{
"sub": "hogehoge-user",
"iat": 1640995201
}
このトークンの有効期限は発行時刻から 15 分 (900 秒) です.
生成や検証の自作は実装ミスによる脆弱性を誘発しますのでライブラリを利用します.
更新トークン
JSON Web Token の一種で, ペイロードには以下の情報を含みます.
{
"sub": "3e1644833000002",
"iat": 1640995201
}
このトークンの有効期限は発行時刻から 30 日 (2,592,000 秒) です. 特に再発行はされず, 手動でのログイン時にのみ発行されます.
なおこのトークンは長命なので, 利用後に無効化しておかないと奪取されて再利用されるリスクがあります. 危険度は高いですがネットワーク上に流れることがほとんどないため, このリスクには対処せず保有することにします.
アカウント
自然人, 団体, ボットなど, このシステム上でオブジェクトを発行する主体となるものです. これは次の制約を持つ属性を備えます.
name: アカウント名- 1 文字以上の URL 安全な ASCII 文字からなる
nickname: 表示名, アカウントの表示に用いる自然言語の名称- 初期値は空文字列
- 空文字列の場合はアカウント名にフォールバックする
- RTL 制御文字を除く任意の
UTF-8シーケンス
created_at: このアカウントが作成された時刻mail: メールアドレス- 有効なメールアドレスであることが検証済み
passphrase_hash: パスフレーズのハッシュ- 元となったパスフレーズは以下の性質を満たします
- 文字種は正規化された
UTF-8シーケンス - 連続する空白タイプの文字 (スペース, タブ, 全角スペース, 改行文字など) は 1 つの半角スペースへと置き換えられる
- 長さは Unicode スカラー値で 8 つぶん以上
- 文字種は正規化された
- 元となったパスフレーズは以下の性質を満たします
salt: パスフレーズのハッシュに使ったソルト- パスフレーズの平文にこれを連結したもののハッシュは,
passphrase_hashに等しい
- パスフレーズの平文にこれを連結したもののハッシュは,
なお, 基本情報はアカウント ID に関連付けられるようにし, このモデル自体は基本情報を保持しません.
アカウント状態

Mermaid code
stateDiagram-v2 NOT_ACTIVATED --> ACTIVE ACTIVE --> FROZEN ACTIVE --> SILENCED SILENCED --> FROZEN FROZEN --> SILENCED SILENCED --> ACTIVE FROZEN --> ACTIVE
登録中アカウント
まだ実際にアカウントが発行されておらず, メールアドレスを検証するスキームに入れられているアカウントです. このアカウントは通常のアカウントとは別の領域に永続化されます.
メールアドレスの検証に用いるトークンはこのモデルに含まれず,
AccountVerifyTokenRepository にアカウント ID と対にして別途保存されます.
なお, 登録中アカウントが追加されてから 168 時間(7days) が経過したものは無効とみなします.
アカウント関係
アカウントが他のアカウントに対してどのような関係性を持っているかを表すステートマシンです. あるアカウントから他のアカウントに対しては次のような関係が存在します.
NONE: なしREQUESTING_FOLLOW: フォローリクエスト中FOLLOWING: フォロー中BLOCKING: ブロック中
そしてこれらの関係は次の遷移図のように遷移できます.

Mermaid code
stateDiagram-v2
NONE --> REQUESTING_FOLLOW
REQUESTING_FOLLOW --> NONE
REQUESTING_FOLLOW --> FOLLOWING
FOLLOWING --> NONE
FOLLOWING --> BLOCKING
NONE --> BLOCKING
BLOCKING --> NONE
新しいモデルを追加する
ID 型の定義
新しいエンティティには必ずそのエンティティ専用の ID 型を定義する. ID<T>
の型引数にエンティティのクラスを渡すことで,異なるエンティティの ID
を混同するコンパイルエラーを得られる.
export type ListID = ID<List>;
エラー型の定義
モジュール内で発生するエラーは model/errors.ts にまとめて定義する. エラー名は
PascalCase で,エラーの内容が一目でわかる名称をつける.
export class AccountNotFoundError extends Error {}
export class AlreadyFollowingError extends Error {}
Repository インタフェースの定義
モデルの永続化操作は model/repository.ts にインタフェースとして定義し,実装は
adaptor/repository/ に置く. Service
はこのインタフェースに依存し,具体的な永続化実装には依存しない.
export interface AccountRepository {
findByID(id: AccountID): Promise<Option.Option<Account>>;
create(account: Account): Promise<void>;
}
SnowflakeIDGenerator の受け取り方
Service から ID を生成する際は SnowflakeIDGenerator
をコンストラクタで受け取る.
直接インポートして使うと,テスト時に差し替えられなくなる.
export class CreateNoteService {
private readonly idGenerator: SnowflakeIDGenerator;
constructor(arg: { idGenerator: SnowflakeIDGenerator }) {
this.idGenerator = arg.idGenerator;
}
}
Account モジュール
特に断りがない限り,文字列長 L は 以下のコードを実行して計算できる文字数
とする.
const count = (s: string) => {
const segmenter = new Intl.Segmenter("ja-JP", { granularity: "grapheme" });
return [...segmenter.segment(s)].length;
};
Account
Pulsate 上で投稿やフォローなどの行動を行う主体. 自然人・団体・ボットなど種別を問わず,このシステムでオブジェクトを発行するすべての主体が Account として表現される.
TypeScript 上ではアカウント固有の ID 型を次のように定義する.
export type AccountID = ID<Account>;
export type AccountName = `@${string}@${string}`;
Account は次の属性を持つ.
id:AccountID- このアカウントを一意に識別する Snowflake ID
name:AccountName@username@domainの形式をとるusernameは英数字で始まり,[a-zA-Z0-9_\-.]のみからなるdomainは RFC1035 のサブドメイン形式に従う
nickname:表示名- RTL 制御文字を除く任意の UTF-8 シーケンス
- 初期値は空文字列
- 空文字列の場合は
nameにフォールバックして表示される - 文字長は
L ≦ 256(空文字列は許容)
bio:自己紹介文- 任意の UTF-8 文字列
- 文字長は
L ≦ 1024
mail:メールアドレス- 有効なメールアドレスであることが検証済み
- 文字長は
7 ≦ L ≦ 319
passphraseHash:パスフレーズのハッシュ- ハッシュの元となるパスフレーズは次の性質を満たす
- 正規化された UTF-8 シーケンス
- 連続する空白文字(スペース・タブ・全角スペース・改行文字など)は 1 つの半角スペースに置き換えられている
- 長さは Unicode スカラー値で 8 つぶん以上
- ハッシュの元となるパスフレーズは次の性質を満たす
salt:パスフレーズのハッシュに使ったソルトpassphraseHashは,saltをパスフレーズの平文に連結したもののハッシュに等しい
createdAt:アカウント作成日時role:アカウントのロール(後述のAccountRoleを参照)frozen:凍結状態(後述のAccountFrozenを参照)silenced:サイレンス状態(後述のAccountSilencedを参照)status:アクティベーション状態(後述のAccountStatusを参照)
Note
アバター画像やヘッダー画像といったプロフィール関連の基本情報は,アカウント ID に関連付けられた別のエンティティとして管理される.
Accountモデル自体はこれらを保持しない.
AccountRole
Account に付与されるロール. 管理操作(凍結やサイレンスなど)を実行できるかどうかはこのロールによって決まる.
normal:通常のアカウントmoderator:モデレーター権限を持つアカウントadmin:管理権限を持つアカウント
AccountStatus (アクティベーション状態)
メールアドレスの検証が完了しているかを表す.
notActivated:メールアドレスの検証が完了していないactive:メールアドレスの検証が完了し,通常の活動が可能な状態
AccountFrozen (凍結状態)
管理者によるアカウント凍結の状態を表す. 凍結されたアカウントはログインできない.
normal:凍結されていない通常の状態frozen:凍結された状態
AccountSilenced (サイレンス状態)
管理者によるアカウントサイレンスの状態を表す. サイレンスされたアカウントはパブリックタイムラインへの投稿ができない.
normal:サイレンスされていない通常の状態silenced:サイレンスされた状態
アカウントのライフサイクル
AccountStatus,AccountFrozen,AccountSilenced
を合わせたアカウント全体のライフサイクルを次の状態図に示す.

Mermaid code
stateDiagram-v2 NOT_ACTIVATED --> ACTIVE ACTIVE --> FROZEN ACTIVE --> SILENCED SILENCED --> FROZEN FROZEN --> SILENCED SILENCED --> ACTIVE FROZEN --> ACTIVE
InactiveAccount (登録中アカウント)
メールアドレスの検証が完了しておらず,まだ有効な Account として発行されていない中間的なエンティティ. 通常の Account とは別の領域に永続化される.
InactiveAccount は次の属性を持つ.
id:AccountIDname:AccountNamemail:メールアドレスpassphraseHash:パスフレーズのハッシュrole:AccountRole
Note
メールアドレスの検証に用いるトークンは
InactiveAccountモデルに含まれない. トークンはAccountVerifyTokenRepositoryに accountID と対にして別途保存される.
登録中アカウントが作成されてから 168 時間(7 日間)が経過したものは無効とみなし,削除される.
AccountRelationship (アカウント関係)
あるアカウントから他のアカウントに対して成立する有向の関係を表すステートマシン. この関係は送信側(アクター)を起点とする一方向の状態であり,相手側の状態とは独立している.

Mermaid code
stateDiagram-v2
NONE --> REQUESTING_FOLLOW
REQUESTING_FOLLOW --> NONE
REQUESTING_FOLLOW --> FOLLOWING
FOLLOWING --> NONE
FOLLOWING --> BLOCKING
NONE --> BLOCKING
BLOCKING --> NONE
各状態の意味は次のとおり.
NONE:特定の関係がない状態REQUESTING_FOLLOW:フォローリクエストを送信し,相手の承認を待っている状態FOLLOWING:フォローしている状態BLOCKING:ブロックしている状態
フォロー関係の永続化エンティティは AccountFollow
で,フォロー元(fromID),フォロー先(targetID),作成日時(createdAt)を持つ.
Note モジュール
特に断りがない限り,文字列長 L は 以下のコードを実行して計算できる文字数
とする.
const count = (s: string) => {
const segmenter = new Intl.Segmenter("ja-JP", { granularity: "grapheme" });
return [...segmenter.segment(s)].length;
};
Note
- ユーザーによって行われる投稿.
- ノートは本文を持つ.
- UTF-8 文字列のみ許容する.
- 文字長は
1 ≦ L ≦ 3,000. - ノートがメディアファイルを含む場合のみ,
0 ≦ L ≦ 3,000.
- ノートの本文がセンシティブであるなどの理由で折りたたむ場合は CW
注釈を追加する.
- UTF-8 文字列のみ許容する.
- 文字長は
1 ≦ L ≦ 256.
- ノートにはメディアファイルを添付できる.
- メディアファイルは 16 個まで添付できる.
- メディアファイルの詳細な仕様は
Drive::Mediumを参照せよ.
- ノートは公開範囲を指定して行う.
- 公開範囲は以下の通り.
- 公開:
- パブリックタイムライン,ホームタイムライン,アカウントタイムラインに表示される.
- ホーム:
- パブリックタイムラインには表示されない.
- 自分およびフォロワーのホームタイムラインに表示される.
- アカウントのタイムラインに表示される.
- フォロワー限定:
- フォロワーのみ閲覧できる.
- リノートは投稿者自身のみ行える.
- ノートはリノートできる.
- リノート:投稿者自身または他のユーザーのノートをそのまま再投稿すること
- 本文:
- UTF-8 文字列,
L = 0.
- UTF-8 文字列,
- CW 注釈:
- UTF-8 文字列,
L = 0.
- UTF-8 文字列,
- メディアファイル:
- 添付できない.
- 本文:
- 引用:リノートのうち,再投稿に追加で本文を付け加えたもの.
- 本文,CW 注釈,メディアファイルは通常のノートと同じである.
- ただし,メディアファイルを含む投稿であっても文字長は
1 ≦ L ≦ 3,000.
- リノートも公開範囲を指定できる.
- ただし,元の公開範囲より広い範囲は指定できない.
- リノート:投稿者自身または他のユーザーのノートをそのまま再投稿すること
DirectNote (ダイレクト投稿)
- 宛先としてアカウントを指定し,宛先と投稿者自身のみ閲覧できるノート.
- ダイレクト投稿は宛先を必ず 1 つ指定する.
- 投稿者自身と宛先アカウントの対,およびその間で送受信されたノートの集合を会話と呼ぶ.
- どちらかのアカウントが削除された場合,会話は削除される.
- ダイレクト投稿はリノートも引用もできない.
- ダイレクト投稿は公開範囲を指定できない.
- ダイレクト投稿の本文,CW 注釈,メディアファイルの仕様はノートと同じである.
Reaction (リアクション)
- ノートに対して行うリアクション.
- リアクションは UTF-8 絵文字を 1 つ持つ.
- 絵文字の UTF-8 範囲は厳密に
/[\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF]/とする. - 複合絵文字は 1 文字として扱う.
- 絵文字の UTF-8 範囲は厳密に
- リアクションは 1 つのノートに対して 1 つしか付けられない.
Bookmark (ブックマーク)
- ノートを後で読むために保存するもの.
- ただし,ダイレクト投稿はブックマークできない.
- ブックマークは 1 つのノートに対して 1 つしか付けられない.
- ブックマークは付けた本人しか取得できない.
Drive モジュール
Drive モジュールはメディアファイルのアップロード・変換・保存を担う. ノートへの添付に使えるメディアファイルはすべてこのモジュールを経由して管理される.
Medium (メディアファイル)
ノートに添付できるメディアファイルを表すエンティティ. アップロード時に適切なフォーマットへの変換とサムネイル生成が行われる.
TypeScript 上ではメディアファイル固有の ID 型を次のように定義する.
export type MediumID = ID<Medium>;
Medium は次の属性を持つ.
id:MediumID- このメディアファイルを一意に識別する Snowflake ID
name:ファイル名authorId:このメディアファイルをアップロードしたアカウントの AccountIDhash:Blurhash によるプレースホルダー文字列- ファイルの読み込み完了前に表示するぼかしプレースホルダーとして利用される
- 画像以外の MIME タイプでは空文字列となる場合がある
mime:MIME タイプ(例:image/jpeg)nsfw:センシティブコンテンツのフラグtrueの場合,クライアントは閲覧前に警告を表示することが期待される
url:変換後のメディアファイルの URLthumbnailUrl:サムネイル画像の URL
List モデル
List
アカウントをグループ化するエンティティ.
リストタイムライン(GET /lists/{list_id}/notes)を通じて,アサインされたアカウントの投稿をまとめて閲覧できる.
TypeScript 上ではリスト固有の ID 型を次のように定義する.
export type ListID = ID<List>;
List は次の属性を持つ.
id:ListID- このリストを一意に識別する Snowflake ID
ownerId:作成者の AccountIDtitle:リストのタイトル- 文字長は
1 ≦ L ≦ 100
- 文字長は
public:公開フラグtrueの場合,アサイン操作の際にアサインされたアカウントへ通知が送られ,リスト作成者以外のアカウントからもリストを参照できるfalseの場合,アサイン通知は送られず,リスト作成者のみが参照できる
assignees:アサインされたアカウントの AccountID の集合
Note
一度の操作でアサインまたは削除できるアカウント数は最大 30.
Notification モジュール
Announcement (お知らせ)
インスタンス管理者がユーザー全体に向けて発信するメッセージ.
TypeScript 上ではお知らせ固有の ID 型を次のように定義する.
export type AnnouncementID = ID<Announcement>;
Announcement は次の属性を持つ.
id:AnnouncementIDtitle:タイトルdescription:本文type:AnnouncementKind(後述)createdAt:作成日時updatedAt:最終更新日時(省略可)unread:未読フラグ
AnnouncementKind (お知らせの種類)
info:一般的なお知らせwarn:ユーザー全体への警告
Notification (通知)
特定のアカウントに向けて発生する通知.
TypeScript 上では通知固有の ID 型を次のように定義する.
export type NotificationID = ID<Notification>;
Notification は次の属性を持つ.
id:NotificationIDtype:NotificationKind(後述)actor:NotificationActor(後述)createdAt:通知発生日時noteId:対象ノートの Snowflake IDmentioned,renoted,reactedのときのみ存在する
content:関連テキストrenotedではリノート元ノートの本文(CW 設定時は空文字列),reactedではリアクション絵文字renotedおよびreactedのときのみ存在する
NotificationKind (通知の種類)
followed:フォローされたfollowRequested:フォローをリクエストされたfollowAccepted:自分のフォローリクエストが承認されたmentioned:メンションされたrenoted:リノートされたreacted:リアクションされた
NotificationActor (通知の発生源)
通知を発生させた主体を表す判別共用体.
account:アカウントによる操作(ボットを含む)id:AccountIDname:AccountNamenickname:表示名avatar:アバター画像の URL
system:システムによる通知(モデレーター警告など)- 詳細は未定義
ドメインイベント
Pulsate は,ユーザーの操作や内部的な処理を非同期化し,リトライを可能にするためにイベント駆動アーキテクチャを採用している.
ドメインイベントは,操作によって生じた差分を記録し,他の処理を駆動するために必要なデータ(ペイロード)を運ぶオブジェクトである.
このページでは,モジュールごとにドメインイベントの発生条件,ペイロード,トリガーされる後続処理の一覧を示す.
共通のイベント形式
ドメインイベントは,次の共通フィールドを持つ.
export type EventID = ID<DomainEvent<unknown>>;
export interface DomainEvent<Payload> {
id: EventID;
name: string;
occurredAt: Date;
version: number;
payload: Payload;
}
id:EventID- このイベントを一意に識別する Snowflake ID
name:イベント名<ModuleName>.<ModelName>.<Action>の形式で表される,イベントの種類を示す文字列- 例:
account.registered,note.bookmark.created
occurredAt:イベント発生時刻version:ペイロードスキーマのバージョン番号payloadの構造に後方互換性のない変更を加えたときにインクリメントする- 購読側はこの番号を見て,対応していないバージョンのペイロードを判別できる
payload:イベント固有のデータ- 各イベントのペイロード型は
<PascalCase のイベント名>EventPayloadと命名する(例:AccountRegisteredEventPayload)
- 各イベントのペイロード型は
各イベントのペイロードは,操作を行ったアカウントを示すフィールドとして
actor(AccountID)を持つことを基本とする.
凍結,サイレンスなど,モデレーターが他のアカウントに対して行う操作では,actor
は操作を行ったモデレーター自身の AccountID
を指し,操作対象のアカウントとは別のフィールドで表す. account.registered
のように,イベントの結果として初めて識別される対象(InactiveAccount)についても,その対象自身の
AccountID を actor として扱う.
後続処理の一覧では,発火先を次の 2 種類で区別する.
- (モジュール間):
Intermoduleパッケージを介して他モジュールが購読し,駆動される処理(内部フローとモジュール間通信を参照) - (外部):メール送信など,システム外部のサービスと連携する処理
ドメインモデルでの扱い
基本的にドメインモデルはドメインイベントを発行する。 ドメインイベントはドメインモデルの内部状態を変更したときに発行され、ドメインイベントの内部の状態として保持される。
class Account {
// AccountEventsはAccountモデルで発生するDomainEventの型の集合
#events: AccountEvent[];
setBio(actor: AccountID, text: string) {
// ...略
this.#events.push(
accountEventFactory.updateName({
actor,
target: this.#id,
eventName: "account.bio.updated",
bio: text,
}),
);
}
}
ドメインイベントは明示的な操作で取り出すことができる。 このとき、ドメインイベントはすべて取り出され、ドメインモデル内部には残らない。
pullEvents(): AccountEvent[] {
// 破壊的に読み出される
return this.#events.splice(0);
}
取り出されたドメインイベントは,Application
Serviceによってイベントバスに発行(publish)される.
イベントバスは非同期にイベントを配送し,購読しているIntermodule
パッケージへ届ける.発行から後続処理までの一連の流れは,次のようになる.
sequenceDiagram
participant Service as Application Service
participant Account as Account(ドメインモデル)
participant EventBus as イベントバス
participant Intermodule
Service ->> Account: setBio(actor, text)
activate Account
Account ->> Account: 内部状態を変更しイベントを追加
deactivate Account
Service ->> Account: pullEvents()
Account -->> Service: AccountEvent[](内部の一覧は空になる)
Service ->> EventBus: 取り出したイベントを発行
EventBus ->> 他モジュール: 購読しているモジュールへ配送
他モジュール ->> Intermodule: 後続処理を実行
モジュール間のイベント
各イベントから後続処理への関係を,モジュール単位でまとめると次のようになる. 図中のラベルはイベント名と後続処理を示す.
flowchart LR
Account["Accountモジュール"]
Note["Noteモジュール"]
Drive["Driveモジュール"]
Timeline["Timelineモジュール"]
Notification["Notificationモジュール"]
Mail["メールサービス(外部)"]
Account -->|"account.registered: 検証コードのメール送信"| Mail
Account -->|"account.activated: タイムライン生成<br/>account.follow.blocked: タイムラインからの除去"| Timeline
Account -->|"account.follow.requested: 通知発火<br/>account.follow.accepted: フォロー成功通知発火<br/>account.follow.rejected: フォロー失敗通知発火"| Notification
Note -->|"note.created: タイムラインへの配信<br/>note.deleted: タイムラインからの除去"| Timeline
Note -->|"note.renoted: リノート通知<br/>note.reaction.created: リアクション通知"| Notification
Timeline -->|"list.member.appended: 通知発火"| Notification
Account モジュール
Account
account.registered:アカウント登録(仮登録状態)
- 発生条件:ユーザーが登録フォームを送信し,
InactiveAccountが作成されたとき - ペイロード(
AccountRegisteredEventPayload):actor:登録するアカウント自身の AccountID(InactiveAccountのid)name:AccountNamemail:メールアドレス
- 後続処理:
- (外部)検証コードのメール送信
account.activated:アカウントの有効化
- 発生条件:メールアドレスの検証が完了し,
InactiveAccountからAccountへ昇格したとき - ペイロード(
AccountActivatedEventPayload):actor:有効化されるアカウント自身の AccountID
- 後続処理:
- (モジュール間)タイムライン生成
account.bio.updated,account.nickname.updated,account.email.updated:プロフィール属性の更新
- 3 つのイベントは,更新された属性が異なるだけで同じ構造を持つため,まとめて記載する.
- 発生条件:ユーザーがプロフィール編集 API 経由で
bio,nickname,mailのいずれかを更新したとき - ペイロード:
actor(AccountID,更新を行った本人)に加えて,更新後の値を 1 件持つaccount.bio.updated(AccountBioUpdatedEventPayload):bio:自己紹介文account.nickname.updated(AccountNicknameUpdatedEventPayload):nickname:表示名account.email.updated(AccountEmailUpdatedEventPayload):mail:メールアドレス
account.admin.frozen,account.admin.unfrozen:管理者操作によるアカウントの凍結と解除
- 発生条件:
moderator以上のロールを持つアカウントが,対象アカウントを凍結/解除したとき - ペイロード(それぞれ
AccountAdminFrozenEventPayload,AccountAdminUnfrozenEventPayload):actor:操作を行った管理者の AccountIDaccountId:凍結/解除対象の AccountID
account.admin.silenced,account.admin.unsilenced:管理者操作によるサイレンス化と解除
- 発生条件:
moderator以上のロールを持つアカウントが,対象アカウントをサイレンス化/解除したとき - ペイロード(それぞれ
AccountAdminSilencedEventPayload,AccountAdminUnsilencedEventPayload):actor:操作を行った管理者の AccountIDaccountId:サイレンス化/解除対象の AccountID
AccountAvatar
account.avatar.updated:アバター設定
- 発生条件:ユーザーがアバター画像を設定または変更したとき
- ペイロード(
AccountAvatarUpdatedEventPayload):actor:AccountIDmediumId:設定されたメディアファイルの MediumID(Drive::Mediumを参照)
account.header.updated:ヘッダー設定
- 発生条件:ユーザーがヘッダー画像を設定または変更したとき
- ペイロード(
AccountHeaderUpdatedEventPayload):actor:AccountIDmediumId:設定されたメディアファイルの MediumID
AccountFollow
account.follow.requested:フォローリクエスト
- 発生条件:あるアカウントが他のアカウントに対してフォローリクエストを送信したとき(
AccountRelationshipがNONEからREQUESTING_FOLLOWへ遷移したとき) - ペイロード(
AccountFollowRequestedEventPayload):actor:フォローリクエストを送信したアカウントの AccountIDtargetId:フォローリクエストを受信したアカウントの AccountID
- 後続処理:
- (モジュール間)通知発火
account.follow.accepted:フォロー承認
- 発生条件:フォローリクエストが承認されたとき(
REQUESTING_FOLLOWからFOLLOWINGへ遷移したとき) - ここでの
actorは「このイベントを実際に発生させた側」を指し,承認を行った受信側のアカウントである.Account モジュール のAccountRelationshipでいう「アクター」(関係の起点となる送信側,fromId)とは別の概念であり,account.follow.requestedとは送信側と受信側が入れ替わる. - ペイロード(
AccountFollowAcceptedEventPayload):fromId:フォローリクエストを送信したアカウントの AccountIDactor:フォローリクエストを承認したアカウントの AccountID
- 後続処理:
- (モジュール間)フォロー成功通知発火
account.follow.rejected:フォロー拒否
- 発生条件:フォローリクエストが拒否されたとき(
REQUESTING_FOLLOWからNONEへ遷移したとき) actorは拒否を行った受信側のアカウントであり,account.mdの「アクター」(fromId)とは異なる(account.follow.acceptedと同様).- ペイロード(
AccountFollowRejectedEventPayload):fromId:フォローリクエストを送信したアカウントの AccountIDactor:フォローリクエストを拒否したアカウントの AccountID
- 後続処理:
- (モジュール間)フォロー失敗通知発火
account.follow.unfollowed:フォロー解除
- 発生条件:フォロー中のアカウントに対してフォローを解除したとき(
FOLLOWINGからNONEへ遷移したとき) - ペイロード(
AccountFollowUnfollowedEventPayload):actor:フォローを解除したアカウントの AccountIDtargetId:フォローを外されたアカウントの AccountID
account.follow.blocked:ブロック操作
- 発生条件:あるアカウントが他のアカウントをブロックしたとき(
AccountRelationshipがBLOCKINGへ遷移したとき) - ペイロード(
AccountFollowBlockedEventPayload):actor:ブロックを行ったアカウントの AccountIDtargetId:ブロックされたアカウントの AccountID
- 後続処理:
- (モジュール間)タイムラインからの除去
account.follow.unblocked:ブロック解除
- 発生条件:ブロックを解除したとき(
BLOCKINGからNONEへ遷移したとき) - ペイロード(
AccountFollowUnblockedEventPayload):actor:ブロックを解除したアカウントの AccountIDtargetId:ブロックを解除されたアカウントの AccountID
Note モジュール
Note
note.created:投稿作成
- 発生条件:ユーザーがノートを投稿したとき
- ペイロード(
NoteCreatedEventPayload):noteId:NoteIDactor:投稿者の AccountIDtext:本文cw:CW 注釈(存在する場合のみ)mediaIds:添付メディアファイルの MediumID の配列visibility:公開範囲(public,home,followersのいずれか)
- 後続処理:
- (モジュール間)タイムラインへの配信
- (検討中)
visibilityがdirectのダイレクト投稿(DirectNoteを参照)作成時に対応するイベントは本ページに未定義.本イベントの対象に含めるか,別イベントとするかは要検討
note.deleted:投稿削除
- 発生条件:投稿者自身または管理者がノートを削除したとき
- ペイロード(
NoteDeletedEventPayload):noteId:NoteIDactor:削除を行ったアカウントの AccountID(投稿者自身の場合と,管理者の場合がある)authorId:投稿者の AccountID(actorと異なる場合がある)
- 後続処理:
- (モジュール間)タイムラインからの除去
note.renoted:リノート
- 発生条件:投稿者自身または他のユーザーがノートをリノート(引用を含む)したとき
- リノートの解除には専用のイベントを設けない.解除操作は,リノート自体を対象とした
note.deletedとして扱う. - ペイロード(
NoteRenotedEventPayload):renoteId:新規に作成されたリノート自身の NoteIDtargetId:リノート元ノートの NoteIDactor:リノートを行ったアカウントの AccountID
- 後続処理:
- (モジュール間)リノート通知
Bookmark
note.bookmark.created:ブックマーク作成
- 発生条件:ユーザーがノートをブックマークしたとき
- ペイロード(
NoteBookmarkCreatedEventPayload):actor:ブックマークを行ったアカウントの AccountIDnoteId:対象ノートの NoteID
note.bookmark.deleted:ブックマーク削除
- 発生条件:ユーザーがブックマークを解除したとき
- ペイロード(
NoteBookmarkDeletedEventPayload):actor:ブックマークを解除したアカウントの AccountIDnoteId:対象ノートの NoteID
Reaction
note.reaction.created:リアクション作成
- 発生条件:ユーザーがノートにリアクションを付けたとき
- ペイロード(
NoteReactionCreatedEventPayload):actor:リアクションを行ったアカウントの AccountIDnoteId:対象ノートの NoteIDreaction:リアクションの絵文字
- 後続処理:
- (モジュール間)リアクション通知
note.reaction.deleted:リアクション解除
- 発生条件:ユーザーがリアクションを解除したとき
- ペイロード(
NoteReactionDeletedEventPayload):actor:リアクションを解除したアカウントの AccountIDnoteId:対象ノートの NoteID
Drive モジュール
Medium
medium.created:メディア作成
- 発生条件:ユーザーがメディアファイルをアップロードしたとき
- ペイロード(
MediumCreatedEventPayload):mediumId:MediumIDactor:アップロードしたアカウントの AccountIDmime:MIME タイプ
- 後続処理:
- (検討中)サムネイル生成.
Drive::Mediumの説明ではアップロード時に同期的にサムネイル生成が行われるとしており,本イベントを契機とした非同期処理が別途必要かは未確認
- (検討中)サムネイル生成.
medium.deleted:メディア削除
- 発生条件:アップロードしたアカウント自身または管理者がメディアファイルを削除したとき
- ペイロード(
MediumDeletedEventPayload):mediumId:MediumIDactor:削除を行ったアカウントの AccountID(アップロード者自身の場合と,管理者の場合がある)authorId:アップロードしたアカウントの AccountID(actorと異なる場合がある)
medium.admin.flagged,medium.admin.unflagged:管理者によるフラグの付与と解除
- 発生条件:
moderator以上のロールを持つアカウントが,メディアファイルにフラグを付与/解除したとき - ペイロード(それぞれ
MediumAdminFlaggedEventPayload,MediumAdminUnflaggedEventPayload):actor:操作を行った管理者の AccountIDmediumId:MediumID
Timeline モジュール
List
list.created:リスト作成
- 発生条件:ユーザーがリストを作成したとき
- ペイロード(
ListCreatedEventPayload):listId:ListIDactor:作成者の AccountID
list.deleted:リスト削除
- 発生条件:ユーザーがリストを削除したとき
- ペイロード(
ListDeletedEventPayload):listId:ListIDactor:作成者の AccountID
list.member.appended:リストメンバー追加
- 発生条件:リスト作成者がアカウントをリストにアサインしたとき
- ペイロード(
ListMemberAppendedEventPayload):listId:ListIDactor:アサインを行ったリスト作成者の AccountIDaccountId:アサインされたアカウントの AccountID
- 後続処理:
- (モジュール間,
publicがtrueのリストのみ)アサインされたアカウントへの通知発火
- (モジュール間,
list.member.removed:リストメンバー削除
- 発生条件:リスト作成者がアカウントをリストから外したとき
- ペイロード(
ListMemberRemovedEventPayload):listId:ListIDactor:削除を行ったリスト作成者の AccountIDaccountId:リストから外されたアカウントの AccountID
Notification モジュール
なし.
Notification
モジュールは,他モジュールが発行するドメインイベントを購読して通知(Notification)を生成する側であり,自身はドメインイベントを発行しない.
NotificationKind(Notification モジュールを参照)を増やす場合は,対応する購読元イベントが本ページに存在するかをあわせて確認すること.
APIについて
main ブランチの最新 API リファレンスは
api.pulsate.dev/reference で閲覧可能.
APIのバージョニング
APIの規定パス(Base Path)は /api/v0 である.
APIのバージョンは以下の通り.
| バージョン | ステータス |
|---|---|
| v0 | 策定途中 |
資格情報が必要なエンドポイントの実行方法
Pulsate v0 APIでは殆どのエンドポイントで資格情報が必要である.
資格情報が必要なエンドポイントへアクセスするためには, 以下の方法でエンドポイントにアクセスする必要がある.
エンドポイントへアクセスするときのAuthorization HTTPヘッダーに
Bearer <token>
の形式で 認証トークン をつけて送信する必要がある.
例:
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
資格情報が必要ないエンドポイントについて Pulsate v0
APIでは、資格情報が必要なエンドポイントであっても、資格情報がない場合にアクセス可能なエンドポイントが存在する。
例えば、あるアカウントのタイムラインを取得するエンドポイントの場合:
- 有効な資格情報がある場合
- フォローしている場合は FOLLOWER 公開範囲の投稿も返す
- フォローしていない場合は資格情報がない場合と変わらない
- 資格情報が無効な場合
- 認証トークンが期限切れや改ざんされている場合
- 認証エラーを返す
- 資格情報がない場合
Authorizationヘッダーが空の場合- PUBLIC/HOME 公開範囲の投稿を返す
“文字数”の扱い
特に明記しない限り、文字数は 以下のコードを実行して計算できる文字数 とする.
const count = (s: string) => {
const segmenter = new Intl.Segmenter("ja-JP", { granularity: "grapheme" });
return [...segmenter.segment(s)].length;
};
このAPIを実装する実行環境のリストはMDNを参照せよ.
Intl.Segmenter - JavaScript | MDN
“時刻/時間”の扱い
特に明記しない限り、システム上の時刻の扱いは以下の通りである.
- 日時は例外なくタイムゾーンをUTCとして扱う
- 時刻のフォーマットは ISO 8601 とする.
"yyyy-MM-DDTHH:mm:ss.SSSZ"形式- タイムゾーン指定されていた場合は指定したタイムゾーンの日時をUTCに変換する.
- 例:
2023-11-21T09:00:00.000+09:00: 日本時間(JST, UTC+9) 2023年11月21日09:00:00.000 →2023-11-21T00:00:00.000Z: 世界協定時(UTC) 2023年11月21日00:00:00.000
- 例:
- 以上の形式に合致しない形で日時の表現がされた場合はエラー終了するものとする.
絵文字の扱い
絵文字とは, Unicodeによって定義される絵文字とカスタム絵文字のことである.
その他の記号や環境依存文字は絵文字ではないとする.
Unicode絵文字
Unicode絵文字とは,Unicodeによって定義される絵文字である.
APIとやり取りするテキストデータでは特殊な記法を用いずUnicodeで表現する.
例:
{
"content": "🎉"
}
カスタム絵文字
カスタム絵文字を文字列内に埋め込むための形式は以下の通りである.
以下の形式に沿わない場合は単なる文字列であると解釈する.
- 全体を
<>で囲む- カスタム絵文字のエイリアスは
::で囲む - カスタム絵文字に関する詳細な定義はここでは行わない. (ToDo)
- カスタム絵文字のエイリアスは
- エイリアスのあとに(絵文字があるインスタンスではなく,APIを提供するインスタンスでの)カスタム絵文字のIDを表記する.
例:
<:alias:2949583895994> <:ii_hanashi:284745363>
特殊なテキスト埋め込みオブジェクト
投稿本文, CW注釈, アカウントbio のテキスト内にカスタム絵文字やメンションなど, Unicodeで規定された文字以外のデータを表現するときの記法
カスタム絵文字は カスタム絵文字 を参照せよ.
メンション
表記法は以下の通り
- メンションは
<>で囲む - 囲んだ中にアカウント名を入れる
- RFC 7565で規定される形式
のスキームである
acct:を取り除き,@に置き換えたもの - 例: アカウント名が
@[email protected]である場合
- RFC 7565で規定される形式
のスキームである
<@[email protected]>
- 宛先のホスト名(FQDN)は 省略不可
<@[email protected]> <@[email protected]> <@[email protected]>
基本エラーオブジェクト
APIにおいて何らかのエラーが発生した際には以下のようなエラーオブジェクトを返却する.
{
"error": "TEST_ERROR_CODE"
}
errorの中にはエラーコード が入る.
エラーコードはエラーを返却するAPIのエンドポイントでどのようなコードを返すか指定するものとする.
エラーオブジェクトはエラーコードのみをメンバーとして含むオブジェクトである.
エラーコード以外の情報をエラーオブジェクトとして返却することは許容されない.
NSFWフラグ
言葉の意味:
NSFWとは、英語圏で用いられるインターネットスラングで、「職場では見ない方がいい」(職場での閲覧は危険)という意味の略語である。日本語の「閲覧注意」におおむね相当する。
NSFWは画像がメインの掲示板や、掲示板中の画像へのリンクなどに付けて用いられやすい。たいていは卑猥(エロ画像)・残酷(グロ画像)といった要素の強い、不用意な閲覧ははばかられる類のコンテンツが掲載されている。
- NSFWフラグとは,画像/音声/動画の内容がNSFWであることを示すためのフラグである.
- 大抵の場合画像/音声/動画にぼかし(ブラー)がかけられたり,閲覧注意の文言が(システムのUI上で)表示される.
ContentsWarning/CWフラグ
- CWフラグとは,投稿本文に何らかのコンテンツ警告があることを示すフラグである.
- 大抵の場合,投稿の注釈(投稿本文より短い,本文の内容を端的に表した文)のみが表示され,本文は[続きを読む]といったボタンを押さないと見ることができない
アカウントAPI
POST /accounts ✓
新規アカウントを作成する 正確には登録中の状態のアカウントを作成し,アカウント登録スキームを開始するものである.
入力
body: application/json
| 項目名 | 型 | 制約/説明 | 文字数制約 | 例 |
|---|---|---|---|---|
| name | string | 文字は A-Z a-z 0-9 - . _である必要がある. 先頭,及び最後の文字は A-Z a-z 0-9 のみとする. | 1 ≤ N ≤ 64 [文字] | john ※登録される情報は@[email protected] |
string | メールアドレスとして正しい形式 (メールアドレスを受信可能であるかは問わない) | 7≤N≤319[文字] | [email protected] | |
| passphrase | string | スペース,タブ,全角スペース,改行,ヌルを除くUTF-8文字列 | 8≤N≤512[文字] | じゃすた・いぐざんぽぅ, just~@_examp1e! |
| captcha_token | string | Cloudflare Turnstile などの手動操作検証のトークン |
入力例
{
"name": "example",
"email": "[email protected]",
"passphrase": "じゃすた・いぐざんぽぅ",
"captcha_token": "hogehogehgoe"
}
出力
200 OK
{
"id": "38477395",
"name": "example",
"email": "[email protected]"
}
body: application/json
| 項目名 | 型 | 説明 | 文字数 | 例 |
|---|---|---|---|---|
| id | snowflake | アカウントのID | - | 30848577730000 |
| name | string | ユーザー名 | 8≤N≤512[文字] | @[email protected] |
string | メールアドレスとして正しい形式 (メールアドレスを受信可能であるかは問わない) | 7≤N≤319[文字] | [email protected] |
400 Bad Request
{
"error": "TEST_ERROR_CODE"
}
INVALID_ACCOUNT_NAME: 使用できない文字が含まれている,文字の使用制限に違反しているTOO_LONG_ACCOUNT_NAME: アカウント名が長すぎるEMAIL_IN_USE: メールアドレスが既に使用されているYOU_ARE_BOT: captcha_token の検証に失敗した
409 Conflict
{
"error": "TEST_ERROR_CODE"
}
ACCOUNT_NAME_IN_USE: アカウント名が既に使用されているEMAIL_IN_USE: メールアドレスが既に使用されている
PATCH /accounts/{account_name} ✓
アカウント情報を指定のパラメータで編集するエンドポイント.
入力の body から次の情報のいずれか 1 つ以上を受け付け、それらを同時に適用できるかを検証し,反映する.
競合を防ぐために「更新前のニックネームと更新前のメールアドレスを結合した文字列」のハッシュを ETag (Entity Tag) として用いる.
これをMD5でハッシュするものとする.
メールアドレスを更新した場合はそのメールアドレスの確認スキームが開始される.
そしてそのメールアドレスが確認されるまで,メールアドレスの更新処理は遅延される.
ニックネームも同時に更新した場合,ニックネームの更新は先に反映される.
Warning
ETag が一致しないときは 412 Precondition Failed のエラーとなる. 結合方法は
更新前のニックネーム:更新前のメールアドレスである
入力
- body:
application/json
| 項目名 | 型 | 制約/説明 | 文字数制約 | 例 |
|---|---|---|---|---|
| name | string | 文字は A-Z a-z 0-9 - . _である必要がある. 先頭,及び最後の文字は A-Z a-z 0-9 のみとする. | 1 ≤ N ≤ 64 [文字] | john ※登録される情報は@[email protected] |
string | メールアドレスとして正しい形式 (メールアドレスを受信可能であるかは問わない) | 7≤N≤319[文字] | [email protected] | |
| passphrase | string | スペース,タブ,全角スペース,改行,ヌルを除くUTF-8文字列 | 8≤N≤512[文字] | じゃすた・いぐざんぽぅ, just~@_examp1e! |
| captcha_token | string | Cloudflare Turnstile などの手動操作検証のトークン | ||
| bio | string | 自己紹介文. 0文字である場合はnullやundefinedではなく空の文字列""である必要がある. | 0≤N≤1024 | "" (空文字列), いい感じの自己紹介🆓, This is bio hello^~ <:javascript:358409384> |
入力例
{
"nickname": "example",
"email": "[email protected]",
"passphrase": "じゃすた・いぐざんぽぅ",
"bio": "テストユーザーなのぜ"
}
出力
200 OK
編集に成功しました.
{
"id": "38477395",
"name": "@[email protected]",
"nickname": "John Doe",
"bio": "テストユーザーなのぜ",
"email": "[email protected]"
}
| 項目名 | 型 | 説明 | 文字数 | 例 |
|---|---|---|---|---|
| id | snowflake | アカウントのID | - | 30848577730000 |
| name | string | ユーザー名 | 8≤N≤512[文字] | @[email protected] |
string | メールアドレスとして正しい形式 (メールアドレスを受信可能であるかは問わない) | 7≤N≤319[文字] | [email protected] | |
| nickname | string | 表示名,アカウントの表示に用いる短い文字列 | 1≤N≤256 | JohnDoe<:json:299384730049> |
ジョン・ドゥ🚉 |
202 Accepted
メールアドレスが更新された場合
{
"id": "38477395",
"name": "@[email protected]",
"nickname": "John Doe",
"bio": "テストユーザーなのぜ",
"email": "[email protected]"
}
400 Bad Request
{
"error": "TEST_ERROR_CODE"
}
INVALID_SEQUENCE: パラメータ内に使用不可能な文字種を含んでいるVULNERABLE_PASSPHRASE: 新しいパスフレーズがパスフレーズとしての要件を満たしていない
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
ACCOUNT_NOT_FOUND: 名前がaccount_nameのアカウントが見つからない
412 Precondition Failed
{
"error": "TEST_ERROR_CODE"
}
INVALID_ETAG: ETagが不正である
PUT /accounts/{account_name}/freeze ✓
アカウントを凍結するエンドポイント.
モデレータ以上の権限を持つアカウント認証情報が必要である.
凍結されると以下のような挙動になる.
- ログインできなくなる.
- ログインしようとするとエラー終了する.
- 投稿できなくなる.
- (公開APIを除く)いかなるAPIへの操作も受け付けなくなる.
入力
- パスパラメータ
account_name:string- アカウント名
- body:
application/json- 空のオブジェクトを送信せよ.
- 空でない場合のメンバーは無視される.
- 空のオブジェクトを送信せよ.
入力例
{}
出力
204 No Content
凍結が完了した.
※レスポンスボディは空になる.
400 Bad Request
{
"error": "TEST_ERROR_CODE"
}
ALREADY_FROZEN: すでに凍結済みである.
403 Forbidden
{
"error": "TEST_ERROR_CODE"
}
NO_PERMISSION: アカウントを凍結できる権限がない.
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
ACCOUNT_NOT_FOUND: アカウントが見つからない.
DELETE /accounts/{account_name}/freeze ✓
アカウントを凍結解除する(解凍と表記することもある).
モデレータ以上の権限の持つアカウント認証情報が必要.
入力
- パスパラメータ
account_name:string- アカウント名
- body:
application/json- 空のオブジェクトを送信せよ.
- 空でない場合のメンバは無視される.
- 空のオブジェクトを送信せよ.
入力例
{}
出力
204 No Content
凍結解除した.
※レスポンスボディは空になる.
403 Forbidden
{
"error": "TEST_ERROR_CODE"
}
NO_PERMISSION: アカウントを凍結解除できる権限がない.
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
ACCOUNT_NOT_FOUND: アカウントが見つからない.
POST /accounts/{account_name}/resend_verify_email ✓
メールアドレスの検証コードを再送
入力
- パスパラメータ
account_name:string- アカウント名
body: application/json
| 項目名 | 型 | 制約/説明 | 文字数 | 例 |
|---|---|---|---|---|
| captcha_token | string | Cloudflare Trunstileなどの検証トークン |
入力例
{
"captcha_token": "hogehogehgoe"
}
出力
204 No Content
再送した
※レスポンスボディは空になる.
400 Bad Request
{
"error": "TEST_ERROR_CODE"
}
ACCOUNT_ALREADY_VERIFIED: アカウントのメールアドレスはすでに検証されている.
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
ACCOUNT_NOT_FOUND: アカウントが見つからない
POST /accounts/{account_name}/verify_email ✓
メールアドレス認証が終了していないアカウントに対して,
メールアドレスに送信された認証トークンを検証することでメールアドレス認証を行う.
処理が完了すると,検証済みアカウントになりログインなどの各種操作が行えるようになる(ToDo 行うことのできる操作の一覧へのリンク
入力
- パスパラメータ
account_name:string- アカウント名
- body:
application/json
| 項目名 | 型 | 制約/説明 | 文字数 | 例 |
|---|---|---|---|---|
| token | string | 認証トークン: モデル を参照 |
入力例
{
"token": "vq34rvyanho10q9hbc98ydbvaervna43r0varhj"
}
出力
204 No Content
検証に成功
※ レスポンスボディは空になります
400 Bad Request
{
"error": "TEST_ERROR_CODE"
}
INVALID_TOKEN: トークンの検証に失敗.
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
ACCOUNT_NOT_FOUND: 指定した名前のアカウントは存在しない.
POST /login ✓
ログインして認証/更新トークンを作成します.
Warning
認証トークンの有効期限は15分(900秒), 更新トークンの有効期限は30日(2,592,000秒)である. どちらのトークンも,APIサーバーが再起動されると無効になり,その場合は再度ログインする必要がある.
入力
- body:
application/json
| 項目名 | 型 | 制約/説明 | 文字数 | 例 |
|---|---|---|---|---|
| name | string | アカウント名 | 8≤N≤512[文字] | @[email protected] |
| passphrase | string | パスフレーズ | 8≤N≤512 | じゃすた・いぐざんぽぅ, just~@_examp1e! |
| captcha_token | string | Cloudflare Trunstileなどの検証トークン |
入力例
{
"name": "@[email protected]",
"passphrase": "じゃすた・いぐざんぽぅ",
"captcha_token": "hogehogehoge"
}
出力
200 OK
ログインしました
{
"authorization_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJzdWIiOiIzZTE2NDQ4MzMwMDAwMDIiLCJpYXQiOjE2NDA5OTUyMDEsInJlZnJlc2hfdG9rZW4iOiJleUpoYkdjaU9pSklVekkxTmlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKemRXSWlPaUl6WlRFMk5EUTR Nek13TURBd01ESWlMQ0pwWVhRaU9qRTJOREE1T1RVeU1ERjkud2Q4cWJVcWowWGtCU1hud0FxM0lRYU1nQS1RTFd2MHVKU1NLX3BIVTZCYyJ9.mRUfLIYOGlLuC9D72zBriVvrHYrQgVHW7ntQ-bp5SHs",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJzdWIiOiIzZTE2NDQ4MzMwMDAwMDIiLCJpYXQiOjE2NDA5OTUyMDEsInJlZnJlc2hfdG9rZW4iOiJleUpoYkdjaU9pSklVekkxTmlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKemRXSWlPaUl6WlRFMk5EUTR Nek13TURBd01ESWlMQ0pwWVhRaU9qRTJOREE1T1RVeU1ERjkud2Q4cWJVcWowWGtCU1hud0FxM0lRYU1nQS1RTFd2MHVKU1NLX3BIVTZCYyJ9.mRUfLIYOGlLuC9D72zBriVvrHYrQgVHW7ntQ-bp5SHs",
"expires_in": 1672498800
}
| 項目名 | 型 | 制約/説明 | 文字数 | 例 |
|---|---|---|---|---|
| authorization_token | string | 認証トークン | ||
| refresh_token | string | 更新トークン | ||
| expires_in | number | 有効期限. Pulsate Epochからの秒数: モデル を参照. |
400 Bad Request
{
"error": "TEST_ERROR_CODE"
}
FAILED_TO_LOGIN: パスフレーズかアカウント名が間違っているYOU_ARE_BOT: captchaトークンの検証に失敗
403 Forbidden
{
"error": "TEST_ERROR_CODE"
}
YOU_ARE_FROZEN: ログインしようとしたアカウントは凍結されている
POST /refresh ✓
更新トークンで認証トークンを再発行
入力
- body:
application/jsonrefresh_token:string- 更新トークン
| 項目名 | 型 | 制約/説明 | 文字数 | 例 |
|---|---|---|---|---|
| refresh_token | string | 更新トークン 参照 |
入力例
{
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzZTE2NDQ4MzMwMDAwMDIiLCJpYXQiOjE2NDA5OTUyMDEsInJlZnJlc2hfdG9rZW4iOiJleUpoYkdjaU9pSklVekkxTmlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKemRXSWlPaUl6WlRFMk5EUTRNek13TURBd01ESWlMQ0pwWVhRaU9qRTJOREE1T1RVeU1ERjkud2Q4cWJVcWowWGtCU1hud0FxM0lRYU1nQS1RTFd2MHVKU1NLX3BIVTZCYyJ9.mRUfLIYOGlLuC9D72zBriVvrHYrQgVHW7ntQ-bp5SHs"
}
出力
200 OK
ログインに成功した
{
"authorization_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzZTE2NDQ4MzMwMDAwMDIiLCJpYXQiOjE2NDA5OTUyMDEsInJlZnJlc2hfdG9rZW4iOiJleUpoYkdjaU9pSklVekkxTmlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKemRXSWlPaUl6WlRFMk5EUTRNek13TURBd01ESWlMQ0pwWVhRaU9qRTJOREE1T1RVeU1ERjkud2Q4cWJVcWowWGtCU1hud0FxM0lRYU1nQS1RTFd2MHVKU1NLX3BIVTZCYyJ9.mRUfLIYOGlLuC9D72zBriVvrHYrQgVHW7ntQ-bp5SHs"
}
| 項目名 | 型 | 制約/説明 | 文字数 | 例 |
|---|---|---|---|---|
| autorization_token | string | 認証トークン | ||
| 共通 を参照 |
400 Bad Request
{
"error": "TEST_ERROR_CODE"
}
INVALID_TOKEN: 更新トークンが不正EXPIRED_TOKEN: トークンの有効期限切れ
GET /accounts/{account_name} ✓
アカウント情報を取得
入力
- パスパラメータ
account_name:string- アカウント名
出力
200 OK
取得に成功
{
"id": "2874987398",
"name": "@[email protected]",
"nickname": "John Doe",
"bio": "I am Test User.",
"avatar": "https://example.com/images/avatar.png",
"header": "https://example.com/images/header.png",
"followed_count": 200,
"following_count": 10,
"note_count": 20000
}
| 項目名 | 型 | 制約/説明 | 文字数 | 例 |
|---|---|---|---|---|
| id | snowflake | アカウントID | 2934002842 | |
| name | string | アカウント名 | 8≤N≤512[文字] | @[email protected] |
| nickname | string | 表示名,アカウントの表示に用いる短い文字列 | 1≤N≤256 | JohnDoe<:json:299384730049>, ジョン・ドゥ🚉 |
| bio | string | 自己紹介文. 0文字である場合はnullやundefinedではなくからの文字列である必要がある. | 0≤N≤1024 | "" (空文字列), いい感じの自己紹介🆓,This is bio hello^~ <:javascript:358409384> |
| avatar | string | アカウントのアイコン画像のURL | ||
| header | string | アカウントのヘッダー画像のURL | ||
| followed_count | number | そのアカウントをフォローしている人数 | ||
| following_count | number | そのアカウントがフォローしている人数 | ||
| note_count | number | そのアカウントの投稿数 |
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
ACCOUNT_NOT_FOUND: 指定した名前のアカウントは存在しない
PUT /accounts/{account_name}/silence ✓
アカウントをサイレンスする.モデレータ以上の権限を持つアカウント認証情報が必要.
サイレンスされると以下のような挙動になる.
- 公開投稿(投稿範囲が
publicになる投稿)ができなくなる
入力
- パスパラメータ
account_name:string- アカウント名
- body:
application/json- ボディは空オブジェクトである必要がある.
- 空でない場合,メンバは無視される.
入力例
{}
出力
204 No Content
凍結に成功
※レスポンスボディは空になる.
403 Forbidden
{
"error": "TEST_ERROR_CODE"
}
NO_PERMISSION: アカウントをサイレンスできる権限がない.
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
ACCOUNT_NOT_FOUND: 指定した名前のアカウントは存在しない.
DELETE /accounts/{account_name}/silence ✓
アカウントをサイレンス解除
モデレータ以上の権限の持つアカウント認証情報が必要.
入力
- パスパラメータ
account_name:string- アカウント名
- body:
application/json- オブジェクトは空である必要がある.
- 空でない場合,メンバは無視される.
入力例
{}
出力
204 No Content
サイレンスを解除
※レスポンスボディは空になる.
403 Forbidden
{
"error": "TEST_ERROR_CODE"
}
NO_PERMISSION: アカウントをサイレンス解除できる権限がない
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
ACCOUNT_NOT_FOUND: 指定した名前のアカウントは存在しない
POST /accounts/{account_name}/follow ✓
指定したアカウントをフォロー
入力
- パスパラメータ
account_name:string- フォローしたいアカウント名
- body:
application/json- 空オブジェクトを送信せよ
- 空でない場合のメンバは無視される
- 空オブジェクトを送信せよ
入力例
{}
出力
201 Accepted
フォローを受け付けた
{
"pending": true
}
- フォローする相手がフォローを手動承認制にしている場合はpendingがtrueになる.
- フォローする相手がフォローを手動承認制にしていない場合もある.
- この場合はpendingは必ず
falseになる.
- この場合はpendingは必ず
- フォローする相手がフォローを手動承認制にしていない場合もある.
- v2以降の予定: フォローする相手が同じインスタンスに所属していない場合はすべての場合でpendingがtrueになる.
400 Bad Request
{
"error": "TEST_ERROR_CODE"
}
ALREADY_FOLLOWING: アカウントをすでにフォローしているYOU_ARE_BLOCKED: 相手にブロックされている
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
ACCOUNT_NOT_FOUND: 指定した名前のアカウントは存在しない.
DELETE /accounts/{account_name}/follow ✓
フォローを解除します
入力
- パスパラメータ
account_name:string- フォロー解除したいアカウント名
- body:
application/json- リクエストボディは空オブジェクトを送信せよ.
- 空でない場合のメンバは無視される.
- リクエストボディは空オブジェクトを送信せよ.
入力例
{}
出力
204 No Content
フォローを解除した.
※レスポンスボディは空になる.
400 Bad Request
{
"error": "TEST_ERROR_CODE"
}
YOU_ARE_NOT_FOLLOW_ACCOUNT: 指定したアカウントをフォローしていない
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
ACCOUNT_NOT_FOUND: 指定した名前のアカウントは存在しない
ドライブAPI
GET /drive
自分がアップロードした画像のリストを取得します
入力
なし
出力
200 OK
[
{
"id": "2938492384",
"name": "image.jpeg",
"author_id": "384880009940302",
"hash": "nf9:e;g711*c@drgj55",
"mime": "image/jpeg",
"nsfw": false,
"url": "https://images.example.com/image.jpeg",
"thumbnail": "https://images.example.com/thumb-image.jpeg"
}
]
| 項目名 | 型 | 説明 | 制約 | 例 |
|---|---|---|---|---|
| id | snowflake | ファイルのID | ||
| name | string | ファイル名 | 1≤N≤256[文字] | |
| author_id | snowflake | アップロードしたアカウントのID | ||
| hash | string | 画像のblurhash | ||
| mime | string | mimeタイプ | ||
| nsfw | boolean | NSFWフラグ | ||
| url | string | 画像へのリンク | ||
| thumbnail | string, undefined | (利用可能な場合のみ) 縮小版のサムネイル画像 |
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
FILE_NOT_FOUND: ファイルが存在しません
POST /drive/
添付ファイルをアップロードします
入力
- body:
multipart/form-data
| 項目名 | 型 | 説明 | 制約 | 例 |
|---|---|---|---|---|
| name | string | ファイル名拡張子が必須 | 1≤N≤256[文字] | neko.png, 箱根駅伝.mp3 |
| file | (ファイルの実体) | アップロードするファイル. アップロード可能なファイル種類は後述 | 1≤100 [MB] | |
| nsfw | string ( "true", "false" ) | NSFWフラグ |
アップロード可能なファイル種類(mimeタイプ) mimeタイプについて詳しくは [https://www.iana.org/assignments/media-types/media-types.xhtml](https://www.iana.org/assignments/media-types/media-types.xhtml) を参照
画像:
image/apngimage/avifimage/gifimage/jpegimage/pngimage/webp
音声、動画:
audio/wave,audio/wavaudio/webmaudio/mpegvideo/mpegvideo/webmaudio/ogg
出力
200 OK
アップロードが完了しました.
{
"id": "2938492384",
"name": "image.jpeg",
"author_id": "493094050",
"hash": "nf9:e;g711*c@drgj55",
"mime": "image/jpeg",
"nsfw": false,
"url": "https://images.example.com/image.jpeg",
"thumbnail": "https://images.example.com/thumb-image.jpeg"
}
| 項目名 | 型 | 説明 | 制約 | 例 |
|---|---|---|---|---|
| id | snowflake | ファイルのID | ||
| name | string | ファイル名 | 1≤N≤256[文字] | |
| author_id | snowflake | アップロードしたアカウントのID | ||
| hash | string | 画像のblurhash | ||
| mime | string | mimeタイプ | ||
| nsfw | boolean | NSFWフラグ | ||
| url | string | 画像へのリンク | ||
| thumbnail | string, undefined | (利用可能な場合のみ) 縮小版のサムネイル画像 |
400 Bad Request
{
"error": "TEST_ERROR_CODE"
}
FILE_SIZE_TOO_BIG: ファイルサイズが大きすぎますFILE_NAME_TOO_LONG: ファイル名が長すぎます
DELETE /drive/{file_id}
指定したファイルを削除します
Warning
ファイルを削除すると,紐付けられているすべてのノートに影響します.
入力
- パスパラメータ
file_id:snowflake- ファイルのID
出力
204 No Content
削除しました.
※レスポンスボディは空になります.
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
FILE_NOT_FOUND: ファイルが存在しません
GET /drive/{file_id}
ファイルのメタ情報を取得します
入力
- パスパラメータ
file_id:snowflake- ファイルのID
出力
200 OK
{
"id": "2938492384",
"name": "image.jpeg",
"author_id": "384880009940302",
"hash": "nf9:e;g711*c@drgj55",
"mime": "image/jpeg",
"nsfw": false,
"url": "https://images.example.com/image.jpeg",
"thumbnail": "https://images.example.com/thumb-image.jpeg"
}
| 項目名 | 型 | 説明 | 制約 | 例 |
|---|---|---|---|---|
| id | snowflake | ファイルのID | ||
| name | string | ファイル名 | 1≤N≤256[文字] | |
| author_id | snowflake | アップロードしたアカウントのID | ||
| hash | string | 画像のblurhash | ||
| mime | string | mimeタイプ | ||
| nsfw | boolean | NSFWフラグ | ||
| url | string | 画像へのリンク | ||
| thumbnail | string, undefined | (利用可能な場合のみ) 縮小版のサムネイル画像 |
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
FILE_NOT_FOUND: ファイルが存在しません
PUT /drive/{file_id}
ファイルの情報を変更します
Warning
情報を変更すると、そのファイルが紐付けられているすべてのノートに影響します
入力
-
パスパラメータ
file_id:snowflake- ファイルのID
-
body:
application/json
| 項目名 | 型 | 説明 | 制約 | 例 |
|---|---|---|---|---|
| nsfw | boolean | NSFWフラグ |
入力例
{
"nsfw": false
}
出力
200 OK
{
"id": "2938492384",
"name": "image.jpeg",
"author_id": "384880009940302",
"hash": "nf9:e;g711*c@drgj55",
"mime": "image/jpeg",
"nsfw": false,
"url": "https://images.example.com/image.jpeg",
"thumbnail": "https://images.example.com/thumb-image.jpeg"
}
| 項目名 | 型 | 説明 | 制約 | 例 |
|---|---|---|---|---|
| id | snowflake | ファイルのID | ||
| name | string | ファイル名 | 1≤N≤256[文字] | |
| author_id | snowflake | アップロードしたアカウントのID | ||
| hash | string | 画像のblurhash | ||
| mime | string | mimeタイプ | ||
| nsfw | boolean | NSFWフラグ | ||
| url | string | 画像へのリンク | ||
| thumbnail | string, undefined | (利用可能な場合のみ) 縮小版のサムネイル画像 |
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
FILE_NOT_FOUND: ファイルが存在しません
| 項目名 | 型 | 説明 | 制約 | 例 |
|---|---|---|---|---|
| id | snowflake | ファイルのID | ||
| name | string | ファイル名 | 1≤N≤256[文字] | |
| author_id | snowflake | アップロードしたアカウントのID | ||
| hash | string | 画像のblurhash | ||
| mime | string | mimeタイプ | ||
| nsfw | boolean | NSFWフラグ | ||
| url | string | 画像へのリンク | ||
| thumbnail | string, undefined | (利用可能な場合のみ) 縮小版のサムネイル画像 |
リストAPI
POST /lists/
リストを作成します
入力
- body:
application/json
| 項目名 | 型 | 説明 | 制約 | 例 |
|---|---|---|---|---|
| title | string | リストのタイトル | 1≤N≤100[文字] | |
| public | boolean | デフォルト: false (非公開) 公開: リストにアサインされたアカウントには通知が飛びます. 非公開: 通知は飛ばず、自分以外のアカウントからは見えません |
入力例
{
"title": "Pulsate Developers",
"public": false
}
出力
200 OK
{
"id": "18342938400393",
"title": "Pulsate Developers",
"public": false
}
400 Bad Request
{
"error": "TEST_ERROR_CODE"
}
TITLE_TOO_LONG: タイトルが長すぎます
PATCH /lists/{list_id}
リスト情報を編集します
入力
-
パスパラメータ
list_id:string- 編集したいリストのID
-
body:
application/json
| 項目名 | 型 | 説明 | 制約 |
|---|---|---|---|
| title | string | リストのタイトル | 1≤N≤100[文字] |
| public | boolean | 公開・非公開のフラグ |
入力例
{
"title": "Edited Title",
"public": true
}
出力
200 OK
{
"id": "18342938400393",
"title": "Edited Title",
"public": true,
"assignees": [
{
"id": "1838933554",
"name": "@[email protected]"
}
]
}
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
LIST_NOTFOUND: リストが見つかりません
400 Bad Request
{
"error": "TEST_ERROR_CODE"
}
TITLE_TOO_LONG: タイトルが長すぎます
GET /lists/{list_id}
リスト情報を取得します
入力
- パスパラメータ
list_id:string- 取得したいリストのID
出力
200 OK
{
"id": "18342938400393",
"title": "Pulsate Developers",
"public": false,
"assignees": [
{
"id": "1838933554",
"name": "@[email protected]"
}
]
}
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
LIST_NOTFOUND: リストが見つかりません
GET /lists/accounts/{account_id}
アカウントが持つリストを取得します
入力
- パスパラメータ
account_id:snowflake- アカウントのID
出力
200 OK
[
{
"id": "18342938400393",
"title": "Pulsate Developers",
"public": false,
"assignees": [
{
"id": "1838933554",
"name": "@[email protected]"
}
]
}
]
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
ACCOUNT_NOT_FOUND: ユーザーが存在しませんLIST_NOTFOUND: リストが見つかりません
POST /lists/{list_id}
リストにアカウントをアサインします
入力
- パスパラメータ
list_id:string- リストのID
- body:
application/json- account_id:
Array<snowflake>- アカウントID
- 一度にアサインできる最大アカウント数: 30
- account_id:
出力
200 OK
{
"account_id": [
"389384553329569",
"586039500493885",
"4847377595"
]
}
400 Bad Request
{
"error": "TEST_ERROR_CODE"
}
TOO_MANY_TARGETS: アサインするアカウント数が多すぎます
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
ACCOUNT_NOT_FOUND: アカウントが存在しませんLIST_NOTFOUND: リストが見つかりません
DELETE /lists/{list_id}
リストからアカウントを削除します
入力
- パスパラメータ
list_id:string- リストのID
- body:
application/json- account_id:
Array<snowflake>- 削除するアカウントのID
- 一度に削除できるアカウント数: 30
- account_id:
入力例
{
"account_id": [
"389384553329569",
"586039500493885",
"4847377595"
]
}
出力
204 No Content
削除しました.
※レスポンスボディは空になります.
400 Bad Request
{
"error": "TEST_ERROR_CODE"
}
TOO_MANY_TARGETS: 削除するアカウント数が多すぎます
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
ACCOUNT_NOT_FOUND: アカウントが存在しませんLIST_NOTFOUND: リストが見つかりません
DELETE /lists/{list_id}
リストを削除します.
入力
- パスパラメータ
list_id:string- リストのID
出力
204 No Content
削除しました.
※レスポンスボディは空になります.
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
LIST_NOTFOUND: リストが見つかりません
GET /lists/{list_id}/notes
リストのノートを取得します.
入力
- パスパラメータ
list_id:snowflake- リストのID
- クエリパラメータ
has_attachment:bool | undefined- デフォルト:
false - ファイルを含む投稿のみを返します
- デフォルト:
no_nsfw:bool | undefined- デフォルト:
false - NSFWフラグの立っているファイルを含む投稿を返さなくなります
- デフォルト:
before_id: ``snowflake| undefined- デフォルト:
undefined- デフォルトでは現在取得できる最新の投稿から20件取得します.
- 指定したIDより古い投稿を返します.指定したIDの投稿は含まれません
- デフォルト:
出力
200 OK
取得に成功しました
[
{
"id": "3893974892",
"content": "hello world!",
"cw_comment": "",
"visibility": "public",
"created_at": "2023-09-27T14:17:29.169Z",
"attachment_files": [
{
"id": "204980348583",
"filename": "hello.png",
"content_type": "image/png",
"url": "https://example.com/images/hello.png",
"blur": "eoig:woi!our@nj/d",
"nsfw": false
}
],
"reactions": [
{
"emoji": "<:alias:11938437>",
"reacted_by": "3085763644"
},
{
"emoji": "🎉",
"reacted_by": "494984128"
}
],
"author": {
"id": "2874987398",
"name": "@[email protected]",
"display_name": "John Doe",
"bio": "I am Test User.",
"avatar": "https://example.com/images/avatar.png",
"header": "https://example.com/images/header.png",
"followed_count": 200,
"following_count": 10
}
}
]
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
LIST_NOTFOUND: リストが見つかりませんNOTHING_LEFT: これ以上古い投稿はありません- 1つでも古い投稿がある場合は投稿を返します
投稿API
Note
投稿は”ノート”, 再投稿は”リノート”と言い換えられている場合があります.
ダイレクト投稿: ノートのうち,公開範囲がダイレクトに指定されているもの
POST /notes
ノートを作成
入力
- body:
application/json
| 項目名 | 型 | 制約/説明 | 数制約 | 例 |
|---|---|---|---|---|
| content | string | 投稿本文添付ファイルが存在する場合は0文字を許容 | 1≤3000[文字] | |
| visibility | string, undefined | 投稿の公開範囲 デフォルト: public とり得る値: public home followers, direct | - | |
| attachment_file_ids | Array<snowflake>, undefined | 投稿の添付ファイルID 1つでもファイルが存在しない場合はエラー終了する | 0≤N≤16[個] | |
| cw_comment | string | CW時の注釈. CW/CWフラグ 参照デフォルトは空文字列 | 0≤N≤256[文字] | |
| send_to | snowflake, undefined | ダイレクト投稿の宛先公開範囲がdirectのときのみ指定可能 |
入力例
{
"content": "hello world!",
"visibility": "public",
"attachment_file_ids": [
"11938472"
],
"cw_comment": ""
}
{
"content": "hello world!",
"visibility": "direct",
"attachment_file_ids": [
"11938472"
],
"cw_comment": "",
"send_to": "8585030584"
}
出力
201 Created
投稿を作成しました.
{
"id": "3893974892",
"content": "hello world!",
"cw_comment": "",
"visibility": "public",
"created_at": "2023-09-27T14:17:29.169Z",
"attachment_files": [
{
"id": "11938472",
"filename": "hello.png",
"content_type": "image/png",
"url": "https://example.com/images/hello.png",
"blur": "eoig:woi!our@nj/d",
"nsfw": false
}
]
}
| 項目名 | 型 | 説明 | 数制約 | 例 |
|---|---|---|---|---|
| id | snowflake | 投稿のID | ||
| content | string | 投稿本文. 添付ファイルが存在する場合は0文字を許容 | 1≤3000[文字] | |
| visibility | string | 投稿の公開範囲. デフォルト: public. とり得る値: public home followers direct | - | |
| attachment_files | Array<object> | 投稿の添付ファイルのオブジェクト (後述) | 0≤N≤16[個] | |
| cw_comment | string | CW時の注釈, 参照 デフォルトは空文字列 | 0≤N≤256[文字] | |
| send_to | Snowflake, undefined | ダイレクト投稿の宛先. 公開範囲がdirectのときのみ指定可能 | ||
| created_at | string | 投稿の送信日時 |
- 添付ファイル (
attchment_files)
| 項目名 | 型 | 説明 | 数制約 | 例 |
|---|---|---|---|---|
| id | snowflake | 投稿のID | ||
| filename | string | ファイル名 | 3≤N≤256[文字] | |
| content_type | string | mimeタイプ | ||
| url | string | 添付ファイルのURL | ||
| blurhash | string, undefined | 添付ファイルが画像であるときのサムネイルの blurhash | ||
| nsfw | boolean | ToDo |
400 Bad Request
{
"error": "TEST_ERROR_CODE"
}
TOO_MANY_ATTACHMENTS: 添付ファイルが制限を超過TOO_MANY_CONTENT`` : CW注釈/投稿本文の文字数制限を超過NO_DESTINATION: 公開範囲がdirectのノートでsend_toが指定されていないINVALID_VISIBILITY: 公開範囲が正しい形式でない
403 Forbidden
{
"error": "TEST_ERROR_CODE"
}
YOU_ARE_SILENCED: サイレンスされている際に公開範囲をpublicに指定した場合
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
ATTACHMENT_NOT_FOUND: 添付したファイルが存在しないACCOUNT_NOT_FOUND: 宛先(send_to)に指定したアカウントが存在しない
GET /notes/{note_id}
特定の投稿を取得します.
入力
- パスパラメータ
note_id:string- 取得したい投稿のID
出力例
200 OK
投稿を取得しました.
{
"id": "3893974892",
"content": "hello world!",
"cw_comment": "",
"visibility": "public",
"created_at": "2023-09-27T14:17:29.169Z",
"attachment_files": [
{
"id": "204980348583",
"filename": "hello.png",
"content_type": "image/png",
"url": "https://example.com/images/hello.png",
"blur": "eoig:woi!our@nj/d",
"nsfw": false
}
],
"reactions": [
{
"emoji": "<:alias:11938437>",
"reacted_by": "3085763644"
},
{
"emoji": "🎉",
"reacted_by": "494984128"
}
],
"author": {
"id": "2874987398",
"name": "@[email protected]",
"display_name": "John Doe",
"bio": "I am Test User.",
"avatar": "https://example.com/images/avatar.png",
"header": "https://example.com/images/header.png",
"followed_count": 200,
"following_count": 10
}
}
| 項目名 | 型 | 説明 | 数制約 | 例 |
|---|---|---|---|---|
| id | snowflake | 投稿のID |
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
NOTE_NOT_FOUND: 指定したIDのノートが存在しない- 指定したIDのノートを(アクセスしたユーザーが)取得できない場合もこのエラーを返す.
POST /notes/{note_id}/renote
指定したIDのノートをリノートします.
入力
- パスパラメータ
note_id:string- 取得したい投稿のID
- body:
application/json
| 項目名 | 型 | 説明 | 数制約 | 例 |
|---|---|---|---|---|
| id | snowflake | 投稿のID | ||
| content | string | 投稿本文. 添付ファイルが存在する場合は0文字を許容 | 1≤3000[文字] | |
| visibility | string | 投稿の公開範囲. デフォルト: public. とり得る値: public home followers direct | - | |
| attachment_files | Array<object> | 投稿の添付ファイルのオブジェクト (後述) | 0≤N≤16[個] | |
| cw_comment | string | CW時の注釈, 参照 デフォルトは空文字列 | 0≤N≤256[文字] | |
| send_to | Snowflake, undefined | ダイレクト投稿の宛先. 公開範囲がdirectのときのみ指定可能 | ||
| created_at | string | 投稿の送信日時 |
attachment_files:
| 項目名 | 型 | 説明 | 数制約 | 例 |
|---|---|---|---|---|
| id | snowflake | 投稿のID | ||
| filename | string | ファイル名 | ToDo | |
| content_type | string | mimeタイプ | ||
| ToDo | ||||
| url | string | 添付ファイルのURL | ||
| blur | string | 添付ファイルが画像であるときのサムネイルの blurhash | ||
| nsfw | boolean | ToDo |
入力例
{
"content": "hello world!",
"visibility": "public",
"attachment_file_ids": [
"11938472"
],
"cw_comment": ""
}
出力
200 OK
リノートしました
{
"id": "3893974892",
"content": "hello world!",
"cw_comment": "",
"visibility": "public",
"renote_id": "4973874850",
"created_at": "2023-09-27T14:17:29.169Z",
"attachment_files": [
{
"id": "11938472",
"filename": "hello.png",
"content_type": "image/png",
"url": "https://example.com/images/hello.png",
"blur": "eoig:woi!our@nj/d",
"nsfw": false
}
]
}
| 項目名 | 型 | 説明 | 数制約 | 例 |
|---|---|---|---|---|
| id | snowflake | 投稿のID | ||
| content | string | 投稿本文. 添付ファイルが存在する場合は0文字を許容 | 1≤3000[文字] | |
| visibility | string | 投稿の公開範囲. デフォルト: public. とり得る値: public home followers direct | - | |
| attachment_files | Array<object> | 投稿の添付ファイルのオブジェクト (後述) | 0≤N≤16[個] | |
| cw_comment | string | CW時の注釈, 参照 デフォルトは空文字列 | 0≤N≤256[文字] | |
| send_to | Snowflake, undefined | ダイレクト投稿の宛先. 公開範囲がdirectのときのみ指定可能 | ||
| created_at | string | 投稿の送信日時 |
| 項目名 | 型 | 説明 | 数制約 | 例 |
|---|---|---|---|---|
| id | snowflake | 投稿のID | ||
| filename | string | ファイル名 | ToDo | |
| content_type | string | mimeタイプ | ||
| ToDo | ||||
| url | string | 添付ファイルのURL | ||
| blur | string | 添付ファイルが画像であるときのサムネイルの blurhash | ||
| nsfw | boolean | ToDo |
400 Bad Request
{
"error": "TEST_ERROR_CODE"
}
TOO_MANY_CHAR_LENGTH: CW注釈/投稿本文の文字数制限を超過INVALID_VISIBILITY: 公開範囲が正しい形式でないNO_DESTINATION: 公開範囲がdirectのノートでsend_toが指定されていない
403 Forbidden
{
"error": "TEST_ERROR_CODE"
}
YOU_ARE_SILENCED: サイレンスされている際に公開範囲をpublicに指定した
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
ATTACHMENT_NOT_FOUND: 添付したファイルが存在しないNOTE_NOT_FOUND: ノートが存在しない
POST /notes/{note_id}/reply
指定したIDのノートに返信します.
入力
- パスパラメータ
note_id:string- 取得したい投稿のID
- body:
application/json
| 項目名 | 型 | 説明 | 数制約 | 例 |
|---|---|---|---|---|
| id | snowflake | 投稿のID | ||
| content | string | 投稿本文. 添付ファイルが存在する場合は0文字を許容 | 1≤3000[文字] | |
| visibility | string | 投稿の公開範囲. デフォルト: public. とり得る値: public home followers direct | - | |
| attachment_files | Array<object> | 投稿の添付ファイルのオブジェクト (後述) | 0≤N≤16[個] | |
| cw_comment | string | CW時の注釈, 参照 デフォルトは空文字列 | 0≤N≤256[文字] | |
| created_at | string | 投稿の送信日時 |
attachment_files:
| 項目名 | 型 | 説明 | 数制約 | 例 |
|---|---|---|---|---|
| id | snowflake | 投稿のID | ||
| filename | string | ファイル名 | ToDo | |
| content_type | string | mimeタイプ | ||
| ToDo | ||||
| url | string | 添付ファイルのURL | ||
| blur | string | 添付ファイルが画像であるときのサムネイルの blurhash | ||
| nsfw | boolean | ToDo |
入力例
{
"content": "hello world!",
"visibility": "public",
"attachment_file_ids": [
"11938472"
],
"cw_comment": ""
}
出力
200 OK
投稿を作成しました.
{
"id": "3893974892",
"content": "hello world!",
"cw_comment": "",
"reply_to": "2948933000",
"visibility": "public",
"created_at": "2023-09-27T14:17:29.169Z",
"attachment_files": [
{
"id": "11938472",
"filename": "hello.png",
"content_type": "image/png",
"url": "https://example.com/images/hello.png",
"blur": "eoig:woi!our@nj/d",
"nsfw": false
}
]
}
| 項目名 | 型 | 説明 | 数制約 | 例 |
|---|---|---|---|---|
| id | snowflake | 投稿のID | ||
| content | string | 投稿本文. 添付ファイルが存在する場合は0文字を許容 | 1≤3000[文字] | |
| visibility | string | 投稿の公開範囲. デフォルト: public. とり得る値: public home followers direct | - | |
| attachment_files | Array<object> | 投稿の添付ファイルのオブジェクト (後述) | 0≤N≤16[個] | |
| cw_comment | string | CW時の注釈, 参照 デフォルトは空文字列 | 0≤N≤256[文字] | |
| send_to | Snowflake, undefined | ダイレクト投稿の宛先. 公開範囲がdirectのときのみ指定可能 | ||
| created_at | string | 投稿の送信日時 |
| 項目名 | 型 | 説明 | 数制約 | 例 |
|---|---|---|---|---|
| id | snowflake | 投稿のID | ||
| filename | string | ファイル名 | ToDo | |
| content_type | string | mimeタイプ | ||
| ToDo | ||||
| url | string | 添付ファイルのURL | ||
| blur | string | 添付ファイルが画像であるときのサムネイルの blurhash | ||
| nsfw | boolean | ToDo |
400 Bad Request
{
"error": "TEST_ERROR_CODE"
}
TOO_MANY_CHAR_LENGTH: CW注釈/投稿本文の文字数制限を超過INVALID_VISIBILITY: 公開範囲が正しい形式でない
403 Forbidden
{
"error": "TEST_ERROR_CODE"
}
YOU_ARE_SILENCED: サイレンスされている際に公開範囲をpublicに指定したYOU_ARE_BLOCKED: 返信先ユーザーにブロックされている
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
ATTACHMENT_NOT_FOUND: 添付したファイルが存在しないNOTE_NOT_FOUND: ノートが存在しない
DELETE /notes/{note_id}
投稿を削除します.
NOTICE: 自分以外のノートを削除する場合はモデレータ以上の資格情報が必要です.
入力
- パスパラメータ
note_id:string- 削除するノートのID
出力
204 No Content
削除しました.
※ レスポンスボディは空になります
403 Forbidden
{
"error": "TEST_ERROR_CODE"
}
NO_PERMISSION: ノートの投稿者でないため削除できない
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
NOTE_NOT_FOUND: 削除するノートが存在しない
POST /notes/{note_id}/reaction
指定したノートにリアクションします.
入力
- パスパラメータ
note_id:string- リアクションしたい投稿のID
body: application/json
| 項目名 | 型 | 説明 | 例 |
|---|---|---|---|
| emoji | string | 絵文字 | |
| 表記法は カスタム絵文字 を参照 | <:alias:11938437> | ||
🎉 |
入力例
{
"emoji": "🎉"
}
{
"emoji": "<:awesome:489395643749>"
}
出力
200 OK
リアクションしました.
{
"id": "3893974892",
"content": "hello world!",
"cw_comment": "",
"visibility": "public",
"created_at": "2023-09-27T14:17:29.169Z",
"attachment_files": [
{
"id": "204980348583",
"filename": "hello.png",
"content_type": "image/png",
"url": "https://example.com/images/hello.png",
"blur": "eoig:woi!our@nj/d",
"nsfw": false
}
],
"reactions": [
{
"emoji": "<:alias:11938437>",
"reacted_by": "3085763644"
},
{
"emoji": "🎉",
"reacted_by": "494984128"
}
],
"author": {
"id": "2874987398",
"name": "@[email protected]",
"display_name": "John Doe",
"bio": "I am Test User.",
"avatar": "https://example.com/images/avatar.png",
"header": "https://example.com/images/header.png",
"followed_count": 200,
"following_count": 10
}
}
| 項目名 | 型 | 説明 | 数制約 | 例 |
|---|---|---|---|---|
| id | snowflake | 投稿のID | ||
| content | string | 投稿本文. 添付ファイルが存在する場合は0文字を許容 | 1≤3000[文字] | |
| visibility | string | 投稿の公開範囲. デフォルト: public. とり得る値: public home followers direct | - | |
| attachment_files | Array<object> | 投稿の添付ファイルのオブジェクト (後述) | 0≤N≤16[個] | |
| cw_comment | string | CW時の注釈, 参照 デフォルトは空文字列 | 0≤N≤256[文字] | |
| send_to | Snowflake, undefined | ダイレクト投稿の宛先. 公開範囲がdirectのときのみ指定可能 | ||
| created_at | string | 投稿の送信日時 |
attachment_files:
| 項目名 | 型 | 説明 | 数制約 | 例 |
|---|---|---|---|---|
| id | snowflake | 投稿のID | ||
| filename | string | ファイル名 | ToDo | |
| content_type | string | mimeタイプ | ||
| ToDo | ||||
| url | string | 添付ファイルのURL | ||
| blur | string | 添付ファイルが画像であるときのサムネイルの blurhash | ||
| nsfw | boolean | ToDo |
reactions:
| 項目名 | 型 | 説明 | 例 |
|---|---|---|---|
| emoji | string | 絵文字. 表記法は 絵文字の扱い を参照 | 🎉, <:alias:11938437> |
| reacted_by | snowflake | リアクションしたアカウントID | 48499372 |
400 Bad Request
{
"error": "TEST_ERROR_CODE"
}
ALREADY_REACTED: すでにリアクション済みEMOJI_NOT_FOUND: 指定した絵文字が存在しない(カスタム絵文字のみ)/複数指定している(Unicode絵文字
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
NOTE_NOT_FOUND: リアクションするノートが存在しない
DELETE /notes/{note_id}/reaction
指定したノートにつけたリアクションを解除します
入力
- パスパラメータ
note_id:string- リアクションしたい投稿のID
出力
204 No Content
削除しました.
※ レスポンスボディは空になります.
400 Forbidden
{
"error": "TEST_ERROR_CODE"
}
NOT_REACTED: リアクションしていない
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
NOTE_NOT_FOUND: ノートが存在しない
POST /notes/{note_id}/bookmark
指定した投稿をブックマークします
入力
- パスパラメータ
note_id:string- リアクションしたい投稿のID
出力
200 OK
ブックマークしました
{
"id": "3893974892",
"content": "hello world!",
"cw_comment": "",
"visibility": "public",
"created_at": "2023-09-27T14:17:29.169Z",
"attachment_files": [
{
"id": "204980348583",
"filename": "hello.png",
"content_type": "image/png",
"url": "https://example.com/images/hello.png",
"blur": "eoig:woi!our@nj/d",
"nsfw": false
}
],
"reactions": [
{
"emoji": "<:alias:11938437>",
"reacted_by": "3085763644"
},
{
"emoji": "🎉",
"reacted_by": "494984128"
}
],
"author": {
"id": "2874987398",
"name": "@[email protected]",
"display_name": "John Doe",
"bio": "I am Test User.",
"avatar": "https://example.com/images/avatar.png",
"header": "https://example.com/images/header.png",
"followed_count": 200,
"following_count": 10
}
}
| 項目名 | 型 | 説明 | 数制約 | 例 |
|---|---|---|---|---|
| id | snowflake | 投稿のID | ||
| content | string | 投稿本文. 添付ファイルが存在する場合は0文字を許容 | 1≤3000[文字] | |
| visibility | string | 投稿の公開範囲. デフォルト: public. とり得る値: public home followers direct | - | |
| attachment_files | Array<object> | 投稿の添付ファイルのオブジェクト (後述) | 0≤N≤16[個] | |
| cw_comment | string | CW時の注釈, 参照 デフォルトは空文字列 | 0≤N≤256[文字] | |
| send_to | Snowflake, undefined | ダイレクト投稿の宛先. 公開範囲がdirectのときのみ指定可能 | ||
| created_at | string | 投稿の送信日時 |
attachment_files:
| 項目名 | 型 | 説明 | 数制約 | 例 |
|---|---|---|---|---|
| id | snowflake | 投稿のID | ||
| filename | string | ファイル名 | ToDo | |
| content_type | string | mimeタイプ | ||
| ToDo | ||||
| url | string | 添付ファイルのURL | ||
| blur | string | 添付ファイルが画像であるときのサムネイルの blurhash | ||
| nsfw | boolean | ToDo |
reactions:
| 項目名 | 型 | 説明 | 例 |
|---|---|---|---|
| emoji | string | 絵文字 | |
| 表記法は 絵文字の扱い を参照 | <:alias:11938437 | ||
🎉 | |||
| reacted_by | snowflake | リアクションしたアカウントID | 48499372 |
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
NOTE_NOT_FOUND: ノートが存在しない
DELETE /notes/{note_id}/bookmark
指定した投稿をブックマーク解除します
入力
- パスパラメータ
note_id:string- ブックマーク解除したい投稿のID
出力
204 No Content
ブックマーク解除しました
※レスポンスボディは空になります.
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
NOTE_NOT_FOUND: ノートが存在しない
| 項目名 | 型 | 説明 | 数制約 | 例 |
|---|---|---|---|---|
| id | snowflake | 投稿のID | ||
| content | string | 投稿本文. 添付ファイルが存在する場合は0文字を許容 | 1≤3000[文字] | |
| visibility | string | 投稿の公開範囲. デフォルト: public. とり得る値: public home followers direct | - | |
| attachment_files | Array<object> | 投稿の添付ファイルのオブジェクト (後述) | 0≤N≤16[個] | |
| cw_comment | string | CW時の注釈, 参照 デフォルトは空文字列 | 0≤N≤256[文字] | |
| send_to | Snowflake, undefined | ダイレクト投稿の宛先. 公開範囲がdirectのときのみ指定可能 | ||
| created_at | string | 投稿の送信日時 |
| 項目名 | 型 | 説明 | 数制約 | 例 |
|---|---|---|---|---|
| id | snowflake | 投稿のID | ||
| filename | string | ファイル名 | ToDo | |
| content_type | string | mimeタイプ | ||
| ToDo | ||||
| url | string | 添付ファイルのURL | ||
| blur | string | 添付ファイルが画像であるときのサムネイルの blurhash | ||
| nsfw | boolean | ToDo |
| 項目名 | 型 | 説明 | 例 |
|---|---|---|---|
| emoji | string | 絵文字 | |
| 表記法は 絵文字の扱い を参照 | <:alias:11938437 | ||
🎉 | |||
| reacted_by | snowflake | リアクションしたアカウントID | 48499372 |
通知API
お知らせ(Announce)の種類
| 種類コード | 説明 |
|---|---|
info | 一般的なお知らせ |
warn | ユーザー全体への警告 |
通知の種類
| 種類コード | 通知が発生する条件 |
|---|---|
followed | フォローされたとき |
followRequested | フォローをリクエストされたとき |
followAccepted | 自分が行ったフォローリクエストが承認されたとき |
mentioned | メンションされたとき |
renoted | リノートされたとき |
reacted | リアクションされたとき |
followed - フォローされた
取り得るactorの種類:
account
{
"id": "20923084093774",
"type": "followed",
"actor": {
"type": "account",
"account": {
"id": "209384",
"name": "@[email protected]",
"nickname": "John Doe🌤",
"avatar": "https://cdn.example.com/johndoe/avatar"
}
},
"createdAt": "2024-08-01T00:00:00.000Z"
}
followRequested - フォローをリクエストされた
取り得るactorの種類:
account
{
"id": "20923084093774",
"type": "followRequested",
"actor": {
"type": "account",
"account": {
"id": "209384",
"name": "@[email protected]",
"nickname": "John Doe🌤",
"avatar": "https://cdn.example.com/johndoe/avatar"
}
},
"createdAt": "2024-08-01T00:00:00.000Z"
}
followAccepted - (自分が行った)フォローリクエストが承認されたとき
取り得るactorの種類:
account
{
"id": "20923084093774",
"type": "followAccepted",
"actor": {
"type": "account",
"account": {
"id": "209384",
"name": "@[email protected]",
"nickname": "John Doe🌤",
"avatar": "https://cdn.example.com/johndoe/avatar"
}
},
"createdAt": "2024-08-01T00:00:00.000Z"
}
mentioned - メンションされた
取り得るactorの種類:
account
{
"id": "20923084093774",
"type": "mentioned",
// 言及元ノートID
"noteId": "29847304533478",
"actor": {
"type": "account",
"account": {
"id": "209384",
"name": "@[email protected]",
"nickname": "John Doe🌤",
"avatar": "https://cdn.example.com/johndoe/avatar"
}
},
"createdAt": "2024-08-01T00:00:00.000Z"
}
renoted - リノートされた
取り得るactorの種類:
account
{
"id": "20923084093774",
"type": "renoted",
// リノートされたノートID
"noteId": "1032809844545437574",
// 投稿本文(CW指定の場合は空になる)
"content": "",
"actor": {
"type": "account",
"account": {
"id": "209384",
"name": "@[email protected]",
"nickname": "John Doe🌤",
"avatar": "https://cdn.example.com/johndoe/avatar"
}
},
"createdAt": "2024-08-01T00:00:00.000Z"
}
reacted - リアクションされた
取り得るactorの種類:
account
{
"id": "20923084093774",
"type": "reacted",
// リアクションされたノートのID
"noteId": "3094320840856",
// リアクションの内容
"content": "🐭",
"actor": {
"type": "account",
"account": {
"id": "209384",
"name": "@[email protected]",
"nickname": "John Doe🌤",
"avatar": "https://cdn.example.com/johndoe/avatar"
}
},
"createdAt": "2024-08-01T00:00:00.000Z"
}
actorについて
通知を発生させた主体の種類
account: (内部外部問わず) (ユーザー)アカウント- botの場合も含む
system: システム通知- モデレーター等からの警告通知など
account:
{
"type": "account",
"account": {
// アカウントID
"id": "209384",
// アカウント名
"name": "@[email protected]",
// アカウントニックネーム
"nickname": "John Doe🌤",
// アバター画像のURL
"avatar": "https://cdn.example.com/johndoe/avatar"
}
}
system:
{
"type": "system",
"system": {
// ToDo
}
}
APIエンドポイント一覧
GET /notifications
届いているすべての通知を取得します。
入力
パスパラメータ:
limit: number, 返す通知の最大数- デフォルト: 30 / 最大: 50
after: string(date), この日以降の通知を返します- デフォルト: “1970-01-01”
include_read: boolean, trueの時は既読の通知も返します- デフォルト: false
出力
- body:
application/json
{
// インスタンス全体へのお知らせ
"announcements": [
{
"id": "308205359",
"title": "Service maintenance",
"description": "scheduled: 2024 Sep. 10 00:00 ~ 01:00(UTC)\nduring this period, all services will be unavailable.",
// お知らせの種類
"type": "info",
"createdAt": "2024-08-01T00:00:00.000Z",
// optional
"updatedAt": "2024-08-01T10:00:00.000Z",
// 既読か
"unread": false
}
],
// 自分宛ての通知
"notifications": [
{
"id": "20923084093774",
"type": "followed",
"actor": {
"type": "account",
"account": {
"id": "209384",
"name": "@[email protected]",
"nickname": "John Doe🌤",
"avatar": "https://cdn.example.com/johndoe/avatar"
}
},
"createdAt": "2024-08-01T00:00:00.000Z"
}
]
}
POST /notifications/{id}/read
通知を既読にします
入力
-
パスパラメータ
id: string- 既読にする通知ID
-
body:
application/json
{}
出力
204 No Content 通知を既読にしました。
検索API
クエリ
ベースは Twitter v2 のクエリ.
オペレータ
| Operator | 意味 | 例 | 単独使用可能 |
|---|---|---|---|
keyword | 特定のキーワードを含む | 松江 AND どこ | yes |
"" | 文字列の完全一致 | "造幣局 桜" | yes |
from: | 特定ユーザーによるノート | from:@[email protected] or from:@[email protected] | yes |
is:renote | リノート | hello world -is:renote | no |
is:quote | 引用リノート | #筑後川花火大会 is:quote | no |
has:link | 本文にリンクを含むノート | nowplaying has:link | no |
has:media | 添付ファイルを含むノート | #徳川家康 has:media | no |
演算子
| 演算子 | 説明 |
|---|---|
AND | スペースを挟んで連続する演算子はAND 論理演算となり、両方の条件を満たしたノートが返されます |
OR | OR を挟んで連続する演算子はOR論理演算となり、どちらかの条件を満たしたノートが返されます. |
- | キーワードの前にハイフンをつけると、そのキーワードを論理否定することができます. |
() | カッコでくくると演算子をグループ化できます. AND>ORの順で適用されます. |
GET /search/notes
投稿を検索します. 返す最大件数は100件です
入力
- クエリパラメータ
query:string- 検索クエリ
- 上記の内容を受け取ります
出力
200 OK
検索に成功
出力の内容はタイムラインと同一である.
[
{
"id": "3893974892",
"content": "hello world!",
"cw_comment": "",
"visibility": "public",
"created_at": "2023-09-27T14:17:29.169Z",
"attachment_files": [
{
"id": "204980348583",
"filename": "hello.png",
"content_type": "image/png",
"url": "https://example.com/images/hello.png",
"blur": "eoig:woi!our@nj/d",
"nsfw": false
}
],
"reactions": [
{
"emoji": "<:alias:11938437>",
"reacted_by": "3085763644"
},
{
"emoji": "🎉",
"reacted_by": "494984128"
}
],
"author": {
"id": "2874987398",
"name": "@[email protected]",
"display_name": "John Doe",
"bio": "I am Test User.",
"avatar": "https://example.com/images/avatar.png",
"header": "https://example.com/images/header.png",
"followed_count": 200,
"following_count": 10
}
}
]
400 Bad Request
{
"error": "TEST_ERROR_CODE"
}
INVALID_QUERY: クエリが正しくありません
タイムラインAPI
Warning
取得した投稿は時系列順にソートされた状態で返されます.
一度に取得できる件数は最大20件です.
GET /timeline/{timeline_type}
タイムラインを取得します.
入力
- パスパラメータ
timeline_type:string,undefined- とり得る値:
home/global - デフォルト:
home
- とり得る値:
- クエリパラメータ
has_attachment:bool | undefined- デフォルト:
false - ファイルを含む投稿のみを返します
- デフォルト:
no_nsfw:bool | undefined- デフォルト:
false - NSFWフラグの立っているファイルを含む投稿を返さなくなります
- デフォルト:
before_id:snowflake| undefined- デフォルト:
undefinedデフォルトでは現在取得できる最新の投稿から20件取得します.
- 指定したIDより古い投稿を返します.指定したIDの投稿は含まれません
- デフォルト:
出力
200 OK
タイムラインを取得します.
[
{
"id": "3893974892",
"content": "hello world!",
"cw_comment": "",
"visibility": "public",
"created_at": "2023-09-27T14:17:29.169Z",
"attachment_files": [
{
"id": "204980348583",
"filename": "hello.png",
"content_type": "image/png",
"url": "https://example.com/images/hello.png",
"blur": "eoig:woi!our@nj/d",
"nsfw": false
}
],
"reactions": [
{
"emoji": "<:alias:11938437>",
"reacted_by": "3085763644"
},
{
"emoji": "🎉",
"reacted_by": "494984128"
}
],
"author": {
"id": "2874987398",
"name": "@[email protected]",
"display_name": "John Doe",
"bio": "I am Test User.",
"avatar": "https://example.com/images/avatar.png",
"header": "https://example.com/images/header.png",
"followed_count": 200,
"following_count": 10
}
}
]
400 Bad Request
{
"error": "TEST_ERROR_CODE"
}
INVALID_TIMELINE_TYPE: 指定したタイムラインタイプは存在しません
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
NOTHING_LEFT: これ以上古い投稿はありません- 1つでも古い投稿がある場合は投稿を返します
GET /timeline/accounts/{account_id|account_name}
特定のユーザーの投稿を取得します.
入力
- パスパラメータ
account_id,account_name:snowflake|string- アカウント名かアカウントのIDを指定できます.
- クエリパラメータ
has_attachment:bool | undefined- デフォルト:
false - ファイルを含む投稿のみを返します
- デフォルト:
no_nsfw:bool | undefined- デフォルト:
false - NSFWフラグの立っているファイルを1つでも含む投稿を返さなくなります
- デフォルト:
before_id:snowflake| undefined`- デフォルト:
undefined- デフォルトでは現在取得できる最新の投稿から20件取得します.
- 指定したIDより古い投稿を返します.指定したIDの投稿は含まれません
- デフォルト:
出力
200 OK
タイムラインを取得します.
[
{
"id": "3893974892",
"content": "hello world!",
"cw_comment": "",
"visibility": "public",
"created_at": "2023-09-27T14:17:29.169Z",
"attachment_files": [
{
"id": "204980348583",
"filename": "hello.png",
"content_type": "image/png",
"url": "https://example.com/images/hello.png",
"blur": "eoig:woi!our@nj/d",
"nsfw": false
}
],
"reactions": [
{
"emoji": "<:alias:11938437>",
"reacted_by": "3085763644"
},
{
"emoji": "🎉",
"reacted_by": "494984128"
}
],
"author": {
"id": "2874987398",
"name": "@[email protected]",
"display_name": "John Doe",
"bio": "I am Test User.",
"avatar": "https://example.com/images/avatar.png",
"header": "https://example.com/images/header.png",
"followed_count": 200,
"following_count": 10
}
}
]
400 Bad Request
{
"error": "TEST_ERROR_CODE"
}
INVALID_TIMELINE_TYPE: 指定したタイムラインタイプは存在しません
403 Forbidden
{
"error": "TEST_ERROR_CODE"
}
YOU_ARE_BLOCKED: 指定したアカウントにブロックされています.
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
NOTHING_LEFT: これ以上古い投稿はありません- 1つでも古い投稿がある場合は投稿を返します
ACCOUNT_NOT_FOUND: 指定したアカウントが見つかりませんでした
GET /timeline/conversations
ダイレクト投稿を受け取った、もしくは送った相手を取得します.
入力
なし
出力
200 OK
[
{
"account": {
"accountId": "20983985",
"accountName": "@[email protected]",
"nickname": "John Doe",
"avatar": "https://example.com/images/avatar.png"
},
"updatedAt": "2023-09-10T00:00:00.000Z"
}
]
GET /timeline/conversations/{account_id}
指定したアカウントとのダイレクト投稿を取得します.
入力
- パスパラメータ
account_id:snowflake- ダイレクト投稿を取得したいアカウントID.
- クエリパラメータ
before_id:snowflake|undefined- デフォルト:
undefined - 指定したIDより古い投稿を返します.指定したIDの投稿は含まれません
- デフォルト:
出力
200 OK
タイムラインを取得します.
[
{
"id": "3893974892",
"content": "hello world!",
"cw_comment": "",
"visibility": "public",
"created_at": "2023-09-27T14:17:29.169Z",
"attachment_files": [
{
"id": "204980348583",
"filename": "hello.png",
"content_type": "image/png",
"url": "https://example.com/images/hello.png",
"blur": "eoig:woi!our@nj/d",
"nsfw": false
}
],
"reactions": [
{
"emoji": "<:alias:11938437>",
"reacted_by": "3085763644"
},
{
"emoji": "🎉",
"reacted_by": "494984128"
}
],
"author": {
"id": "2874987398",
"name": "@[email protected]",
"display_name": "John Doe",
"bio": "I am Test User.",
"avatar": "https://example.com/images/avatar.png",
"header": "https://example.com/images/header.png",
"followed_count": 200,
"following_count": 10
}
}
]
404 Not Found
{
"error": "TEST_ERROR_CODE"
}
NOTHING_LEFT: これ以上古い投稿はありません- 1つでも古い投稿がある場合は投稿を返します
ACCOUNT_NOT_FOUND: 指定したアカウントが見つかりませんでした
内部フロー・モジュール間通信

Mermaid code
sequenceDiagram
autonumber
actor ユーザー
ユーザー ->> NoteModule: 投稿
opt ダイレクト投稿の場合
NoteModule ->> AccountModule: 送信先アカウントの存在チェック
end
NoteModule ->> AccountModule: 投稿者情報取得
opt リノートの場合
NoteModule ->> NoteModule: 引用 or リノート先情報取得
end
NoteModule ->> ユーザー: 投稿完了
NoteModule ->> SearchModule: 検索インデックス投入
NoteModule ->> TimelineModule: 投稿作成イベント発火
TimelineModule ->> AccountModule: フォロー関係情報取得
TimelineModule ->> NotificationModule: 通知イベント発火
TimelineModule ->> ActivityPubModule: 投稿作成イベント発火
モジュールの概要
Pulsateのバックエンドでは“モジュール“と呼ばれる単位にプログラムを大まかに分け、機能や責任範囲を分離しています.
現在時点でPulsateのバックエンドには以下のモジュールが存在しています.
Note Module
- ノート(投稿)の作成を行う.
- ノートの削除を行う.
- ノート情報を返す.
- 投稿に紐づいたメディアの管理をする
Account Module
- アカウント情報の管理を行う.
- アカウントを凍結/解除する.
- アカウントをサイレンスする.
- ログイン時に発行されるトークンの生成/検証を行う.
- アカウントのフォロー/フォロー解除を行う.
Timeline Module
- (ホーム/リスト)タイムラインを構築する.
- 特定のアカウントが見ることのできる投稿をフォロー関係と公開範囲から決定する.
Drive Module
- メディアファイルの投稿を受け付ける
- メディアファイルを適切に圧縮する
- メディアファイル情報を返す
- メディアファイルの保存を行う
モジュールの内部構造
モジュールの内部は以下のようになっています
.
├── adaptor/
│ ├── controller コントローラー
│ ├── presenter APIのエラー定義
│ ├── repository Repositoryの実装
│ └── validator APIスキーマ定義ファイル
├── model/
│ ├── errors.ts モジュール内で使うエラーの定義
│ ├── <moduleName>.ts モジュールで使うモデル
│ └── repository.ts RepositoryのInterface定義
├── service 実際の処理を行うApplication Serviceの実装
├── mod.ts APIのハンドラ (エントリーポイント)
└── router.ts APIのルーティング定義
処理の大まかな流れは以下の通りです
- mod.ts がトップレベル(pkg/main.ts)から呼び出され、APIのリクエストを受ける
- router.tsの定義に従ってmod.tsで定義されたハンドラーにリクエストの内容が渡される
- adaptor/validatorで定義されたスキーマに沿ったリクエストかを判定する
- ハンドラーからadaptor/controllerを呼び出す
- controllerからserviceを呼び出す
- serviceが処理を行い、model/repository.tsで定義されたInterfaceを呼び出し、データの取得や保存を行う
- Intermoduleパッケージを利用して他のモジュールの機能を呼び出して使うこともできる
Intermoduleパッケージ (モジュール間通信)
モジュールが他のモジュールのServiceをimportすることは禁止されています. そのため,
他のモジュールの機能を利用したい場合には Intermodule パッケージを利用します.
Intermodule
パッケージはそれぞれのモジュールごとに定義されるインタフェースで、モジュールが他のモジュールから参照される機能を公開するようになっています.
Intermodule
の内部ではそのモジュールのServiceのメソッドを呼び出すよう実装されています.
認可制御
このドキュメントでは,Pulsate API(v0) における認可制御について記述する.
用語
Actor: アクションを実行する主体.Accountが該当する.Action: リソースに対して行う何らかの操作のこと.read: 読み取りwrite: 書き込み,更新(リソースが更新可能な場合),リソースの削除
Resource:Actionの対象となるもの.Target: 操作が許可されたときに使用する,操作を行うまたは操作後のリソースを保存するもの.Policy:ActorがActionを実行するための条件.
全体像
- Pulsate API での認可制御は Policy を接尾辞にもつクラス群によって定義される.
- この Policy クラス群の各クラスは
withCheckstatic メソッドを持ち,actor, action, resource, targetの3値,および関数fnを要求する.withCheckメソッドはジェネリクス<Target,Res>を受け取る.Targetは target の型,Resはfnの返値である.
interface PolicyArgs<Actor, Action, Resource> {
actor: Actor;
action: Action;
resource: Resource;
}
type NotePolicyArgs = PolicyArgs<Account, NotePolicyAction, Note>;
class NotePolicy {
static withCheck<Target, Res>(
target: Target,
): (
args: NotePolicyArgs,
fn: (target: Target) => Promise<Result.Result<Error, Res>>,
) => Promise<Result.Result<Error, Res>> {
return async (
args: AccountPolicyArgs,
fn: (target: Target) => Promise<Result.Result<Error, Res>>,
): Promise<Result.Result<Error, Res>> => {
if (!args.actor) return Result.err(Error("ログインしないと使えません"));
// 条件を満たしたときだけ実行する
return await fn(target);
};
}
}
PolicyAction
Action は識別子 PolicyAction を用いて識別する.
- PolicyAction は3つの要素からなる文字列である.
- 要素は以下の通り.
- 1: ポリシー名
- 2: モデル名 (例:
account,note)- モデルではないものも含む (例:
follow). - タイムラインの場合はタイムライン種別名とする (例:
home,conversation)
- モデルではないものも含む (例:
- 3: アクション名
- これらは以下で示す型で表現できる形式に従って結合される.
type PolicyAction = `${PolicyName}.${ModelName}:${ActionName}`;
withCheckメソッドは,PolicyAction のポリシー名が自分が管理するものでない場合,エラーを返す.