技術資料Technical docs
convs の仕組み・API 仕様・セキュリティなどをまとめています。How convs works: architecture, API reference, and security notes.
技術資料Technical docs
convs でできることWhat convs can do
- 投稿の登録と一覧取得:投稿者名・本文・投稿日時(JST)を保存し新着順で取得。名前は投稿時点のスナップショット。
- プロジェクト・掲示板ごとのデータ分離:
プロジェクト識別子 × 掲示板識別子の単位で完全に分離。1プロジェクトに掲示板を何枚でも。 - アンカー返信(>>N):本文に
>>1と書くと参照リンクになり、参照先には「レス(N件)」ラベルが付きます。スレッド化しないフラットな掲示板のままです。 - 投稿の AI 翻訳:翻訳ボタンで日本語・英語へ翻訳。翻訳済みの投稿は最初から訳文を表示し、「原文を表示」でいつでも戻せます(原文は常に保持・結果はキャッシュ)。
- 動物ネーム:ニックネームを省略すると「流浪のぶたさん」のような名前が自動で付きます(ピクセルアバターと同じ動物・同じ人はいつも同じ名前)。
- 本文中の https リンク:
https://の URL は確認ダイアログ付きのリンクになります(http://は対象外)。 - 通報:各投稿の ⚠ から運営に連絡できます(理由 + 任意の詳細・匿名可。自動では非表示にせず運営が判断)。
- 新着の自動反映:タブが可視で掲示板が画面内にあるときだけ約15秒間隔で新着を取得し、変化があったときだけ静かに再描画します。
- 入力値チェックと安全な表示:長さ制限・制御文字除去・空投稿の拒否。本文はプレーンテキスト(XSS を持ち込まない)。
- 連続投稿への対策:送信元(IP)単位のレート制限+同一内容の連投防止(詳細は「API 仕様」のレート制限一覧)。
- Post and list messages: stores author name, text, and timestamp (JST), returned newest first. Names are a snapshot taken at post time.
- Data isolation per project and board: fully separated by
project ID × board ID. One project can have any number of boards. - Anchor replies (>>N): writing
>>1becomes a reference link, and referenced posts get a reply-count label. Boards stay flat — no threading. - AI translation of posts: translate posts into Japanese or English. Posts with a cached translation show it by default, with a "Show original" toggle (the original is always kept).
- Animal names: leave the nickname blank and you get a name like "Wandering Pig" — the same animal as your pixel avatar, and always the same name for the same person.
- https links in posts:
https://URLs become links with a leave-site confirmation (http://is not linkified). - Reporting: the ⚠ on each post notifies the operators (a reason plus optional detail, anonymous OK; nothing is hidden automatically — operators decide).
- Automatic updates: new posts are fetched about every 15 seconds — only while the tab is visible and the board is on screen — and re-rendered quietly only when something changed.
- Input validation and safe rendering: length limits, control-character stripping, empty-post rejection. Text is plain text only (no XSS brought in).
- Flood protection: per-source (IP) rate limiting plus duplicate-post prevention (see the rate-limit table under "API reference").
設置方法とオプションEmbedding & options
掲示板を置きたいところに <div> を1つ、ページのどこかに embed.js を1つ。JavaScript を書く必要はありません。
<div data-convs="my-app/lounge"></div>
<script src="https://convs.net/embed.js" async></script>
属性一覧
| 属性 | 値 | 説明 |
|---|---|---|
data-convs | 必須 | プロジェクト識別子/掲示板識別子。ページや対象物ごとに何枚でも設置できます。 |
data-avatar | pixel | 投稿者ごとに動物モチーフのピクセルアバター(16×16・全10種×配色)を表示。同じ人は必ず同じキャラクター。名無し投稿の「動物ネーム」もこのアバターと連動します。 |
data-convs-lang | en | フォームのラベル・ボタン・状態表示などウィジェットの UI を英語に(省略時は日本語)。投稿の AI 翻訳の翻訳先もこの言語になります。 |
data-convs-endpoint | URL | ローカル開発・セルフホスト時の接続先上書き。空文字にするとバックエンド無しのモックで動きます(表示確認用)。 |
SPA などで後から要素を足した場合は window.ConvsEmbed.scan() を呼ぶと再スキャンされます。フッターの「Powered by convs」リンクは無料版では常に表示されます。
Put one <div> where the board should appear and embed.js anywhere on the page — no JavaScript to write.
<div data-convs="my-app/lounge"></div>
<script src="https://convs.net/embed.js" async></script>
Attributes
| Attribute | Value | Description |
|---|---|---|
data-convs | required | project ID/board ID. Place as many boards as you like, per page or per item. |
data-avatar | pixel | Shows an animal-motif pixel avatar (16×16, 10 species × color variations) per poster; the same person always gets the same character. Anonymous "animal names" match this avatar. |
data-convs-lang | en | Switches the widget UI — labels, buttons, status text — to English (Japanese by default). AI translation also targets this language. |
data-convs-endpoint | URL | Endpoint override for local development / self-hosting. An empty string runs an in-memory mock with no backend (for layout checks). |
If you add elements later (e.g. in an SPA), call window.ConvsEmbed.scan() to rescan. The "Powered by convs" footer link always shows on the free tier.
投稿まわりの仕様How posting works
- ニックネームと動物ネーム:ニックネーム欄は普段は折りたたまれています(「ニックネームを変えたいときはこちら」で展開)。省略して投稿すると、アバターと同じ動物の「流浪のぶたさん」のような名前が自動で付きます(同じ人はいつも同じ名前)。アバター無効の掲示板では「名無しさん」です。
- 文字数:本文は既定 500 字・名前は 24 字(掲示板ごとに変更可)。
- アンカー返信:本文に
>>1と書くと参照になります(1投稿あたり最大8件・ボタンでも手書きでも同じ)。参照リンクはクリックでジャンプ・ホバーでプレビュー。 - 本文中のリンク:
https://で始まる URL だけがリンクになります。クリックすると「外部サイトへ移動します」の確認が出て、OK のときだけ新しいタブで開きます。http://はリンクになりません。 - AI 翻訳:表示言語と違う言語らしい投稿に「翻訳」ボタンが出ます。翻訳済みならボタンを押さなくても訳文が「AI翻訳」の注記付きで表示され、「原文を表示」でトグルできます。翻訳に失敗しても原文の閲覧はそのまま可能です。
- 通報:各投稿の ⚠ を押すと「通報」ボタンに変わり、もう一度押すと理由(誹謗中傷/不適切/個人情報/スパム/その他)+ 任意の詳細のフォームが開きます。匿名で送れます。通報しても投稿は自動では消えず、運営が管理画面で判断します。
- 編集・削除:投稿の編集はできません(書いた時点のスナップショット)。削除・非表示は運営のみが行えます。
- Nicknames and animal names: the nickname field is collapsed by default ("Want to set a nickname?" expands it). Post without one and you get a name like "Wandering Pig" — the same animal as your avatar, and always the same name for you. Boards without avatars use the default anonymous name.
- Length limits: text defaults to 500 characters, names to 24 (configurable per board).
- Anchor replies: write
>>1to reference a post (up to 8 per post; button or hand-typed both work). Links jump on click and preview on hover. - Links in posts: only URLs starting with
https://become links. Clicking shows a leave-site confirmation and opens a new tab only on OK.http://stays plain text. - AI translation: posts that look like another language get a Translate button. Already-translated posts show the translation by default with an "AI translation" note and a "Show original" toggle. If translation fails, reading the original is unaffected.
- Reporting: press ⚠ on a post to arm the Report button, press again for a form (harassment / inappropriate / personal info / spam / other, plus optional detail). Anonymous is fine. Reports never hide a post automatically — operators decide.
- Editing & deletion: posts cannot be edited (they are a snapshot). Hiding and deletion are operator-only.
識別子の考え方Project & board IDs
すべての会話は「どのプロジェクトの、どの対象物か」の 2 つの識別子で整理されます。
| 識別子 | 形式 | 例 | 意味 |
|---|---|---|---|
| project | 小文字英数字とハイフン・32文字まで | my-app |
サービスそのもの。利用には台帳への登録が必要です。 |
| board | 小文字英数字とハイフン・アンダースコア・64文字まで | lounge / pin-123 |
会話を紐付ける対象物。全体の雑談なら lounge、個別対象ならその ID など。登録不要で自由に増やせます。 |
「サービス雑談」も「ピンへのコメント」も「レビュー」も内部では同じ会話データ。UI だけ用途に応じて変えれば、同じ基盤の上に何種類でもコメント欄を展開できます。
Every conversation is organized by two identifiers: which project, and which thing it belongs to.
| Identifier | Format | Example | Meaning |
|---|---|---|---|
| project | Lowercase letters, digits, hyphens; up to 32 chars | my-app |
The service itself. Projects must be registered to use the API. |
| board | Lowercase letters, digits, hyphens, underscores; up to 64 chars | lounge / pin-123 |
The thing the conversation is attached to — lounge for general chat, an item's ID for per-item comments. Boards need no registration. |
Service chat, comments on a pin, reviews — internally they're all the same conversation data. Vary only the UI and you can run any number of comment sections on the same platform.
JavaScript から直接呼ぶCalling from JavaScript
埋め込みではなく自前で描画したい場合は、クライアントを読み込んで list / post を呼びます。必須の設定は project ひとつ(API キーや接続先の指定は不要)。
<script src="https://convs.net/js/convs-client.js"></script>
<script>
const convs = Convs.createClient({ project: "my-app" });
// 短縮形: Convs.createClient("my-app")
const messages = await convs.list("lounge", { limit: 30 });
await convs.post("lounge", { name: "名無しさん", text: ">>1 アンカー返信もそのまま書けます" });
const byNo = await convs.messagesByNo("lounge", [1, 3]); // >>N の解決用
</script>
翻訳・通報などクライアントに無い操作は、次の「API 仕様」の action を素の fetch(POST・JSON)で呼べます。
取得したテキストは textContent で描画してください(innerHTML に渡すと XSS になります)。
To render things yourself instead of using the embed, load the client and call list / post. The only required setting is the project (no API key, no endpoint config).
<script src="https://convs.net/js/convs-client.js"></script>
<script>
const convs = Convs.createClient({ project: "my-app" });
// shorthand: Convs.createClient("my-app")
const messages = await convs.list("lounge", { limit: 30 });
await convs.post("lounge", { name: "Anonymous", text: ">>1 anchor replies work as-is" });
const byNo = await convs.messagesByNo("lounge", [1, 3]); // resolves >>N references
</script>
Operations the client does not wrap (translation, reporting, …) can be called with plain fetch (POST, JSON) using the actions in the API reference below.
Render fetched text with textContent (passing it to innerHTML opens you up to XSS).
API 仕様API reference
convs の API エンドポイント(クライアント/埋め込みに内蔵)への POST(JSON)。body の action で分岐。API キーは不要です。
権限モデル
| 操作 | 誰ができるか | 守り |
|---|---|---|
| 閲覧 | 公開(誰でも) | — |
| 投稿 | 登録プロジェクトのページから | Origin 検証 + レート制限 + 重複投稿防止 + 入力検証 |
| 翻訳 | 閲覧と同じ(読める投稿のみ) | レート制限 + 結果キャッシュ(同じ文章を再翻訳しない) |
| 通報 | 閲覧と同じ(実在する投稿のみ・匿名可) | レート制限 + 同一投稿への通報は1日1回 |
| 編集 | 原則不可(API 自体がない) | 投稿は書いた時点のスナップショット |
| 削除・非表示・通報管理 | 管理者のみ | 公開フロントに埋め込まれない管理者認証(管理 API を分離) |
action 一覧
| action | 主なパラメータ | 返り値 |
|---|---|---|
ping | — | { ok, service, time } |
listMessages | project / board / limit(既定30・最大100)/ lang(任意・ja | en) | { ok, messages: [{ id, no, name, text, createdAt, avatar, … }] }。lang 指定時、翻訳キャッシュがある投稿には translated を添付 |
postMessage | project / board / name(省略時「名無しさん」)/ text(必須)/ clientId(任意・アバターの種) | { ok, message: { id, no, name, text, createdAt, … } } |
getMessagesByNo | project / board / nos(No. の配列・最大20件) | { ok, messages: { "1": {…} | null } }(null=存在しない・削除済み) |
translateMessage | project / board / id / target(ja | en) | { ok, translated: { lang, text, srcLang } }(結果はキャッシュ・原文は常に保持) |
reportMessage | project / board / id / reason / detail(任意) | { ok }(運営に通知。投稿は自動では非表示にしない) |
joinWaitlist | email / lang(確認メールの言語) | { ok }(登録済みなら { ok, already: true }) |
このほかに管理 API(非表示・非表示解除・削除・通報管理・集計)がありますが、公開フロントに載らない管理者認証で分離されています。
レート制限(送信元 IP 単位・既定値)
| 対象 | 既定の上限 |
|---|---|
| 全 action 共通 | 60回 / 60秒 |
| 投稿 | 5回 / 60秒 + 同一内容の連投防止(120秒) |
| 翻訳 | 10回 / 1時間 |
| 通報 | 3回 / 10分 + 同一投稿への通報は1日1回 |
| API 利用登録 | 3回 / 1時間 |
エラーコード
| HTTP | error | 意味 |
|---|---|---|
| 400 | INVALID_PROJECT / INVALID_BOARD | 識別子の形式が不正 |
| 400 | EMPTY_TEXT / TEXT_REJECTED | 本文が空・禁止語を含む |
| 400 | INVALID_EMAIL | メールアドレスの形式が不正(joinWaitlist) |
| 400 | INVALID_JSON / INVALID_REQUEST / UNKNOWN_ACTION | ボディ不正・想定外フィールド・未対応 action |
| 403 | PROJECT_NOT_FOUND / PROJECT_DISABLED | 未登録・停止中のプロジェクト |
| 403 | BOARD_DISABLED / BOARD_READ_CLOSED / BOARD_WRITE_CLOSED | 停止・閲覧停止・書き込み停止の掲示板 |
| 403 | ORIGIN_NOT_ALLOWED / FORBIDDEN | 許可外 Origin・管理 API への権限なし |
| 404 | NOT_FOUND | 対象の投稿が存在しない(通報・翻訳など) |
| 429 | RATE_LIMITED / DUPLICATE_MESSAGE | レート制限・同一内容の連投 |
| 500 | INTERNAL | サーバー内部エラー |
| 502 / 503 | TRANSLATE_FAILED / TRANSLATE_UNAVAILABLE | 翻訳の失敗・翻訳機能が未設定(原文の閲覧はそのまま可能) |
POST JSON to the convs API endpoint (built into the client/embed); the body's action field selects the operation. No API key needed.
Permission model
| Operation | Who | Protection |
|---|---|---|
| Read | Public (anyone) | — |
| Post | Pages of registered projects | Origin check + rate limit + duplicate prevention + input validation |
| Translate | Same as read (readable posts only) | Rate limit + result cache (never re-translates the same text) |
| Report | Same as read (existing posts only; anonymous OK) | Rate limit + one report per post per day per source |
| Edit | Not possible (no API exists) | Posts are a snapshot of what was written |
| Hide / delete / report management | Admins only | Admin auth never embedded in public frontends (separate admin API) |
Actions
| action | Main parameters | Returns |
|---|---|---|
ping | — | { ok, service, time } |
listMessages | project / board / limit (default 30, max 100) / lang (optional, ja | en) | { ok, messages: [{ id, no, name, text, createdAt, avatar, … }] }. With lang, posts with a cached translation include translated |
postMessage | project / board / name (optional) / text (required) / clientId (optional, seeds the avatar) | { ok, message: { id, no, name, text, createdAt, … } } |
getMessagesByNo | project / board / nos (array of No., max 20) | { ok, messages: { "1": {…} | null } } (null = missing or deleted) |
translateMessage | project / board / id / target (ja | en) | { ok, translated: { lang, text, srcLang } } (results are cached; the original is always kept) |
reportMessage | project / board / id / reason / detail (optional) | { ok } (notifies the operators; the post is not hidden automatically) |
joinWaitlist | email / lang (confirmation email language) | { ok } (already registered → { ok, already: true }) |
There is also an admin API (hide, unhide, delete, report management, analytics), separated behind admin authentication that never ships in public frontends.
Rate limits (per source IP, defaults)
| Scope | Default limit |
|---|---|
| All actions | 60 / 60s |
| Posting | 5 / 60s + duplicate-post prevention (120s) |
| Translation | 10 / hour |
| Reporting | 3 / 10min + one report per post per day |
| Waiting-list signup | 3 / hour |
Error codes
| HTTP | error | Meaning |
|---|---|---|
| 400 | INVALID_PROJECT / INVALID_BOARD | Malformed identifier |
| 400 | EMPTY_TEXT / TEXT_REJECTED | Empty text; banned word |
| 400 | INVALID_EMAIL | Malformed email address (joinWaitlist) |
| 400 | INVALID_JSON / INVALID_REQUEST / UNKNOWN_ACTION | Bad body, unexpected field, or unsupported action |
| 403 | PROJECT_NOT_FOUND / PROJECT_DISABLED | Unregistered or disabled project |
| 403 | BOARD_DISABLED / BOARD_READ_CLOSED / BOARD_WRITE_CLOSED | Disabled, read-closed, or write-closed board |
| 403 | ORIGIN_NOT_ALLOWED / FORBIDDEN | Origin not allowed; no admin permission |
| 404 | NOT_FOUND | Target post does not exist (report, translate, etc.) |
| 429 | RATE_LIMITED / DUPLICATE_MESSAGE | Rate limited; duplicate post |
| 500 | INTERNAL | Internal server error |
| 502 / 503 | TRANSLATE_FAILED / TRANSLATE_UNAVAILABLE | Translation failed or not configured (reading the original still works) |
データの持ち方Data model
DynamoDB のシングルテーブル。プロジェクト×掲示板ごとに完全分離されます。
pk = conv#<project>#<board> … プロジェクト×掲示板ごとに完全分離
sk = <JSTタイムスタンプ>#<ランダムID> … 時刻順に並ぶ投稿ID(= レスポンスの id)
属性: no / name / text / createdAt / refs / replyCount / replyNos
/ (hidden) / (i18n_ja / i18n_en … 翻訳キャッシュ。無ければ付けない)
pk = seq#<project>#<board> … No. 採番カウンタ(原子的インクリメント)
pk = convno#<project>#<board> … No.→投稿の索引(sk はゼロ詰めの No.)
pk = report … 通報(sk = 時刻#ランダムID・status で管理)
pk = waitlist … API 利用ウェイティングリスト(sk = メール)
- アバターと動物ネームの種:投稿の
avatarは clientId の SHA-256 の先頭12桁(一方向)。clientId 本体は API から返しません。 - 後方互換:機能追加は「属性が無ければ付けない」方式で行い、既存データはそのまま共存します(翻訳キャッシュ・非表示フラグも同様)。
- 投稿の状態:通常(属性なし)→ 非表示(
hidden=true・往復可能な論理削除)→ 物理削除(最終手段)の3段階。
A single DynamoDB table, fully isolated per project × board.
pk = conv#<project>#<board> … fully isolated per project × board
sk = <JST timestamp>#<random ID> … time-ordered post ID (= id in responses)
attrs: no / name / text / createdAt / refs / replyCount / replyNos
/ (hidden) / (i18n_ja / i18n_en … translation cache, absent until needed)
pk = seq#<project>#<board> … No. counter (atomic increment)
pk = convno#<project>#<board> … No. → post index (sk is the zero-padded No.)
pk = report … reports (sk = timestamp#random ID, tracked by status)
pk = waitlist … API waiting list (sk = email address)
- Avatar / animal-name seed: a post's
avataris the first 12 hex chars of SHA-256 of the clientId (one-way). The clientId itself is never returned by the API. - Backward compatibility: features are added as "attributes absent until needed", so existing data coexists untouched (translation cache and the hidden flag included).
- Post states: normal (no attribute) → hidden (
hidden=true, reversible logical delete) → physical delete (last resort).
セキュリティSecurity
- 本文は必ずテキストとして描画:API は無害化済み HTML を返しません。埋め込みウィジェットは textContent 描画。自前描画時も innerHTML に渡さないこと(AI 翻訳の結果も同様です)。
- リンクの取り扱い:ウィジェットは
https://の URL だけをリンク化し、クリック時に外部サイトへの移動を確認します。リンクにはnoopener noreferrer nofollowが付きます。 - API キーを使わない設計:フロントに埋め込む値は秘密になりません。鍵の代わりに「台帳登録制 + Origin 検証 + レート制限 + 重複投稿防止 + 入力検証」の多層で守ります。Origin も完全な認証ではない前提です。
- プライバシー:clientId は API から返しません(アバター・動物ネームの種は一方向ハッシュ由来)。通報は匿名で送れ、通報者の情報が他の利用者に見えることはありません。
- 投稿は公開情報:誰でも読めます。個人情報を書かない案内を各サービスの UI 上でも出してください。
- レート制限は万能ではない:荒らしの速度は落とせますが完全には防げません。問題投稿は ⚠(通報)で運営に知らせることができ、管理者が非表示・削除できます。
- Always render text as text: the API does not return sanitized HTML. The embed widget renders with textContent; if you render yourself, never pass content to innerHTML (this applies to AI translation results too).
- Link handling: the widget only linkifies
https://URLs and confirms before leaving the site. Links carrynoopener noreferrer nofollow. - No API keys by design: anything embedded in a frontend is not a secret. Instead of keys, convs layers "registry allowlist + Origin checks + rate limits + duplicate prevention + input validation". Origin itself is not treated as full authentication either.
- Privacy: the clientId is never returned by the API (avatar and animal-name seeds are one-way hashes). Reports can be sent anonymously and reporter details are never shown to other users.
- Posts are public: anyone can read them. Please also tell your users, in your own UI, not to post personal information.
- Rate limiting is not a silver bullet: it slows abuse but cannot stop it entirely. Problem posts can be reported with ⚠, and admins can hide or delete them.