genko.meDocs
Frameworks and languagesRuby

Get started

Use Ruby's standard net/http library to fetch a list and a single entry from genko.me and display them.

This guide connects Ruby to the genko.me blog API and fetches a list and a single entry. It uses only the standard net/http, json, and uri libraries.

Prerequisites

  • A genko.me account and workspace. You need the Owner role to create an API and issue an API key.
  • Ruby 3.1 or later. The typed example with Data.define requires Ruby 3.2 or later. No extra gems are needed.
  • One list API and one published entry. If needed, create them in steps 2 and 3 of the quickstart.

Replace your-workspace with your workspace ID and blog with your endpoint throughout this guide.

Install

Nothing to install. You do not need another Gemfile dependency. Add the following code to your project.

Configure an API key and workspace ID

If your dashboard URL is https://genko.me/w/your-workspace, your workspace ID is your-workspace.

Create an API key in Settings and plan → API keys (/w/your-workspace/settings/api-keys). Scope it to blog under Selected APIs only to limit exposure if the key leaks. You cannot view its value again after creation.

Read the key from an environment variable. In Rails, you can use Rails.application.credentials instead.

ruby
ENV.fetch("GENKO_API_KEY")

ENV.fetch raises if the variable is missing. With ENV["GENKO_API_KEY"], you would send nil and get a 401, so use fetch. Do not commit the key.

Write a client

Put delivery API GET requests in one place.

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
  # Send a GET request to the delivery API and return a Hash with symbol keys.  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] || "Unknown error.")    end
    body  endend

URI.encode_www_form URL-encodes brackets in filters and non-ASCII values. Do not concatenate a query string yourself.

queries.compact drops nil values, so pass nil for any omitted query.

Show a list

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

The response is a Hash with :contents, :totalCount, :offset, and :limit. When no entries match, :contents is an empty array rather than an error.

Escape text fields before passing them to ERB. The delivery API returns text without escaping it. Rails templates escape by default; with plain ERB, call ERB::Util.html_escape yourself.

Map entries to types

With a plain Hash, a mistyped field ID may go unnoticed. Define a Data class matching your schema so accessing an unknown field raises NoMethodError.

lib/post.rb
# frozen_string_literal: true
require "time"
# Data.define requires Ruby 3.2 or later. On 3.1, use 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] || "",      # Optional fields and fields added later may be absent from older entries.      category: entry[:category],      tags: entry[:tags] || [],      published_at: entry[:publishedAt] && Time.iso8601(entry[:publishedAt])    )  endend

Delivery API keys are camelCase (publishedAt). If you want snake_case in Ruby, convert them in this mapping.

When you narrow the response with fields, unspecified fields are omitted. Keep defaults such as || "" and || [].

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

Show a single entry

Append an id from the list to the path.

ruby
require "erb" # For ERB::Util.url_encode; unnecessary in 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 "Entry not found."else  puts post.title  puts post.published_at&.getlocal("+09:00")&.strftime("%B %-d, %Y")  puts post.body # Rich text is an HTML stringend

Unlike a list response, a detail response is the entry Hash itself, without a :contents wrapper. Unpublished, deleted, and nonexistent IDs all return 404.

Dates arrive as ISO 8601 strings in UTC. Convert them to Japan time before display.

Rich text (body) is an HTML string, so output it as HTML. In a Rails view, use raw or html_safe. It displays content entered in the dashboard; give editing access only to trusted authors. If your site accepts external submissions, sanitize it with Sanitize or a similar library before output.

Filter entries

filters combines a field ID and an operator in a string.

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)}")

See the query reference for the operators and how to combine conditions with [and] and [or].

In a Rails app

Cache results instead of calling the API directly from a controller.

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

Include the query in the cache key so different queries have different keys. To reflect updates quickly, invalidate with a webhook instead of shortening the TTL.

Handle errors

StatusLikely cause
400Invalid query format, value, or operator
401Missing, invalid, or expired API key
403Key and workspace do not match
404Workspace, API, or entry does not exist, or is outside the key's scope
429More than 300 requests per minute with the same API key

Genko::ApiError#status contains the HTTP status. See Errors and limits for details.

Next steps

Continue with the tutorial for pagination, previewing saved drafts, and webhook signature verification.

Last updated: October 6, 2026

On this page