GraphQL API

概要

TesseraのGraphQL APIは、コンテンツタイプ定義(スキーマ)から実行時に自動導出されます。ビルド時に固定されたスキーマではなく、コンテンツタイプの追加・変更が承認されるとすぐに新しいフィールド・型がクエリ可能になります。記事とその著者のように関連付けられたコンテンツ(リレーション)も、複数回のAPIコールを重ねることなく1リクエストで取得できます。

コンテンツのCRUD(作成・更新・アーカイブ)にも対応しています。コンテンツタイプ自体のスキーマ変更はMutationの対象外で、引き続きAI提案→人間の承認という既存フローのみで行います。

クイックスタート

GraphQL Playground(GraphiQL)は/graphqlからアクセスできます。ログイン中のセッションでそのまま実行でき、発行済みのAPIキーで試したい場合はHeadersタブにAuthorization: Bearer YOUR_API_KEYを入力してください。

外部から利用する場合のエンドポイントはhttps://YOUR_WORKSPACE.tesseracms.com/graphqlです。

query {
  articles {
    id
    title
    body
    author {
      id
      name
    }
  }
}

実例

基本クエリ

query {
  articles(limit: 20, sort: CREATED_AT_DESC) {
    id
    title
  }
  articlesCount
}

limitのデフォルトは20件、上限は100件です(上限超過時の挙動は後述のエラーケース参照)。<apiId>sCountは同じ絞り込み条件での件数のみを軽量に取得できるフィールドです。

リレーション込みクエリ

query {
  articles {
    id
    title
    author {
      id
      name
    }
  }
}

reference型フィールドは自動的に関連するオブジェクト型として解決されます。逆方向(例: 著者から見た「この著者が書いた記事一覧」)も、relation.inverseFieldApiIdが設定されていれば自動的にリスト型フィールドとして追加されます。

filter引数による絞り込み

filter引数は、select型・boolean型・reference型(cardinality: one、単一参照のみ)のフィールドに限り、完全一致での絞り込みに対応しています。対象フィールドを1件も持たないコンテンツタイプには、このfilter引数自体が生成されません。

query {
  articles(filter: { category: "tech", isPublishedExternally: true }) {
    id
    title
  }
  articlesCount(filter: { category: "tech" })
}

部分一致のテキスト検索や数値の範囲検索には現時点で対応していません(バックログ扱いです)。

Mutation(作成・更新・アーカイブ)

mutation {
  createArticle(input: { title: "はじめての記事", body: "本文", author: "AUTHOR_ENTRY_ID" }, status: DRAFT) {
    id
    status
  }
}

mutation {
  updateArticle(id: "ENTRY_ID", input: { title: "更新後のタイトル" }) {
    id
    updatedAt
  }
}

mutation {
  archiveArticle(id: "ENTRY_ID") {
    id
    status
  }
}

create<TypeName>/update<TypeName>/archive<TypeName>はコンテンツタイプごとに自動生成されます。物理削除(完全削除)はGraphQL Mutationの対象外で、管理画面限定です。

エラーケース

フィルタ対象外のフィールドを指定した場合

filterの対象になるのはselect/boolean/reference(one)型のフィールドのみです。対象外のフィールド(text/number型や、cardinality: manyのreference型など)をfilterに指定しようとすると、そのフィールドは生成された<TypeName>FilterInput型に存在しないため、クエリ実行前のバリデーション段階で標準的なGraphQLエラー(未知の入力フィールド)として拒否されます。

クエリ複雑度の上限超過

reference/inverse/media解決系のフィールドには、fan-out(1件から複数件を辿ることによる爆発的な計算量増加)を防ぐための複雑度コストが設定されています。合計スコアが上限(現在50,000)を超えるクエリは、以下のようなエラーで拒否されます。

Query complexity of 68000 exceeds maximum allowed complexity of 50000

深いネストのクエリ自体も、現状はクエリ深度制限(最大6階層、暫定値)で別途制限されています。

双方向リレーションの循環について(バグではなく仕様)

例えば「記事→著者→その著者の記事一覧→各記事の著者→…」のように、reference/inverseのリレーションを行き来するクエリは、深度・複雑度の上限内であれば実行できますが、逆方向フィールド(一覧側)は常に一定件数(現在20件)で打ち切られます。これは無制限のfan-outを防ぐための意図的な設計で、「もっと多く返らない」という挙動は不具合ではありません。

その他のエラー

  • limitが100件を超える場合: クランプせずBAD_USER_INPUTで明確に拒否します。
  • 書き込み権限が不足している場合: FORBIDDEN(APIキーのスコープがread_only、または対象コンテンツタイプが個別に読み取り専用扱いにオーバーライドされている場合)
  • ワークスペースのコンテンツ数上限に達している場合: ENTRY_LIMIT_EXCEEDED(プラン名・上限値を含む)
  • unique制約のあるフィールドで値が重複した場合: UNIQUE_CONSTRAINT_VIOLATION
  • 存在しないコンテンツIDを指定した更新・アーカイブ: NOT_FOUND
  • 単体型(シングルトン)コンテンツタイプへのアーカイブ操作: SINGLETON_ARCHIVE_NOT_ALLOWED(単体型はdraft/publishedの間のみ遷移し、アーカイブには対応していません)
  • 存在しない形式のID(UUID形式でない値)を指定した場合: エラーにはならず、単に「該当コンテンツなし」として null が返ります。

コスト・利用上限

GraphQLのクエリ・Mutationの実行自体はAIクレジットを消費しません(AI APIを呼び出さないため)。回数ベースのレート制限は、クエリ(読み取り)とMutation(書き込み)で別軸です。

操作プラン1時間あたり1日あたり
Query(読み取り)Free300回2,000回
Query(読み取り)Paid1,200回10,000回
Mutation(書き込み)Free30回200回
Mutation(書き込み)Paid120回1,000回

読み取り系機能自体(GraphQL・MCP問わず)はFreeプランでも制限なく開放されています。上記はあくまで「回数」の上限で、「機能」自体を制限するものではありません。

クエリ複雑度の上限(50,000)はプランに関わらず共通です。