技術資料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 >>1 becomes 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-avatarpixel投稿者ごとに動物モチーフのピクセルアバター(16×16・全10種×配色)を表示。同じ人は必ず同じキャラクター。名無し投稿の「動物ネーム」もこのアバターと連動します。
data-convs-langenフォームのラベル・ボタン・状態表示などウィジェットの UI を英語に(省略時は日本語)。投稿の AI 翻訳の翻訳先もこの言語になります。
data-convs-endpointURLローカル開発・セルフホスト時の接続先上書き。空文字にするとバックエンド無しのモックで動きます(表示確認用)。

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

AttributeValueDescription
data-convsrequiredproject ID/board ID. Place as many boards as you like, per page or per item.
data-avatarpixelShows 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-langenSwitches the widget UI — labels, buttons, status text — to English (Japanese by default). AI translation also targets this language.
data-convs-endpointURLEndpoint 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 >>1 to 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.

IdentifierFormatExampleMeaning
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 }
listMessagesproject / board / limit(既定30・最大100)/ lang(任意・ja | en){ ok, messages: [{ id, no, name, text, createdAt, avatar, … }] }lang 指定時、翻訳キャッシュがある投稿には translated を添付
postMessageproject / board / name(省略時「名無しさん」)/ text(必須)/ clientId(任意・アバターの種){ ok, message: { id, no, name, text, createdAt, … } }
getMessagesByNoproject / board / nos(No. の配列・最大20件){ ok, messages: { "1": {…} | null } }(null=存在しない・削除済み)
translateMessageproject / board / id / target(ja | en){ ok, translated: { lang, text, srcLang } }(結果はキャッシュ・原文は常に保持)
reportMessageproject / board / id / reason / detail(任意){ ok }(運営に通知。投稿は自動では非表示にしない)
joinWaitlistemail / lang(確認メールの言語){ ok }(登録済みなら { ok, already: true }

このほかに管理 API(非表示・非表示解除・削除・通報管理・集計)がありますが、公開フロントに載らない管理者認証で分離されています。

レート制限(送信元 IP 単位・既定値)

対象既定の上限
全 action 共通60回 / 60秒
投稿5回 / 60秒 + 同一内容の連投防止(120秒)
翻訳10回 / 1時間
通報3回 / 10分 + 同一投稿への通報は1日1回
API 利用登録3回 / 1時間

エラーコード

HTTPerror意味
400INVALID_PROJECT / INVALID_BOARD識別子の形式が不正
400EMPTY_TEXT / TEXT_REJECTED本文が空・禁止語を含む
400INVALID_EMAILメールアドレスの形式が不正(joinWaitlist)
400INVALID_JSON / INVALID_REQUEST / UNKNOWN_ACTIONボディ不正・想定外フィールド・未対応 action
403PROJECT_NOT_FOUND / PROJECT_DISABLED未登録・停止中のプロジェクト
403BOARD_DISABLED / BOARD_READ_CLOSED / BOARD_WRITE_CLOSED停止・閲覧停止・書き込み停止の掲示板
403ORIGIN_NOT_ALLOWED / FORBIDDEN許可外 Origin・管理 API への権限なし
404NOT_FOUND対象の投稿が存在しない(通報・翻訳など)
429RATE_LIMITED / DUPLICATE_MESSAGEレート制限・同一内容の連投
500INTERNALサーバー内部エラー
502 / 503TRANSLATE_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

OperationWhoProtection
ReadPublic (anyone)
PostPages of registered projectsOrigin check + rate limit + duplicate prevention + input validation
TranslateSame as read (readable posts only)Rate limit + result cache (never re-translates the same text)
ReportSame as read (existing posts only; anonymous OK)Rate limit + one report per post per day per source
EditNot possible (no API exists)Posts are a snapshot of what was written
Hide / delete / report managementAdmins onlyAdmin auth never embedded in public frontends (separate admin API)

Actions

actionMain parametersReturns
ping{ ok, service, time }
listMessagesproject / 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
postMessageproject / board / name (optional) / text (required) / clientId (optional, seeds the avatar){ ok, message: { id, no, name, text, createdAt, … } }
getMessagesByNoproject / board / nos (array of No., max 20){ ok, messages: { "1": {…} | null } } (null = missing or deleted)
translateMessageproject / board / id / target (ja | en){ ok, translated: { lang, text, srcLang } } (results are cached; the original is always kept)
reportMessageproject / board / id / reason / detail (optional){ ok } (notifies the operators; the post is not hidden automatically)
joinWaitlistemail / 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)

ScopeDefault limit
All actions60 / 60s
Posting5 / 60s + duplicate-post prevention (120s)
Translation10 / hour
Reporting3 / 10min + one report per post per day
Waiting-list signup3 / hour

Error codes

HTTPerrorMeaning
400INVALID_PROJECT / INVALID_BOARDMalformed identifier
400EMPTY_TEXT / TEXT_REJECTEDEmpty text; banned word
400INVALID_EMAILMalformed email address (joinWaitlist)
400INVALID_JSON / INVALID_REQUEST / UNKNOWN_ACTIONBad body, unexpected field, or unsupported action
403PROJECT_NOT_FOUND / PROJECT_DISABLEDUnregistered or disabled project
403BOARD_DISABLED / BOARD_READ_CLOSED / BOARD_WRITE_CLOSEDDisabled, read-closed, or write-closed board
403ORIGIN_NOT_ALLOWED / FORBIDDENOrigin not allowed; no admin permission
404NOT_FOUNDTarget post does not exist (report, translate, etc.)
429RATE_LIMITED / DUPLICATE_MESSAGERate limited; duplicate post
500INTERNALInternal server error
502 / 503TRANSLATE_FAILED / TRANSLATE_UNAVAILABLETranslation 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 avatar is 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 carry noopener 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.