genko.meDocs

導入

Rubyでgenko.meの配信APIに標準添付のnet/httpで接続し、記事一覧と記事詳細を取得して表示するまでの手順。

このページでは、Rubyからgenko.meのblogAPIに接続し、記事一覧と記事詳細を取得します。使うのは標準添付のnet/http・json・uriだけです。

用意するもの

  • genko.meのアカウントとワークスペース。APIの作成とAPIキーの発行にはオーナー権限が必要です。
  • Ruby 3.1以降。Data.defineを使う型付きの受け取り方だけはRuby 3.2以降が必要です。追加のgemは要りません。
  • リスト形式のAPIが1つと、公開済みの記事が1件。まだない場合はクイックスタートの手順2・3で作れます。

以降のyour-workspaceは自分のワークスペースIDに、blogは自分のエンドポイントに置き換えます。

インストール

ありません。 Gemfileに足すものはなく、以下のコードをプロジェクトに置くだけです。

APIキーとワークスペースIDを設定する

管理画面のURLがhttps://genko.me/w/your-workspaceなら、ワークスペースIDはyour-workspaceです。

APIキーは「設定・プラン」の「APIキー」タブ(/w/your-workspace/settings/api-keys)で作成します。「選択したAPIのみ」でblogだけを許可しておくと、キーが漏れたときの範囲を限定できます。作成直後に表示される値は後から再表示できません。

キーは環境変数から読みます。RailsならRails.application.credentialsでも構いません。

ruby
ENV.fetch("GENKO_API_KEY")

ENV.fetchは未設定のときに例外になります。ENV["GENKO_API_KEY"]だとnilのまま401を受け取ることになるので、fetchを使ってください。キーをリポジトリに含めないでください。

クライアントを書く

配信APIへのGETを1か所にまとめます。

lib/genko.rb
# frozen_string_literal: true
require "json"require "net/http"require "uri"
module Genko  BASE_URL = "https://api.genko.me"  WORKSPACE = "your-workspace"
  class ApiError < StandardError    attr_reader :status
    def initialize(status, message)      @status = status      super(message)    end  end
  # 配信APIをGETして、シンボルキーのHashで返す。  def self.get(path, **queries)    uri = URI("#{BASE_URL}/v1/#{WORKSPACE}#{path}")    queries = queries.compact    uri.query = URI.encode_www_form(queries) unless queries.empty?
    request = Net::HTTP::Get.new(uri)    request["X-API-KEY"] = ENV.fetch("GENKO_API_KEY")
    response = Net::HTTP.start(      uri.hostname, uri.port,      use_ssl: true, open_timeout: 10, read_timeout: 10    ) { |http| http.request(request) }
    body = JSON.parse(response.body, symbolize_names: true)    unless response.is_a?(Net::HTTPSuccess)      raise ApiError.new(response.code.to_i, body[:message] || "原因不明のエラーです。")    end
    body  endend

URI.encode_www_formを使うと、filtersの角括弧や日本語の値がURLエンコードされます。クエリ文字列を自分で連結しないでください。

queries.compactでnilの値を落としているので、指定しないクエリはnilを渡すだけで済みます。

記事一覧を出す

ruby
list = Genko.get("/blog", limit: 10, orders: "-publishedAt", fields: "title,publishedAt")
puts "全#{list[:totalCount]}件"list[:contents].each do |post|  puts "#{post[:id]} #{post[:title]}"end

レスポンスは:contents・:totalCount・:offset・:limitを持つHashです。該当する記事がない場合もエラーにはならず、:contentsが空配列になります。

ERBに渡すときは、テキストフィールドをエスケープしてください。配信APIはテキストをエスケープせずに返します。Railsのテンプレートは既定でエスケープしますが、素のERBではERB::Util.html_escapeを自分で通します。

型を付けて受け取る

Hashのまま扱うとフィールドIDの打ち間違いに気づけません。スキーマに合わせてDataクラスを1つ書いておくと、存在しない項目を読んだ時点でNoMethodErrorになります。

lib/post.rb
# frozen_string_literal: true
require "time"
# Data.define は Ruby 3.2 以降。3.1 では Struct.new(..., keyword_init: true) を使う。Post = Data.define(:id, :title, :body, :category, :tags, :published_at) do  def self.from_entry(entry)    new(      id: entry[:id],      title: entry[:title] || "",      body: entry[:body] || "",      # 任意項目や、あとから追加したフィールドは古い記事に無いことがある。      category: entry[:category],      tags: entry[:tags] || [],      published_at: entry[:publishedAt] && Time.iso8601(entry[:publishedAt])    )  endend

配信APIのキーはキャメルケース(publishedAt)です。Ruby側でスネークケースにするなら、この変換の場所で揃えます。

fieldsで項目を絞ると、指定しなかったフィールドはレスポンスに含まれません。既定値の|| ""や|| []を省略しないでください。

ruby
posts = list[:contents].map { |entry| Post.from_entry(entry) }

記事詳細を出す

一覧で得たidをパスの末尾に付けます。

ruby
require "erb" # ERB::Util.url_encode。Railsでは不要
def find_post(id)  Post.from_entry(Genko.get("/blog/#{ERB::Util.url_encode(id)}"))rescue Genko::ApiError => e  raise unless e.status == 404
  nilend
post = find_post("your-entry-id")if post.nil?  puts "記事が見つかりません。"else  puts post.title  puts post.published_at&.getlocal("+09:00")&.strftime("%Y年%-m月%-d日")  puts post.body # リッチテキストはHTML文字列end

一覧と違い、詳細のレスポンスは:contentsで包まれず、コンテンツのHashがそのまま返ります。非公開・削除済み・存在しないIDはどれも404です。

日時はISO 8601のUTC文字列で届きます。表示前に日本時間へ変換してください。

リッチテキスト(body)はHTML文字列なので、そのまま出力します。Railsのビューではrawやhtml_safeが必要です。管理画面で入力された内容がそのまま出るため、信頼できる書き手だけに編集権限を渡してください。 外部から投稿を受け付ける構成では、Sanitizeなどで整えてから出力します。

絞り込む

filtersはフィールドIDと演算子を組み合わせた文字列です。

ruby
tech = Genko.get("/blog", filters: "category[equals]tech", orders: "-publishedAt", limit: 10)
recent = Genko.get("/blog", filters: "publishedAt[greater_than]#{(Time.now - 30 * 24 * 60 * 60).utc.iso8601(3)}")

演算子の一覧と、[and]/[or]での連結はクエリ仕様を参照してください。

Railsに置く場合

コントローラから直接呼ばず、キャッシュを挟みます。

app/models/blog_post.rb
class BlogPost  CACHE_TTL = 5.minutes
  def self.recent(limit: 10)    Rails.cache.fetch("genko/blog/recent/#{limit}", expires_in: CACHE_TTL) do      Genko.get("/blog", limit: limit, orders: "-publishedAt")[:contents]        .map { |entry| Post.from_entry(entry) }    end  endend

キャッシュのキーは、クエリが変わればキーも変わるように組み立てます。更新を早く反映したい場合は、TTLを短くするのではなくWebhookで破棄してください。

エラーの扱い

ステータス主な原因
400クエリの形式・値・演算子が不正
401APIキーがない、無効、期限切れ
403キーとワークスペースが一致しない
404ワークスペース・API・コンテンツがない、またはキーの対象外
429同じAPIキーで1分300リクエストを超えた

Genko::ApiError#statusにHTTPステータスが入ります。詳しくはエラーと制限にまとめています。

次に読む

ページ送り、保存済み下書きのプレビュー、Webhookの署名検証は実践に続きます。

最終更新: 2026年9月17日

目次