REST API

ベース URL は 1 つ、キーは 1 つ、レンダリングの方法は 2 つです。テンプレートとデータを送ると、PDF または画像が返ってきます。

認証

/v1 へのリクエストにはすべて、x-api-key ヘッダーに API キーが必要です。キーはコンソールで発行し、表示は一度きりです — こちらではハッシュしか保存しないため、なくしたキーは再発行するもので、取り出せるものではありません。

キーがない・間違っている・失効している、のいずれも同じ 401 を返します。どれなのかはお伝えしません。その違いが役に立つのは、推測している人だけだからです。

その場で生成する

POST /v1/create は、同じリクエストの中で生成して応答します。応答に入るのはファイルそのものではなく署名付きのダウンロード URL です — そのおかげで、20 KB のラベルでも 40 MB のレポートでも同じ応答で済みます。

この URL には期限があります。受け取ったらその場でダウンロードしてください。保存やキャッシュはしないでください。

バックグラウンドで生成する

POST /v1/create-async はジョブ ID をすぐに返し、生成はキューで行います。大きなドキュメントや、呼び出し側が待てない場面でお使いください。

GET /v1/jobs/{id} で状態が分かり、完了後は同じ署名付き URL が返ります。

失敗したジョブは POST /v1/jobs/{id}/retry でもう一度実行できます。ジョブが失敗の状態にある間、かつ 24 時間以内に限ります — リクエストのデータをその間だけ保管しているためで、過ぎた場合は送り直してください。完了したジョブを再実行することはありません。同じレンダリングを二重に課金することになるからです。

POST /v1/create-async
# 1. enqueue the job
curl -X POST https://brewmypdf.com/v1/create-async \
  -H "x-api-key: bmp_live_..." \
  -H "content-type: application/json" \
  --data-binary @invoice.json

# 2. poll for the result
curl https://brewmypdf.com/v1/jobs/01M0... \
  -H "x-api-key: bmp_live_..."

まとめて生成する

POST /v1/batch は items 配列を最大 50 件まで受け取り、まとめてキューに入れます。項目ごとに template または template_id と、それぞれの data を持てるので、1 回の呼び出しで顧客ごとの明細書を生成できます。応答はジョブ ID の一覧で、結果は /v1/jobs/{id} から 1 件ずつ受け取ります。この API は待ちません — 50 件のレンダリングを待つと、リクエストのほうが先に尽きるからです。

merge を true にすると、送った順に結合された PDF が 1 つ返ります。この経路は 20 件までです。1 つのジョブの中でレンダリングを順番に行うためです。こちらは待って、/v1/create と同じようにダウンロード URL を返します。画像の結合は、黙って PDF に変えたりせず拒否します。

クォータはバッチ全体に対して、キューに入れる前に一度だけ確認します。残り 3 件のアカウントから 50 件のバッチが来た場合は、まるごと拒否します — 一部だけ処理するくらいなら、1 件も受け取らないほうがよいからです。途中まで処理されたバッチは、返金も再試行も扱いに困ります。

結合したファイルは、ドキュメントごとのヘッダーとフッターをそのまま保ちます。あとからページ番号を振り直すことはしません。振り直すと「これは 1 つのドキュメントだ」と主張することになりますが、実際はそうではなく、1 つのファイルに入った複数のドキュメントだからです。通しでページ番号を振りたい場合は、繰り返しブロックを使って 1 つのドキュメントとして作ってください。

終わったことをお知らせする

非同期リクエストやバッチに webhook を指定すると、ジョブが終わった時点でその宛先に一度だけ POST します。ポーリングをしなくて済むようにするための仕組みです。https のみを受け付け、プライベートや内部のアドレスは拒否します。宛先はキューに入れる前に検査するので、誤った宛先はレンダリングを消費する前に 400 として返ります。

本文はこれだけです: {"job_id":"...","status":"done"} — 失敗したときは "status":"failed" と error_code が入ります。ダウンロード URL は入っていません。こちらのダウンロード URL は、それ自体が権限だからです。通知に入れてしまうと、その通知を見た人すべてにドキュメントを渡すことになります。URL はあとからご自身の API キーで取得してください: GET /v1/jobs/{id}。

つまり、この通知は証拠ではありません。署名もなく、同じ形の POST は誰にでも送れます。「見に行くための合図」として扱い、何が本当かは GET /v1/jobs/{id} に決めさせてください。同じ通知が二度届くこともあるので、ハンドラーは job_id について冪等にしておいてください。

最大 3 回まで試みます — 失敗した 1 秒後、次は 2 秒後です。再試行するのは 5xx と無応答のときだけで、4xx は二度目も同じなのでそこでやめます。5 秒以内に応答してください。なお、リダイレクトは追いません。まず 200〜299 をすぐ返し、処理はそのあとで行ってください。

通知が失敗しても、レンダリングが失敗するわけではありません。ファイルはできており、GET /v1/jobs/{id} からお渡しできます。配信の結果は同じ応答の webhook_status に入ります — 受け取った HTTP ステータス、まったく届かなかった場合は 0 です。この項目がない場合は、そのジョブに webhook がなかったということです。結合しないバッチは複数のジョブなので、ジョブごとに 1 通ずつ通知します。

PNG と JPEG

同じテンプレートを PDF ではなく画像として出すこともできます。リクエストに format を足してください: "png" または "jpeg" です。省略すると、これまでどおり PDF になります。JPEG では quality を 1〜100 で指定することもできます。

quality は JPEG だけに効きます。PNG と一緒に送った場合は、無視せず拒否します — 黙って無視されるオプションは、こちら側の不具合のように見えますし、レンダラー自体がその組み合わせを受け付けません。

画像は、設計したページの大きさそのままで、鮮明さを保つために 2 倍で描かれ、ページ余白も保たれます。画像も PDF も、月間クォータでは同じく 1 レンダーとして数えます。

画像にできないこと: ページです。印刷すると 3 ページになるドキュメントは、1 枚の縦長の画像になります。改ページは印刷の考え方だからです。同じ理由で、ヘッダーもフッターもページ番号もありません — [[page]] と [[bpage]] は印刷の最中に埋まるものであり、ここでは何も印刷していません。

背景画像をアップロードする

背景画像は POST /v1/assets でアップロードします。本文はファイルそのもので、content-type は image/jpeg または image/png です。応答の asset_id を、テンプレートの page.backgrounds[].assetId に入れてください。

これは PDF のアップロード(POST /v1/files)とは別のアドレスです。あちらは 24 時間で削除されますが、背景はテンプレートの一部なので、削除するまで残ります。だからこそアドレスを分けています — パラメータ 1 つで分けていたら、間違えたときに応答は同じ見た目のまま、翌朝テンプレートが壊れていたはずです。

JPEG と PNG のみです。WebP・GIF・SVG は拒否します。1 枚あたり 2MB まで、ドキュメント 1 つにつき背景は 3 枚までです。

PNG は展開せずにそのまま通します — 速く、ファイルも大きくなりません。ただし透明度(アルファチャンネル)やインターレースを含む PNG は、こちら側で再圧縮する必要があるため、画素数が 400 万を超えるものは拒否します。A4 1 ページを 200dpi で読み取ると約 387 万画素です。300dpi で読み取った全面の用紙は、JPEG でアップロードしてください — そちらの経路に画素数の上限はなく、結果は同じです。

応答の passthrough が false のとき、その画像が上の上限の対象になります。アップロードした時点で分かりますので、必ずご確認ください。

削除は DELETE /v1/assets/{id} です。存在しないものを削除しても成功として返します — 区別してお伝えすると、その asset を他の誰かが持っているかどうかを教えることになるからです。こちらから自動で削除することはありませんので、使わなくなったものはご自身で消してください。

背景はすべてのページに敷かれ、常にいちばん下に置かれます。特定のページだけを狙うことはできません — レンダリング前にページ数が分からないためです。何かを上に重ねたい場合は POST /v1/pdf/stamp をお使いください。背景の座標は用紙全体を基準に測ります。余白の内側から始まるノードの座標とは異なりますのでご注意ください。PDF を背景として使うことには、まだ対応していません。

ファイルに鍵をかける

リクエストに password を足すと、そのパスワードなしでは PDF が開けなくなります — 契約書や給与明細など、ファイルが外に出ても閉じたままであってほしいものにお使いください。ダウンロード URL が守るのは受け渡しであってファイルではありません。いったんダウンロードされれば、誰でも開けます。

owner_password も併せて指定すると、そちらで開いたときはすべての制限が外れます。省略すると開くためのパスワードが所有者パスワードを兼ねてしまい、下の制限が意味を持たなくなります — 何かを制限するおつもりなら、2 つを別々にしてください。

permissions では、印刷・コピー・変更・注釈・入力を個別に禁止できます。省略するとすべて許可されます。フォーム欄のあるドキュメントで入力を禁止すると、その欄が使えなくなりますのでご注意ください。スクリーンリーダーのためのテキスト抽出は、いかなる場合も妨げません。

パスワードは UTF-8 で 127 バイトまでです — 日本語は 1 文字 3 バイトなので、およそ 42 文字です。それより長い場合は、切り詰めずに拒否します。黙って切り詰めると誰にも開けないファイルができあがり、そこから戻る方法がないからです。

パスワードはこちらで保管しません。リクエストとともに届き、ジョブが終わると削除されます — ですから、なくされた場合はこちらでも開けません。画像(PNG・JPEG)に鍵はかけられず、両方を指定した場合は拒否します。

お手元の PDF を加工する

お手元の PDF を加工することもできます。POST /v1/files でアップロードしてください — content-type: application/pdf で、本文に PDF をそのまま入れます。応答の file_id を、以下の操作に渡します。アップロードしたファイルは 24 時間で削除され、1 ファイル 10MB・100 ページまでです。結合は合計 20 ファイル・100 ページまでで、ページの上限が同じなのは、結合すると 1 つのファイルになり、実際に開かれるのはそれだからです。ページ数の上限をサイズとは別に設けているのには理由があります: 3,000 ページの PDF が 600KB 未満ということもあり、サイズだけでは通ってしまいます。そしてこの上限は、こちらの都合ではなく読む側が決めています — こちらのサーバーが問題なく扱える 1,000 ページの PDF でも、スマートフォンでは止まります。受け取った方が開けないファイルをお渡しするくらいなら、お断りするほうがよいと考えています。

GET /v1/files/{id} は、そのファイルが何であるかを教えます: ページ数、各ページの大きさと回転、暗号化されているか、XFA フォームを含むか(xfa: true — 下のフォーム欄の項もご覧ください)、そして中にあるフォーム欄の名前です。大きさはビューアが表示するものです — MediaBox で切り取った CropBox を正の数に正規化したもので、ファイル内部の生の値ではなく画面上のページと一致します。組み込む前に、まずこれをご覧ください。

POST /v1/pdf/merge は、並べた順にファイルを結合します(20 件まで)。POST /v1/pdf/stamp はファイルに文字を書き込みます: text は必須で、pages(1 から数えます。省略すると全ページ)、x と y(ページ左下からのポイント)、size、opacity、rotate、color を足せます。透かしと文字の追加は、オプションが違うだけの同じ機能です。

POST /v1/pdf/fields は、すでにあるフォーム欄に値を入れます。名前と値を values で渡し、flatten を true にすると欄が描画の一部になり、あとから変更できなくなります — これは元に戻せませんので、意図してお使いください。署名欄のあるドキュメントは決して平坦化しません。固めてしまうと、署名するための場所そのものがなくなるからです。応答の filled・missing・rejected は必ずご確認ください: ドキュメントに存在しない名前や、書き込めなかった値は、黙って見過ごさずにお伝えします。

できないこと: スタンプの文字は Latin-1 のみです — 日本語・中国語・韓国語・絵文字は拒否します。標準フォントでは描けず、黙って豆腐(□)を押すよりはお断りするほうがよいからです。暗号化された PDF は、こちらで開けないため加工できません。選択肢を持つ欄には、その一覧にある値しか入れられません。XFA フォーム(Adobe LiveCycle — 官公庁や金融の書類でよく使われます)は、はっきり拒否します: 欄は入力できるように見えても、ビューアは XFA の層を描くため、書き込んだ値が黙って現れないからです。これらの操作はブラウザを使わないので、月間のレンダークォータには数えません — レート制限だけが適用されます。

API から見るアカウント

GET /v1/account は、上限に拒否されて知る前に、いまの状況をお伝えします: プラン、今月使ったレンダー数と残り、上限に対するテンプレート数、そして今月のジョブの要約(成功・失敗・バイト数)です。これらの数値は、上限を実際に適用している関数と同じものから出ています — このエンドポイントが「残り」として示す数は、クォータの関門がいまなお受け付ける数そのものです。読み取り専用で、プランの変更は請求ポータルから、API キーの管理はコンソールでのみ行います。

活動記録

アカウント上のすべての操作は、追記のみの記録に残ります — 誰が・いつ・どこから行い、成功したかどうかです。コンソールの「活動記録」でご覧いただけます。記録は 90 日を過ぎると削除ではなく、こちらのアーカイブへ移ります。

記録には変わらない操作コードが入っています。画面には分かりやすい名前が出ますが、分岐に使うのはコードのほうです — 表示名を書き直してもコードは変わりません。当社の担当者が書いた記録は BrewMyPDF チームとして印が付くので、こちらがアカウントを開いたときは必ず分かります。

コード意味
acct.billing.webhook決済の変化
acct.team.accept招待を承諾
acct.team.inviteメンバー招待
acct.team.removeメンバー削除
acct.team.roleメンバー権限変更
admin.account.activateBrewMyPDF チームがアカウント復帰
admin.account.eraseBrewMyPDF チームがアカウント削除
admin.account.listBrewMyPDF チームが一覧を閲覧
admin.account.planBrewMyPDF チームがプラン変更
admin.account.suspendBrewMyPDF チームがアカウント停止
admin.account.viewBrewMyPDF チームがアカウントを閲覧
admin.job.retryBrewMyPDF チームがレンダー再試行
admin.job.viewBrewMyPDF チームがレンダーを閲覧
admin.usage.viewBrewMyPDF チームが使用量を閲覧
api.limit.ip.setAPI IP 許可リスト変更
auth.apikey.issueAPI キー発行
auth.apikey.rotateAPI キー交換
auth.apikey.verifyAPI キー使用
auth.login.logoutログアウト
auth.login.submitログイン
auth.password.changeパスワード変更
auth.reset.confirmパスワード再設定の完了
auth.reset.requestパスワード再設定の要求
auth.signup.form登録申請
auth.signup.verifyメール認証
job.dlqレンダー最終失敗
job.queue.consumeレンダー完了
job.queue.enqueueレンダー要求
legal.eraseアカウント削除
store.byo.deliver自分のストレージへ配信
store.eu.setデータ所在地の変更
tpl.crud.createテンプレート作成
tpl.crud.deleteテンプレート削除
tpl.crud.updateテンプレート更新
tpl.version.restore以前の版に復元

チーム

Professional プラン以上では、コンソール(チームメニュー)からワークスペースに人を招待できます。編集者はテンプレートを変更でき、閲覧者は見ることとプレビューだけができます。メンバーが触れるのはテンプレートだけで、請求・API キー・設定はオーナーのもとに残ります。これは意図した設計です — それらはアカウント自身の資格情報であり、委ねてしまうと「誰が払ったのか」「誰が漏らしたのか」の答えが消えてしまうからです。招待はメールで届き、招待されたアドレスでサインインした状態で承諾する必要があります。メンバーの操作はすべて、その人自身の身元で監査ログに残ります。チームの API はありません — 人の管理は慎重に行うべき仕事なので、コンソールで行います。

API でテンプレートを管理する

テンプレートは API から管理できるので、ご自身のリポジトリに置いてコードと同じようにデプロイできます。GET /v1/templates が一覧を返し(id・名前・日時。?limit は 200 まで)、POST /v1/templates は { name, doc } から作成して template_id と version_id を返します。doc はエディタが保存するのと同じドキュメント JSON で、下のスキーマの節で説明しているものです。書き込み時に検証するので、レンダリング時に失敗するテンプレートは保存の時点で拒否し、どこが違反かを応答に並べます。

GET /v1/templates/{id} は、現在の doc と schema_version を含めてテンプレートを返します。PUT /v1/templates/{id} で更新します: 新しいバージョンを書くなら doc を、名前を変えるなら name を、両方でも構いません — どちらもない PUT は、成功したふりをせずに拒否します。更新で上書きされることはありません。書いた doc は必ず新しいバージョンになり、テンプレートがそれを指すようになるので、過去にレンダリングしたものが失われることはありません。DELETE /v1/templates/{id} は論理削除です — 過去のレンダリング履歴はそのまま残り、テンプレートが一覧に出なくなります。

GET /v1/templates/{id}/versions はバージョンの一覧を返します(doc の中身は含めず、新しい順で、現在のものに印が付きます)。POST /v1/templates/{id}/versions/{vid}/restore は、古いバージョンをもう一度現在のものにします。復元はコピーではなくポインタの付け替えなので、すぐに終わり、バージョン一覧にもそのまま現れます。この 1 回の呼び出しが、そのままロールバックです。

これらの呼び出しはキーのレート制限を共有しますが、レンダークォータは消費しません。API キーで行った変更は、監査ログにそのキーとして記録され、人がコンソールで行った編集とは区別されます。プランごとのテンプレート数の上限は、コンソールと同じように作成時に適用されます。

エディタを埋め込む

当社のテンプレートエディタを、当社のブランドを出さずにご自身の製品の中へ置けます。利用者に BrewMyPDF のアカウントは必要ありません。ご自身のサーバーから API キーを使って POST /v1/embed/sessions を { template_id, mode, ttl_seconds } とともに呼びます — mode は "edit" または "view"、有効期間は既定で 15 分、最長 1 時間です。応答に url が入るので、それを iframe に入れてください。ブラウザから直接呼ばないでください: API キーをサーバーに留め、短命の URL だけがページに届くようにすることが、この仕組みの要だからです。

セッションの中で保存すると、そのテンプレートの新しいバージョンが作られます(監査にはお客様の API キーとして残ります)。プレビューは通常の経路でレンダリングされ、クォータを消費します — 埋め込みからのレンダリングも、ほかと同じ 1 件です。"view" のセッションでは保存できません。セッションが切れると保存は 403 になります: サーバーから新しいセッションを開いて、URL を読み込み直してください。なお、トークンは URL に含まれますので、埋め込み URL 自体を短命の秘密として扱ってください。

ご自身のバケットへ配信する

Professional プラン以上では、コンソール(設定 → BYO)から、成功したレンダリングをすべてお客様ご自身の S3 互換バケット(Cloudflare R2 または AWS S3)へコピーできます。任意のキー接頭辞の下に {job_id}.{ext} という名前で置かれます。約束をはっきりさせておきます: 配信はコピーであって、置き換えではありません。こちらに保存した出力とその署名付き URL は、どちらの場合も変わらず使えます。配信に失敗してもジョブが失敗することはありません — 監査ログに記録され、次のレンダリングで改めて試みます。設定を保存するとその場でテストアップロードを行うので、資格情報の誤りは最初の実レンダリングの後ではなく、前に分かります。シークレットキーは暗号化して保存し、二度と表示も返却もしません。

EU データレジデンシー

Premium プラン以上では、設定 → データレジデンシーからドキュメントを EU に固定できます。有効にすると、生成した出力とリクエストの本文は Cloudflare R2 の EU 管轄バケットに保存され、ダウンロードは常にキャッシュなしで配信されます。境界をはっきりさせておきます: 対象は切り替えた後に作られたドキュメント本体(出力とリクエストデータ)です — それ以前に保存された対象は移動せず、テンプレートの素材とフォントは引き続きグローバルに置かれ、ジョブのメタデータ(状態の行、監査の項目)はこちらのグローバルなデータベースにあります。レンダリング自体がどこで実行されるかはネットワークのエッジが決めるもので、この保証には含まれません。

上限

レート制限は、キー・アカウント・IP の 3 つの軸で同時に数えます。1 つだけで制限すると、キーをもう 1 本発行する、別のホストから呼ぶ、というだけで簡単に迂回できてしまうからです。

上限を超えると Retry-After ヘッダー付きの 429 を返します。その値に従ってください。すぐに再試行すると、状況は悪くなるだけです。

月間のレンダークォータはこれとは別で、アカウントごとに数えます。使い切ると 429 ではなく 402 を返します — 待てば解ける問題とそうでない問題の違いなので、ここは区別が大切です。

内容の上限はレート制限とは別のもので、皆さんが驚かれるのはこちらです。1 回のレンダリングで作れるのは最大 100 ページです。それを超えるリクエストは、何も描く前に 400 で拒否します — 途中で切ったりはしません。100 通で切られた請求書の束は完成しているように見えるので、エラーより厄介だからです。ページ数のほかに上限が 2 つあり、それぞれ数えるものが違います。表の行: 1 回のレンダリングで描けるのは合計 2,000 行まで、表 1 つあたりは 500 行までで、表が複数あるときは 2,000 を分け合います。改ページを伴う繰り返しブロック(同じ体裁をレコードごとに繰り返し、1 組が 1 ページになるもの): 1 回のレンダリングで 100 組までです — 1 組が 1 ページなので、それがそのまま 100 ページにあたります。改ページしないブロックは、この数には入りません。2 つは別々の予算で、互いを削り合いません — 100 社ぶんの請求書に明細が 8 行ずつなら、ブロック 100・行 800 で、どちらも収まっています。それを超えた分は切り詰め、warnings でお知らせします。この線は原価と予測可能性から来ています: 上限がないと、1 つのドキュメントがレンダリング時間を何倍にも使い、どこで切られるかは列数によって変わるため、前もって分かりません。1 回のリクエストが展開できるノードは最大 20,000 個、ドキュメント JSON 全体は 1 MB 未満、インライン画像はリクエストあたり合計 30 MB、テンプレートの入れ子は最大 4 段です。何かを切ったときは必ずお伝えします — 応答の warnings 配列が、どのノードで何が起きたかを示します。

レンダリングにはブラウザ内で 5 秒の予算があります。それより長くかかるドキュメントは、固まるのではなく失敗し、キューが再試行することもありません — いま大きすぎるドキュメントは、二度目も大きすぎるからです。

レンダリングが混み合っているときは、新しい仕事をお断りします — Retry-After ヘッダー付きの 429 が返ります。受け取っておいて数分遅れてお渡しするより、いまお伝えするほうがよいからです。Retry-After の時間だけ待って、同じリクエストをもう一度送ってください。

API へのアクセスをご自身のサーバーに限定できます: コンソールの 設定 → API IP 許可リスト に IP または CIDR を 20 件まで登録でき、リストがある状態では、その外からの API キーによるリクエストは — たとえ正しいキーであっても — 403 になります。コンソールへのサインインは意図的に対象外です。リストを間違えても、直しに入れなくなることがないようにするためです。同じ規則が MCP エンドポイントにも適用されます。

MCP サーバー

AI エージェントが BrewMyPDF を直接操作できます。MCP のエンドポイントは /mcp で、HTTP 上の JSON-RPC 2.0 を話し、REST の API キーを Authorization: Bearer <key> として送って認証します — REST API が使う x-api-key とはヘッダーが異なりますのでご注意ください。キー自体は同じものなので、エージェントが別の身元になることはなく、クォータも監査もお客様のアカウントに残ります。

公開しているツールは 4 つです: list_templates、describe_template_schema(そのテンプレートがどんなデータを求めるか)、preview_template(レンダークォータを消費しないプレビュー)、render_pdf(POST /v1/create と同じ経路)。

お使いの MCP クライアントを https://brewmypdf.com/mcp にキーとともに向けてください。説明文からテンプレートを生成する機能はコンソールにある別の機能で、クレジットで計量されます。MCP のツールはモデルを呼びません。

表ファイルからの実行

GET /v1/templates/{id}/form?format=xlsx(または format=csv&sheet=doc)は、コンソールが渡すのと同じデータ入力フォームを返します: 配列の深さごとに 1 シート、1 行目がヘッダー、テンプレート id とパス集合の 12 桁ハッシュを載せた隠しシート(xlsx)またはファイル名(csv)です。提出のときは、そのハッシュを送り返します。

POST /v1/intake/runs は、すでに data オブジェクトへまとめたドキュメントを受け取ります — 表の解析は呼び出し側で行ってください — 1 回あたり最大 50 件です: { template_id, hash, total, seq_start, docs: [{ key, data }], run_id }。最初のチャンクでは run_id を省き、応答で受け取ります。seq_start + docs.length が total に達すると、実行がキューに入ります。本文は 10 MB までです。チャンクごとに /v1/batch と同じクォータ・バックログ・予算・ページ数の検査を受けます — クォータはチャンク全体に対して判定します(残りのクレジットが少なければ、そのチャンクは 1 件も作りません)。チャンクが拒否されると実行はその時点で失敗になり、すでに受け付けた分はそのまま作られます。

GET /v1/intake/runs/{id} は、status(submitting・queued・done・partial・failed)、件数、完了したドキュメントごとの key と署名付き url、失敗したドキュメントの error_code、ZIP があれば zip_url を返します。POST /v1/intake/runs/{id}/zip は、終わった実行の ZIP を作ります — ストレージへ流しながら作り、合計 500 MB までです。それを超えると 413 になりますので、ドキュメントは個別にダウンロードしてください。

エラー: E:intake.serve#stale-form(409、フォームがテンプレートと合っていません — 新しいものを取得してください)、E:intake.serve#bad-chunk(400)、E:intake.run#chunk-failed(原因は中にあります: クォータ・予算・ページ数)、E:intake.run#not-found(404)、E:intake.run#zip-not-ready(409)、E:intake.run#zip-too-large(413)。

コネクタを作る

Zapier・Make・n8n、そして同種のものが必要とするのは同じ 5 つで、その 5 つはすべてここにあります。キーの確認は GET /v1/account で — 安価で副作用がありません。テンプレートの選択肢は GET /v1/templates で埋めます。項目の対応付け画面は GET /v1/templates/{id}/schema で描きます。生成は POST /v1/create です。長いジョブは GET /v1/jobs/{id} で確認するか、webhook を渡して知らせを受け取ってください。

知っておく価値があるのは schema エンドポイントです。paths(data.customer や data.items[*].name のような平坦な一覧)と schema(JSON Schema)を返します。入力欄にするのは平坦な一覧のほうで、型が必要なときのために JSON Schema があります。配列のパスは [*] で終わります — 1 つの項目がすべての要素を表すので、入力欄を 1 行ぶん作って繰り返してください。

つまずきやすい形が 2 つあります。アカウント応答の plan は文字列ではなくオブジェクトです({ id, renders_per_month, templates_max })— 忘れてラベルを組み立てると [object Object] と表示されます。もう 1 つ、式をまったく含まないテンプレートは paths が空の一覧で返りますが、これは正しい結果です: そのテンプレートはデータを必要としません。

お客様がすでにお持ちの API キーを、そのままお使いください。コネクタ専用の身元も、OAuth も、別のレート制限もありません — どの経路から呼んでも、クォータ・監査・上限はそのアカウントに残ります。

エラー

すべてのエラーには変わらないコードが付いています。メッセージではなくコードで分岐してください — メッセージは書き直されますし、翻訳もされます。

コードステータス意味
E:api.auth.key#unauthorized401キーがない、知らないキー、または失効したキーです。
E:api.auth.key#plan-required403キーは有効ですが、アカウントのプランに API アクセスが含まれていません。Professional プランから利用できます。
E:api.create#bad-json400リクエスト本文が正しい JSON ではありません。
E:api.create#missing-template400template も template_id も指定されていません。
E:api.export.mode#unsupported400export_type が "json" でも "file" でもありません。
E:api.export.mode#not-sync400export_type "file" は POST /v1/create でのみ使えます。
E:editor.doc.model#schema-mismatch400テンプレートのドキュメントがスキーマに違反しています。どこがどう違反したかを応答に並べます。
E:api.limit.payload#too-large413リクエスト本文が上限を超えています。
E:api.rate.limit#exceeded429レート制限を超えました。Retry-After の時間だけお待ちください。
E:api.limit.backlog#full429レンダリングが混み合っているため、受け付けを止めています。Retry-After の時間だけ待って、もう一度お送りください。
E:api.job.retry#not-failed400再実行できるのは、失敗したジョブだけです。
E:api.job.retry#payload-expired400リクエストデータの保管期限(24 時間)が過ぎました — もう一度お送りください。
E:acct.quota#exceeded402月間のレンダークォータを使い切りました。プランを上げるか、次の期間までお待ちください。
E:acct.quota#daily-exceeded402Free プランのみ — 1 日あたりのレンダー上限に達しました。00:00 UTC に戻ります。プランを上げると日次の上限はなくなりますが、月間クォータは引き続き適用されます。
E:api.job.route#not-found404そのジョブがないか、他のアカウントのものです。この 2 つは区別してお伝えしません。
E:job.webhook#bad-url400webhook のアドレスが https でないか、内部アドレスか、長すぎます。
E:render.pdf.password#bad-password400パスワードが文字列でないか、UTF-8 で 127 バイトを超えています。
E:render.pdf.password#not-pdf400画像にパスワードはかけられません。
E:render.pdf.password#failed400パスワードを適用できませんでした。ファイルは作りません — 保護されていないものをお渡しするよりよいからです。
E:render.pdfpage#too-large400背景画像が 2MB を超えています。
E:render.pdfpage#too-many400背景はドキュメント 1 つにつき 3 枚までです。
E:render.pdfpage#too-many-pixels400透明度またはインターレースを含む PNG で、画素数が 400 万を超えています — 全面の用紙は JPEG でアップロードしてください。
E:render.pdfpage.png#not-png400JPEG でも PNG でもありません。WebP・GIF・SVG は背景に使えません。
E:render.pdfpage.png#bad-idat400PNG が壊れています — 中の画像が、宣言している大きさと一致しません。
E:tpl.asset#not-found400その asset がありません。削除されたか、他のアカウントのものです。
E:pdf.op.file#not-pdf400アップロードされたものが PDF ではありません — ファイルが %PDF- で始まっていません。
E:pdf.op.file#not-found400そのファイルがありません。24 時間を過ぎて削除されたか、他のアカウントのものです。
E:pdf.op.file#too-large400アップロードされた PDF が 10MB を超えています。分割するか、先に軽くしてください。
E:pdf.op.merge#empty400files が空です。結合するものがありません。
E:pdf.op.merge#too-many400一度に結合できるのは 20 ファイルまでです。
E:pdf.op.stamp#not-latin1400その文字は標準フォントでは描けません(Latin-1 のみ)。
E:pdf.op.stamp#empty400text が空です。押すものがありません。
E:pdf.op.stamp#too-long400押す文字が 200 文字を超えています。
E:pdf.op.fields#empty400values が空です。入れるものがありません。
E:pdf.op.fields#too-many400一度に入力できるのは 500 欄までです。
E:pdf.op.fields#encrypted400パスワード保護された PDF はこちらで開けないため、欄に入力できません。
E:pdf.op.fields#xfa400これは XFA フォーム(Adobe LiveCycle)です。書き込んだ値はビューアに現れないため、「入力しました」と答えずにお断りします。組み込む前に GET /v1/files/{id} の xfa をご確認ください。
E:pdf.op.file#too-many-pages400PDF のページ数が、受け付けられる数(最大 100 ページ)を超えています。分割してアップロードしてください。
E:render.pdf.merge#too-many-pages400ファイルの合計が 100 ページを超えます。一度に結合する数を減らしてください。
E:api.template#not-found404そのテンプレートがありません。他のアカウントのものか、削除された可能性がありますが、この 2 つは区別してお伝えしません。
E:api.template#nothing-to-update400PUT に name も doc もありません。更新するものがありません。
E:tpl.crud#not-found404そのテンプレートがありません。他のアカウントのものか、削除された可能性がありますが、この 2 つは区別してお伝えしません。
E:tpl.version#not-found404このテンプレートに、そのバージョンはありません。
E:tpl.crud#invalid-doc400テンプレートのドキュメントがスキーマに違反しています。どこがどう違反したかを応答に並べます。
E:tpl.crud#missing-name400name が空です。テンプレートには名前が必要です。
E:pdf.op.fields#flatten-signature400署名欄のあるドキュメントは平坦化しません — 平坦化すると、署名する場所そのものがなくなるからです。
E:pdf.op.fields#flatten-failed400平坦化に失敗しました。何も書き込んでいませんので、flatten なしでもう一度お呼びください。
POST /v1/create
curl -X POST https://brewmypdf.com/v1/create \
  -H "x-api-key: bmp_live_..." \
  -H "content-type: application/json" \
  --data-binary @invoice.json
レスポンス
{
  "ok": true,
  "id": "01M0...",
  "format": "pdf",
  "url": "https://.../d/eyJ0...",
  "expiresAt": 1786884000
}