目次

開発者向け API リファレンス

FixDay の予約データを外部のシステムから読み書きするための HTTP API・Webhook・埋め込み・CSV エクスポートの仕様です。

はじめに

  • ベース URL: https://fixday.app/api/v1
  • リクエスト・レスポンスはすべて JSON(UTF-8)です。
  • 日時はすべて epoch ミリ秒(UTC)です。表示するときに利用者のタイムゾーンへ変換してください。
  • 公開 API はすべてのプラン(無料プランを含む)で使えます。
  • 機械可読な仕様(OpenAPI 3.1): /developers/openapi.json
まずはこれを試してください
curl https://fixday.app/api/v1/bookings \
  -H "Authorization: Bearer fdk_live_xxxxxxxxxxxxxxxxxxxx"

認証

管理画面(https://fixday.app/app)の「API キー」で[キーを発行]を押してキーを作り、Authorization ヘッダーに載せてください。 キーの発行・失効は、チームのオーナーと管理者が行えます。

ヘッダー
Authorization: Bearer fdk_live_xxxxxxxxxxxxxxxxxxxx

キーは発行時に一度しか表示されません。FixDay 側はハッシュしか保存していないため、 紛失したキーを再表示することはできません(新しいキーを発行し、古いキーを失効させてください)。

  • キーはチームに属します。作成した担当者が退職しても連携は止まりません。
  • キーで参照・操作できるのはそのチームのデータだけです。
  • URL のクエリ文字列(?api_key=)では受け付けません。URL はログや履歴に残るためです。
  • 失効させたキーは復活しません。止めたキーは確実に止まります。
  • キーはサーバー側に置き、ブラウザやアプリに埋め込まないでください。

バージョン

版はパスに含めます(/api/v1/...)。v1 では、返している項目を削ったり意味を変えたりしません。 項目が増えることはあるため、知らないキーは無視する実装にしてください。 互換性を壊す変更は /api/v2/ として並走させ、v1 は予告のうえで残します。

ページング

一覧(GET /api/v1/bookings)は limit(既定 50・最大 100)と offset で分けて取得します。 次のページがあるかは応答の hasMore で判断してください(総件数は返しません)。

全件を取得する例(JavaScript)
let offset = 0
for (;;) {
  const res = await fetch(`https://fixday.app/api/v1/bookings?limit=100&offset=${offset}`, {
    headers: { Authorization: `Bearer ${process.env.FIXDAY_API_KEY}` },
  })
  const page = await res.json()
  for (const b of page.bookings) handle(b)
  if (!page.hasMore) break
  offset += page.limit
}

取得の途中で予約が増えると、順番がずれて同じ予約が 2 回出ることがあります。予約の id で重複を除いてください。

レート制限

キーごとに 1 分あたり 60 リクエストです。応答には次のヘッダーが付きます。

  • X-RateLimit-Limit — 上限
  • X-RateLimit-Remaining — 残り
  • X-RateLimit-Reset — 枠が戻る時刻(epoch 秒)

超えると 429 と Retry-After(秒)を返します。 429 を受けたら、その秒数だけ待ってから再試行してください(即時リトライは状況を悪化させます)。

これとは別に、認証に失敗したリクエスト(無効・失効済みのキー)は、接続元の IP アドレスごとに 1 分あたり 120 回までです。 超えると、その接続元からのリクエストはキーの確認より前に 429 になります(Retry-After と、上限 120 の X-RateLimit-* が付きます)。 正しいキーのリクエストはこちらには数えないので、同じサーバーから複数のキーを使っても、キーごとの上限の内側なら制限されません。

エラー

エラーは常に次の形です。分岐には code を使ってください (message は日本語の説明文で、予告なく変わります)。

エラーの形
{
  "error": {
    "code": "not_found",
    "message": "予約が見つかりません"
  }
}
codeHTTP発生する場面
unauthorized401キーが未指定・不正・失効済み
invalid_request400パラメータの形式が不正(例: from / to が数値でない)
not_found404対象が無い、または他のチームのもの
rate_limited429レート制限を超えた
internal_error500サーバー側の失敗
plan_required402ご契約のプランで使えない機能(現在、公開 API はすべてのプランで使えるため通常は返りません)

他のチームのデータを指す ID には 404 を返します。存在の有無を区別しないためです。

エンドポイント

予約の項目のうち、status は pending・confirmed・cancelled・rescheduled・failed・no_show、 source(予約の経路)は public(予約ページ)・internal(社内)・api・poll(日程投票)のいずれかです。

GET /api/v1/event-types

調整ページの一覧 — 公開中(status が active)の調整ページを最大 100 件返します。社内メモは含みません。

パラメータはありません。

リクエストの例(curl)
curl https://fixday.app/api/v1/event-types \
  -H "Authorization: Bearer fdk_live_xxxxxxxxxxxxxxxxxxxx"
レスポンスの例(200)
{
  "eventTypes": [
    {
      "id": "01JAXQ3N4P5R6S7T8V9W0X1Y2Z",
      "slug": "30min",
      "title": "30分のオンライン相談",
      "description": "サービスの導入についてのご相談を承ります。",
      "durationMinutes": 30,
      "status": "active",
      "folderId": null,
      "createdAt": 1790000000000,
      "updatedAt": 1790500000000
    }
  ]
}

エラー: 401 ・ 429

GET /api/v1/event-types/{id}/slots

空き枠の取得 — 受付時間・カレンダーの予定・上限などを反映した、いま予約できる枠を返します。対象期間は調整ページの「予約を受け付ける期間(今日から何日先まで)」の範囲で、from / to はその中をさらに絞り込みます。

名前場所型説明
id必須pathstring調整ページの ID(一覧の id)
fromqueryintegerこの時刻以降に始まる枠だけ(epoch ミリ秒)
toqueryintegerこの時刻より前に始まる枠だけ(epoch ミリ秒)
リクエストの例(curl)
curl "https://fixday.app/api/v1/event-types/01JAXQ3N4P5R6S7T8V9W0X1Y2Z/slots?from=1791442800000" \
  -H "Authorization: Bearer fdk_live_xxxxxxxxxxxxxxxxxxxx"
レスポンスの例(200)
{
  "eventTypeId": "01JAXQ3N4P5R6S7T8V9W0X1Y2Z",
  "timezone": "Asia/Tokyo",
  "durationMinutes": 30,
  "slots": [
    {
      "start": 1791442800000,
      "end": 1791444600000
    },
    {
      "start": 1791444600000,
      "end": 1791446400000
    }
  ],
  "unavailableHostCount": 0
}

unavailableHostCount が 1 以上のときは、カレンダーを読めなかった・予定を登録できない担当者(連携が切れている・カレンダーを接続していない)がいて、候補が本来より少なく出ています。

エラー: 400 ・ 401 ・ 404 ・ 429

GET /api/v1/bookings

予約の一覧 — 開始日時の新しい順に返します。limit(既定 50・最大 100)と offset で分けて取得します。

名前場所型説明
fromqueryinteger開始日時がこの時刻以降(epoch ミリ秒)
toqueryinteger開始日時がこの時刻以前(epoch ミリ秒)
statusquerystring状態で絞り込む(pending / confirmed / cancelled / rescheduled / failed / no_show)
eventTypeIdquerystring調整ページで絞り込む
limitqueryinteger件数(1〜100。範囲外は丸めます)
offsetqueryinteger読み飛ばす件数(既定 0)
リクエストの例(curl)
curl "https://fixday.app/api/v1/bookings?status=confirmed&limit=50&offset=0" \
  -H "Authorization: Bearer fdk_live_xxxxxxxxxxxxxxxxxxxx"
レスポンスの例(200)
{
  "bookings": [
    {
      "id": "01JB2Y7Q8M3K4N5P6R7S8T9V0W",
      "eventTypeId": "01JAXQ3N4P5R6S7T8V9W0X1Y2Z",
      "status": "confirmed",
      "startAt": 1791442800000,
      "endAt": 1791444600000,
      "guestTimezone": "Asia/Tokyo",
      "guestName": "予約 一郎",
      "guestEmail": "ichiro@example.com",
      "guestPhone": null,
      "meetingUrl": "https://meet.google.com/abc-defg-hij",
      "source": "public",
      "cancelReason": null,
      "cancelledAt": null,
      "createdAt": 1791100000000,
      "updatedAt": 1791100000000
    }
  ],
  "hasMore": false,
  "limit": 50,
  "offset": 0
}

エラー: 401 ・ 429

GET /api/v1/bookings/{id}

予約の明細 — 予約 1 件の内容と、予約時の質問への回答(answers)を返します。

名前場所型説明
id必須pathstring予約の ID
リクエストの例(curl)
curl https://fixday.app/api/v1/bookings/01JB2Y7Q8M3K4N5P6R7S8T9V0W \
  -H "Authorization: Bearer fdk_live_xxxxxxxxxxxxxxxxxxxx"
レスポンスの例(200)
{
  "booking": {
    "id": "01JB2Y7Q8M3K4N5P6R7S8T9V0W",
    "eventTypeId": "01JAXQ3N4P5R6S7T8V9W0X1Y2Z",
    "status": "confirmed",
    "startAt": 1791442800000,
    "endAt": 1791444600000,
    "guestTimezone": "Asia/Tokyo",
    "guestName": "予約 一郎",
    "guestEmail": "ichiro@example.com",
    "guestPhone": null,
    "meetingUrl": "https://meet.google.com/abc-defg-hij",
    "source": "public",
    "cancelReason": null,
    "cancelledAt": null,
    "createdAt": 1791100000000,
    "updatedAt": 1791100000000,
    "answers": [
      {
        "label": "会社名",
        "value": "株式会社サンプル"
      }
    ]
  }
}

エラー: 401 ・ 404 ・ 429

POST /api/v1/bookings/{id}/cancel

予約のキャンセル — ゲストにキャンセルのメールが届き、カレンダーの予定も消えます。事前決済のある予約は、返金の決まりに沿って返金されます。すでにキャンセル済みの予約に対しても ok: true を返すので、何度呼んでもかまいません。

名前場所型説明
id必須pathstring予約の ID
reasonbody (JSON)stringキャンセルの理由(500 文字まで。超えた分は切り捨て)
リクエストの例(curl)
curl -X POST https://fixday.app/api/v1/bookings/01JB2Y7Q8M3K4N5P6R7S8T9V0W/cancel \
  -H "Authorization: Bearer fdk_live_xxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"reason":"先方のご都合によりキャンセル"}'
レスポンスの例(200)
{
  "ok": true,
  "orphanedEventCount": 0
}

orphanedEventCount が 1 以上のときは、カレンダーから消せなかった予定があります。お手数ですがカレンダー側で消してください。

エラー: 401 ・ 404 ・ 429 ・ 500

Webhook

予約が確定・変更・キャンセルされたときに、指定した URL へ JSON を POST します。 管理画面の「外部への通知」で[連携を追加]を押し、種類に webhook を選んで URL を登録してください(すべてのプランで使えます。登録できる数は無料プランで 2 件からプランに応じて増えます)。

  • URL は https のみです。社内ネットワークや IP アドレスを指す URL は登録できません。
  • リダイレクトは追いません。3xx を返すと届かなかった扱いになります。最終的な URL を登録してください。
  • 応答は 10 秒以内に返してください。重い処理は受け取ったあとで行ってください。
  • 登録すると署名鍵(whsec_ で始まる文字列)が一度だけ表示されます。控えておいてください。
  • 「テスト送信」で、架空の予約(booking.id が test)を 1 通送れます。

イベントと本文

event送られるとき
booking.confirmed予約が確定した(承認フローのある予約は承認されたとき)
booking.rescheduled予約の日程が変更された
booking.cancelled予約がキャンセルされた(承認の却下・期限切れを含む)
booking.reminder予約者へリマインドのメールを送った(調整ページで設定したリマインドの時刻ごとに 1 回)

管理画面から登録した Webhook は、booking.confirmed・booking.rescheduled・booking.cancelled の 3 つを受け取ります。 登録の途中で「リマインドのメールを送ったときも知らせますか?」に OK を押すと、booking.reminder も受け取ります(予約者へリマインドのメールを送ったあとに、設定したリマインドの時刻ごとに 1 回)。 booking.reminder の本文には、何分前のリマインドかを表す reminderMinutes(1440 なら 24 時間前。分からない古いリマインドでは null)が付きます。配信 ID(id)はリマインド 1 件ごとに決まった値なので、同じリマインドが 2 回届いても id で重複を除けます。 承認の却下や期限切れによるキャンセルも booking.cancelled で届き、booking.approvalState(rejected / expired)で区別できます。 日程変更では、新しい予約として作り直すため、元の予約の booking.cancelled(cancelReason に日程変更である旨が入ります)と、新しい予約の booking.rescheduled の 2 つが届きます(届く順番は前後することがあります)。

ヘッダー:

  • X-FixDay-Signature — 署名(下記)
  • X-FixDay-Event — イベント名(本文を読む前に振り分けられます)
  • X-FixDay-Delivery — 配信 ID。再送しても変わりません(本文の id と同じ)
  • User-Agent — FixDay-Webhook/1.0
本文の例(booking.confirmed)
{
  "id": "01JB2Y9ZQ0DELIVERY00000000",
  "event": "booking.confirmed",
  "occurredAt": 1791100000000,
  "teamId": "01JAT0TEAM000000000000000",
  "teamName": "株式会社サンプル",
  "booking": {
    "id": "01JB2Y7Q8M3K4N5P6R7S8T9V0W",
    "eventTypeId": "01JAXQ3N4P5R6S7T8V9W0X1Y2Z",
    "eventTitle": "30分のオンライン相談",
    "status": "confirmed",
    "approvalState": null,
    "startAt": 1791442800000,
    "endAt": 1791444600000,
    "timezone": "Asia/Tokyo",
    "guestName": "予約 一郎",
    "guestEmail": "ichiro@example.com",
    "hostNames": [
      "見本 太郎"
    ],
    "meetingUrl": "https://meet.google.com/abc-defg-hij",
    "manageUrl": "https://fixday.app/b/abcdefghij",
    "cancelReason": null
  }
}
本文の例(booking.reminder)
{
  "id": "reminder_01JB2YJOB000000000000000.01JAT0INTEGRATION0000000",
  "event": "booking.reminder",
  "occurredAt": 1791100000000,
  "teamId": "01JAT0TEAM000000000000000",
  "teamName": "株式会社サンプル",
  "booking": {
    "id": "01JB2Y7Q8M3K4N5P6R7S8T9V0W",
    "eventTypeId": "01JAXQ3N4P5R6S7T8V9W0X1Y2Z",
    "eventTitle": "30分のオンライン相談",
    "status": "confirmed",
    "approvalState": null,
    "startAt": 1791442800000,
    "endAt": 1791444600000,
    "timezone": "Asia/Tokyo",
    "guestName": "予約 一郎",
    "guestEmail": "ichiro@example.com",
    "hostNames": [
      "見本 太郎"
    ],
    "meetingUrl": "https://meet.google.com/abc-defg-hij",
    "manageUrl": "https://fixday.app/b/abcdefghij",
    "cancelReason": null
  },
  "reminderMinutes": 1440
}

日時の表示用のタイムゾーン(booking.timezone)はチームのものです。manageUrl はゲストが予約を変更・キャンセルするための URL で、ゲスト本人以外に渡さないでください。

署名の検証

X-FixDay-Signature: t=<UNIX 秒>,v1=<16 進の HMAC-SHA256> の形です。 「t の値」「.」「本文そのもの」をつないだ文字列を、署名鍵(whsec_ を含めた全体)で HMAC-SHA256 した値が v1 です(Stripe と同じ方式)。

  • 本文は受け取ったままの文字列で検証してください。JSON を解釈してから文字列に戻すと一致しません。
  • t が 300 秒より古い(または未来の)ものは捨ててください。盗まれた本文の再送を防げます。
  • 比較は定数時間で行ってください(下の例の timingSafeEqual / compare_digest)。
Node.js
import crypto from 'node:crypto'

// rawBody は JSON.parse する前の本文(文字列)。parse してから文字列に戻すと一致しません
export function verifyFixDaySignature(rawBody, header, secret, toleranceSec = 300) {
  let t = null
  const sigs = []
  for (const part of (header || '').split(',')) {
    const [k, v] = part.split('=')
    if (k?.trim() === 't') t = v
    if (k?.trim() === 'v1' && v) sigs.push(v.trim())
  }
  if (!t || sigs.length === 0) return false
  // 古い署名を弾く(盗んだ本文の再送を防ぐ)
  if (Math.abs(Date.now() / 1000 - Number(t)) > toleranceSec) return false
  const expected = crypto.createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex')
  return sigs.some((s) => s.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)))
}

// Express の例: 本文を文字列のまま受け取る
app.post('/fixday-webhook', express.text({ type: 'application/json' }), (req, res) => {
  if (!verifyFixDaySignature(req.body, req.get('X-FixDay-Signature'), process.env.FIXDAY_WEBHOOK_SECRET)) {
    return res.status(400).end()
  }
  const event = JSON.parse(req.body)
  // event.id(= X-FixDay-Delivery)で重複を除いてから処理する
  res.status(200).end()
})
Python
import hashlib, hmac, time

def verify_fixday_signature(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    t, sigs = None, []
    for part in (header or '').split(','):
        k, _, v = part.partition('=')
        if k.strip() == 't':
            t = v.strip()
        elif k.strip() == 'v1' and v:
            sigs.append(v.strip())
    if not t or not sigs:
        return False
    if abs(time.time() - int(t)) > tolerance:
        return False
    expected = hmac.new(secret.encode(), t.encode() + b'.' + raw_body, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(s, expected) for s in sigs)

再送と自動停止

  • 2xx 以外の応答・タイムアウト・接続失敗は失敗として、間隔を空けて送り直します(30 秒 → 1 分 → 2 分 → 4 分。最初の送信を含めて最大 5 回)。
  • 同じイベントが 2 回以上届くことがあります。X-FixDay-Delivery で重複を除いてください。
  • 届く順番は発生順と前後することがあります。occurredAt で並べ直してください。
  • 送り直すのは、送信を試みたあとの失敗だけです。予約の確定・変更・キャンセル・リマインドのどれでも、FixDay 側で送信待ちに積むこと自体に失敗したイベント(まれです)は送り直しません。booking.reminder の場合、リマインドのメールは届いていても Webhook は届かないことになります。取りこぼしが困る用途では、公開 API の予約一覧と定期的に突き合わせてください。
  • 5 回とも失敗したイベントが 3 件続くと、その連携は自動で停止し、管理画面に理由が表示されます。直したら画面から再開してください。

Slack・Chatwork・Microsoft Teams への通知は、同じ画面から種類を選ぶだけで使えます(署名の検証は不要です)。

埋め込み

予約ページを、自社のウェブサイトの中にそのまま表示できます。次の 2 行を HTML に貼り付けてください(すべてのプランで使えます)。

埋め込みコード(チーム名/30min を置き換える)
<div data-fixday-url="https://fixday.app/チーム名/30min/embed"></div>
<script src="https://fixday.app/embed.js" async></script>
  • data-fixday-url には、調整ページの URL の末尾に /embed を付けたものを指定します(https://fixday.app/チーム名/調整ページ/embed)。
  • embed.js が iframe を作り、中身に合わせて高さを自動で変えます(日時の選択 → 入力 → 完了で高さが変わっても、切れたり空白が残ったりしません)。
  • 同じページに複数の data-fixday-url を置けます。
  • 高さの自動調整は https://fixday.app の URL で埋め込んだときに働きます。

CSV エクスポート

予約の一覧を CSV で取得できます。管理画面にログインした状態で利用します(API キーではありません)。 チームのオーナー・管理者など、調整ページを編集できる人が使えます(Solo 以上のプラン)。

ブラウザで開く URL
https://fixday.app/api/export/bookings.csv?from=2026-01-01&to=2026-03-31
  • 期間(from / to)は YYYY-MM-DD か epoch ミリ秒。省略時は直近 90 日、指定できる幅は最長 366 日です。
  • status と eventTypeId で絞り込めます。
  • 文字コードは UTF-8(BOM 付き)。Excel でそのまま開けます。
  • 日時はチームのタイムゾーンでの表示に加えて、UTC の ISO 形式の列も入れてあります。
  • 列: 予約ID、ステータス、調整ページ、開始日、開始時刻、終了時刻、タイムゾーン、開始(UTC/ISO)、ゲスト名、メールアドレス、電話番号、経路、オンライン会議URL、キャンセル理由、キャンセル日時(UTC/ISO)、作成日時(UTC/ISO)

先頭が = + - @ のセルには ' を付けています。 表計算ソフトが値を数式として実行してしまう問題(CSV インジェクション)を防ぐためで、 記入された内容そのものは変えていません。

OpenAPI

このページと同じ内容を、OpenAPI 3.1 の形式で配信しています。クライアントの生成や、API ツールへの読み込みにお使いください。

取得
curl https://fixday.app/developers/openapi.json

Webhook の本文の形は、OpenAPI 3.1 の webhooks に載せています。

ご利用条件

  • 公開 API … すべてのプラン(無料プランを含む)
  • Webhook・Slack・Chatwork・Teams への通知 … すべてのプラン(登録できる数はプランによります)
  • 埋め込み … すべてのプラン
  • CSV エクスポート … Solo 以上

キーの発行・失効はチームの管理者(オーナー・管理者)が行えます。

仕様についてのご質問は お問い合わせフォーム からご連絡ください。 使い方は ヘルプセンター にまとめています。