catalog-beer

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Catalog.beer API

Catalog.beer API

Catalog.beer is an open database of 6,700+ breweries and 60,000+ beers with a curated, versioned style taxonomy. The API is plain REST + JSON.
  • Base URL:
    https://api.catalog.beer
    (HTTPS required — plain HTTP fails)
  • Send
    Accept: application/json
    on every request
  • Send
    Content-Type: application/json
    on POST/PUT/PATCH
  • Full API docs: https://catalog.beer/api-docs
This skill is always served current at https://catalog.beer/skills/catalog-beer/SKILL.md. Copies installed from a zip don't update themselves — if the
updated
date above is months old, or the API rejects a request in a way these instructions don't explain, fetch that URL and follow it instead of this copy.
Catalog.beer是一个包含6700+家酿酒厂和60000+款啤酒的开放数据库,拥有经过精心整理的版本化风格分类体系。该API为纯REST+JSON格式。
  • 基础URL:
    https://api.catalog.beer
    (必须使用HTTPS——纯HTTP请求会失败)
  • 所有请求需携带
    Accept: application/json
    头部
  • POST/PUT/PATCH请求需携带
    Content-Type: application/json
    头部
  • 完整API文档:https://catalog.beer/api-docs

Authentication

认证方式

HTTP Basic auth. The API key is the username; the password is blank.
bash
curl https://api.catalog.beer/beer/search?q=pliny \
  -u "$CATALOG_BEER_API_KEY:" \
  -H "Accept: application/json"
Getting a key (there is no signup API — accounts are created on the website):
  1. The user creates a free account at https://catalog.beer/signup
  2. They verify their email address (the key is
    null
    until verified)
  3. The key is shown at https://catalog.beer/account
Ask the user for their key and read it from the
CATALOG_BEER_API_KEY
environment variable. Never hardcode or commit it. Keys include 1,000 free requests/month; past that, usage bills at $1 per 1,000 requests only if the user has added a payment method at https://catalog.beer/billing — otherwise a
402
ends the month's access.
GET /usage/my-usage
and
GET /billing
report status without counting against the limit. See
references/api-basics.md
→ "Usage limits & billing" — and never call the billing endpoints (checkout, spend cap, disable) unless the user explicitly asks; they spend the user's money.
采用HTTP基本认证。API密钥作为用户名,密码留空。
bash
curl https://api.catalog.beer/beer/search?q=pliny \
  -u "$CATALOG_BEER_API_KEY:" \
  -H "Accept: application/json"
获取密钥(暂无注册API——需通过网站创建账户):
  1. 用户在https://catalog.beer/signup创建免费账户
  2. 验证邮箱地址(验证前密钥为
    null
  3. 可在https://catalog.beer/account页面查看密钥
请向用户索要密钥,并从
CATALOG_BEER_API_KEY
环境变量读取。切勿硬编码或提交密钥。密钥每月包含1000次免费请求;超出后,每1000次请求收费1美元**仅当用户已在https://catalog.beer/billing添加支付方式**——否则当月访问会因`402`错误终止。`GET /usage/my-usage
GET /billing
接口可查询状态且不计入请求限额。请查阅
references/api-basics.md` → "使用限额与计费"——除非用户明确要求,否则切勿调用计费相关端点(结账、消费上限、禁用),这些操作会产生费用。

The contribution rules (non-negotiable)

贡献规则(不可协商)

Catalog.beer's value is that its data is verifiable. Contributions must follow these rules:
  1. No source, no write. Only submit facts you have actually verified — fetched from the brewery's own website, or read by the user off the can, label, or menu in front of them. Never fill in ABV, IBU, style, or any other fact from your own knowledge or memory, no matter how confident you are. If a required fact (like ABV) can't be found, ask the user — don't guess.
  2. Search before you create. Always call
    GET /brewer/search
    and
    GET /beer/search
    before POSTing. Names vary ("Russian River Brewing Company" vs "Russian River") — search by the distinctive part of the name and check the results before concluding something is missing. If the entity exists, update it (PATCH) instead of creating a duplicate.
  3. Prefer PATCH for edits.
    PUT
    is a full replacement: any optional field you omit is cleared to null. Only use PUT when you intend to replace the whole record.
  4. Never DELETE without the user explicitly asking for that specific deletion. Deleting a brewer cascades to its beers and locations.
  5. When unsure about style, be less specific. Filing a beer as family
    ipa
    is correct; guessing
    west-coast-ipa
    when the brewery just says "IPA" is wrong. See "Classifying styles" below.
  6. Read the reference before your first write to an entity type. Before your first write to a brewer, beer, or location, read that entity's file in
    references/
    (plus
    references/api-basics.md
    once per session). The examples in this file show request shape, not the full contract — field limits, clearing semantics, and edge cases live in the references.
Catalog.beer的价值在于其数据的可验证性。贡献必须遵循以下规则:
  1. 无来源,不写入。仅提交你已实际验证的事实——来自酿酒厂官网,或用户当面从罐身、标签、菜单上读取的信息。绝不要凭自身知识或记忆填写ABV、IBU、风格或其他任何事实,无论你有多确定。如果必填事实(如ABV)无法找到,请询问用户——不要猜测。
  2. 创建前先搜索。在发送POST请求前,务必调用
    GET /brewer/search
    GET /beer/search
    。名称可能存在变体(如"Russian River Brewing Company"与"Russian River")——使用名称中的独特部分搜索并检查结果,再判断是否存在重复。如果实体已存在,请使用PATCH更新而非创建重复项。
  3. 编辑优先使用PATCH
    PUT
    是全量替换:任何你省略的可选字段都会被清空为null。仅当你打算替换整条记录时才使用PUT。
  4. 除非用户明确要求删除特定内容,否则切勿执行DELETE。删除酿酒厂会级联删除其关联的啤酒和位置信息。
  5. 对风格不确定时,选择更宽泛的分类。将啤酒归类为
    ipa
    家族是正确的;但如果酿酒厂仅标注"IPA",却猜测归类为
    west-coast-ipa
    则是错误的。请参阅下方的「风格分类」部分。
  6. 首次写入某类实体前,请阅读参考文档。在首次写入酿酒厂、啤酒或位置信息前,请阅读
    references/
    目录下对应实体的文档(每次会话需阅读一次
    references/api-basics.md
    )。本文件中的示例仅展示请求结构,而非完整契约——字段限制、清空规则和边缘情况均在参考文档中说明。

Workflow: add a beer the user is drinking

工作流:添加用户正在饮用的啤酒

The most common task. Order of operations:
1. GET /brewer/search?q={brewery name}     → does the brewery exist?
2. If not: fetch the brewery's website, then POST /brewer
3. GET /brewer/{brewer_id}/beer            → does the beer exist?
4. If not: find the beer on the brewery's site, then POST /beer
5. (Optional) POST /location + POST /address/{location_id} for taprooms
Step 2 — create the brewery (verify name/URL/description from their site).
short_description
is a subtitle shown in search results and is limited to 160 characters — exceeding it returns a 400.
url
is fetched live by the API and rejects far more than bad syntax — see "The URL field bites" below:
bash
curl -X POST https://api.catalog.beer/brewer \
  -u "$CATALOG_BEER_API_KEY:" \
  -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{
    "name": "Alibi Brewing",
    "url": "https://alibi.beer",
    "short_description": "Neighborhood brewery in ..."
  }'
Step 4 — create the beer (
brewer_id
,
name
,
abv
, and a style are required;
abv
is a float
0
99.9
stored rounded to one decimal place,
ibu
an optional whole number
0
1000
). Send the brewery's exact figure and let the API round it — and when a label publishes a bound instead of a number ("Less than 0.5% ABV"), record the bound (
0.5
). Omit
ibu
when the brewery doesn't publish one:
0
means the beer has no measurable bitterness, not that you don't know. See
references/beers.md
→ "Recording ABV" and "Recording IBU":
bash
curl -X POST https://api.catalog.beer/beer \
  -u "$CATALOG_BEER_API_KEY:" \
  -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{
    "brewer_id": "c1a5b1f0-...",
    "name": "Kölsch",
    "style": "Kölsch",
    "abv": 4.8,
    "description": "..."
  }'
Successful creates return the full object including its
id
. Report the new catalog.beer URL to the user:
https://catalog.beer/beer/{id}
.
这是最常见的任务。操作顺序如下:
1. GET /brewer/search?q={酿酒厂名称}     → 该酿酒厂是否已存在?
2. 若不存在:获取酿酒厂官网信息,然后调用POST /brewer
3. GET /brewer/{brewer_id}/beer            → 该啤酒是否已存在?
4. 若不存在:在酿酒厂官网找到该啤酒信息,然后调用POST /beer
5. (可选)调用POST /location + POST /address/{location_id}添加品酒室信息
步骤2——创建酿酒厂(需从其官网验证名称/URL/描述信息)。
short_description
是显示在搜索结果中的副标题,长度限制为160字符——超出会返回400错误。
url
会被API实时抓取验证,其限制远不止语法错误——请参阅下方的「URL字段的陷阱」:
bash
curl -X POST https://api.catalog.beer/brewer \
  -u "$CATALOG_BEER_API_KEY:" \
  -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{
    "name": "Alibi Brewing",
    "url": "https://alibi.beer",
    "short_description": "Neighborhood brewery in ..."
  }'
步骤4——创建啤酒(
brewer_id
name
abv
和风格为必填项;
abv
为0–99.9的浮点数,存储时会四舍五入至一位小数
ibu
为可选整数,范围0–1000)。直接发送酿酒厂提供的精确数值,由API进行四舍五入——如果标签标注的是范围而非具体数值(如"ABV低于0.5%"),则记录上限值(
0.5
)。若酿酒厂未公布IBU,则省略该字段:
0
表示啤酒无可测量的苦味,而非未知。请参阅
references/beers.md
→ "记录ABV"和"记录IBU":
bash
curl -X POST https://api.catalog.beer/beer \
  -u "$CATALOG_BEER_API_KEY:" \
  -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{
    "brewer_id": "c1a5b1f0-...",
    "name": "Kölsch",
    "style": "Kölsch",
    "abv": 4.8,
    "description": "..."
  }'
创建成功后会返回包含
id
的完整对象。请向用户反馈新的catalog.beer URL:
https://catalog.beer/beer/{id}

Classifying styles

风格分类

The taxonomy has three tiers — class (
ale
/
lager
) → family (26, e.g.
ipa
,
stout
) → style (~200, e.g.
west-coast-ipa
). A beer may be filed at any tier; the API derives the broader tiers automatically.
  • Default: pass the brewery's own label as
    style
    .
    It's matched against canonical names + aliases — "NEIPA", "New England IPA", and "Juicy IPA" all resolve to the same style — and stored verbatim alongside the match.
  • A label that doesn't resolve is rejected, not stored. There is no "keep the label with a null
    style_id
    " fallback: the API returns
    400
    and writes nothing. Marketing names ("Cali Pilsner", "Margarita-inspired Gose") hit this constantly.
  • Recovery: resend the same verbatim label plus an explicit tier, in one request. The explicit field classifies the beer; the label is stored exactly as you sent it, so the brewery's own words survive:
    json
    {"style": "Margarita-inspired Gose", "style_id": "contemporary-gose"}
    The 400 tells you what to send. It carries a
    suggestions.style
    object —
    styles[]
    , each with
    style_id
    ,
    name
    ,
    parent
    ,
    class
    ,
    catch_all
    ,
    aliases[]
    and
    match
    — ranked best-first. Retry with the best candidate that fits; no second lookup needed.
    Check
    match
    before you trust the order.
    It is the difference between an answer and a guess:
    match
    What to do
    exact
    Your label is this style's name or alias. Take it.
    all_terms
    Every word of your label is in its name or aliases. Usually right.
    partial
    Only some words matched. Never take
    [0]
    on faith.
    Read the list; prefer
    families
    /
    classes
    or a catch-all.
    description
    Matched only our prose about the style. Weakest signal we return.
    Within a
    match
    level ties break on how many beers we hold in each style, so a populous style can outrank a better-fitting rare one (for "Cali Pilsner" every candidate is
    partial
    and the closest is last of six). An all-
    partial
    list means the API did not recognise your label — that is a cue to file one tier up, not to pick the first row.
    families[]
    and
    classes[]
    ride along
    when your label names a family or super-class but no style matched outright — "Crisp American Lager" returns the
    lager
    class. Send
    parent
    /
    class
    back the same way you would a
    style_id
    .
    matched_on
    appears when we could not match your whole label and fell back to its last two words; the candidates describe that shorter phrase, not what you sent.
    GET /style/search?q={label}
    and
    GET /style/parent
    are there if you want to look further afield — and dropping marketing words from the query ("Crisp American Lager" → "American Lager") is usually what finds it. Send
    style_id
    (style slug),
    parent
    (family slug), or
    class
    (
    ale
    /
    lager
    ) — the most specific field you send wins. When no real style fits, use the nearest catch-all style (
    catch_all: true
    wild-beer
    ,
    experimental-ipa
    ,
    specialty-beer
    , …). It keeps the beer in the right family and filed at style tier, which a bare
    parent
    doesn't.
  • Always send the label with the tier, never the tier alone. With
    style_id
    and no
    style
    , the API substitutes the canonical style name — "Margarita-inspired Gose" is stored as "Contemporary-Style Gose" and the brewery's wording is lost.
  • File at the tier the evidence supports. Brewery says "IPA" → send
    parent: "ipa"
    , not a guessed sub-style. Picking the tier is a mapping judgment, not a fact you're inventing — but the label you send must be the brewery's, verbatim.
  • If you had to guess, say so.
    style_confidence
    flags a classification for review. Omit it normally — the API derives it. Send
    "catch-all"
    or
    "family"
    when your mapping is shakier than the request looks (style inferred from the beer's name, ambiguous brewery page). You can only claim less certainty this way, never more: an unmatched label sent as
    "confident"
    is silently reduced. See
    references/beers.md
    .
  • beverage_type
    (beer/cider/perry/mead) is derived — never send it.
Style specs are also the API's best read feature:
GET /style/{slug}
returns curated ABV/IBU/SRM/OG/FG ranges sourced from BA/BJCP guidelines — use it instead of recalling specs from memory.
分类体系分为三层——类别
ale
/
lager
)→ 家族(26种,如
ipa
stout
)→ 风格(约200种,如
west-coast-ipa
)。啤酒可归类至任意层级,API会自动推导更宽泛的层级。
  • 默认规则:直接使用酿酒厂标签作为
    style
    。系统会将其与标准名称及别名匹配——"NEIPA"、"New England IPA"和"Juicy IPA"都会解析为同一风格——并将标签原文与匹配结果一同存储。
  • 无法解析的标签会被拒绝,不会存储。不存在「保留标签但
    style_id
    设为null」的回退机制:API会返回400错误且不会写入任何内容。营销名称(如"Cali Pilsner"、"Margarita-inspired Gose")经常遇到这种情况。
  • 解决方法:在同一请求中发送标签原文+明确的层级信息。明确字段用于分类啤酒;标签原文会被原样存储,因此酿酒厂的表述得以保留:
    json
    {"style": "Margarita-inspired Gose", "style_id": "contemporary-gose"}
    400错误会提示你应发送的内容。响应中包含
    suggestions.style
    对象——
    styles[]
    数组,每个元素包含
    style_id
    name
    parent
    class
    catch_all
    aliases[]
    match
    字段——按匹配度从高到低排序。选择最合适的候选值重试,无需再次查询。
    选择前请检查
    match
    字段
    。该字段区分精确匹配与猜测:
    match
    操作建议
    exact
    你的标签就是该风格的名称或别名。直接使用。
    all_terms
    标签中的每个词汇都能在该风格的名称或别名中找到。通常是正确匹配。
    partial
    仅部分词汇匹配。切勿盲目选择第0项。请阅读完整列表;优先选择家族/类别或通用风格。
    description
    仅匹配到我们关于该风格的描述文本。这是我们返回的最弱匹配信号。
    在同一
    match
    级别下,系统会按该风格收录的啤酒数量排序,因此流行风格可能会排在更匹配的小众风格之前(例如"Cali Pilsner"的所有候选匹配都是
    partial
    ,最接近的匹配在六个结果中排最后)。如果所有结果都是
    partial
    ,说明API无法识别你的标签——此时应选择更上层的分类,而非第一个结果。
    families[]
    classes[]
    会随响应返回
    ,当你的标签匹配到家族或大类但未匹配到具体风格时——例如"Crisp American Lager"会返回
    lager
    类别。你可以像使用
    style_id
    一样返回
    parent
    /
    class
    。**
    matched_on
    **字段会在无法匹配整个标签,只能匹配最后两个词汇时出现;候选结果描述的是该短短语,而非你发送的完整标签。
    如果你需要进一步查询,可以使用
    GET /style/search?q={label}
    GET /style/parent
    ——通常从查询中移除营销词汇(如"Crisp American Lager" → "American Lager")就能找到匹配项。发送
    style_id
    (风格别名)、
    parent
    (家族别名)或
    class
    ale
    /
    lager
    )——你发送的最具体字段优先级最高。如果没有合适的具体风格,请使用最接近的通用风格
    catch_all: true
    ——如
    wild-beer
    experimental-ipa
    specialty-beer
    等)。这能让啤酒归入正确的家族并保持风格层级,而仅使用
    parent
    无法实现这一点。
  • 务必同时发送标签和层级信息,切勿仅发送层级。如果只发送
    style_id
    而不发送
    style
    ,API会用标准风格名称覆盖酿酒厂的标签——例如"Margarita-inspired Gose"会被存储为"Contemporary-Style Gose",酿酒厂的表述会丢失。
  • 按证据支持的层级分类。如果酿酒厂标注"IPA" → 发送
    parent: "ipa"
    ,而非猜测的子风格。选择层级是判断性的映射,而非编造事实——但你发送的标签必须是酿酒厂的原文。
  • 如果必须猜测,请标注
    style_confidence
    字段用于标记需要审核的分类。正常情况下省略该字段——API会自动推导。当你的映射比请求看起来更不确定时(如从啤酒名称推断风格、酿酒厂页面信息模糊),发送
    "catch-all"
    "family"
    。你只能降低确定性,无法提高:如果将无法匹配的标签标记为
    "confident"
    ,会被自动降级。请参阅
    references/beers.md
  • beverage_type
    (beer/cider/perry/mead)由系统推导——切勿自行发送。
风格规格也是API最实用的读取功能:
GET /style/{slug}
会返回来自BA/BJCP指南的ABV/IBU/SRM/OG/FG范围——请使用该接口获取规格,而非凭记忆回忆。

The URL field bites

URL字段的陷阱

url
(on brewers and locations) is not just syntax-checked — the API fetches it live before accepting the write: a
HEAD
request, 10s timeout, up to 10 redirects, strict TLS verification, sent with the user agent
api.catalog.beer/1.0
. Anything other than a final 2xx/3xx is treated as a bad URL.
That produces false rejections on URLs that are perfectly correct:
  • Bot protection / WAF (Cloudflare et al.) answering
    403
    to a non-browser user agent — common for breweries on hosted platforms
  • Servers that answer
    405
    to
    HEAD
    but serve
    GET
    fine
  • Sites slower than 10s, expired/self-signed certs, geo-blocked hosts
Two consequences worth knowing before you start:
  • A refused URL fails the entire request
    POST /brewer
    with an unreachable
    url
    creates no brewer at all, not a brewer without a URL.
  • A wrong URL already in the catalog can't be corrected if the correct one is bot-protected: the PATCH 400s and the wrong URL stays.
When a write fails with
valid_msg.url
set (the message says "something seems to be wrong with your URL" regardless of cause — it is not evidence the URL is wrong):
  1. Retry once with the exact URL a browser lands on —
    https://
    , correct
    www.
    or bare host, no tracking params.
  2. If it fails again, resend without
    url
    so the rest of the record is still created or updated. Never let the URL sink the write. (If the original was a PUT, retry as a PATCH — an omitted
    url
    on PUT clears the URL already on the record.)
  3. Tell the user plainly: the URL is correct, the API's reachability check refused it, and the field was left unset. Don't record a substitute URL (a Facebook page, an old domain) just to fill the field — a wrong URL is worse than none.
酿酒厂和位置信息中的
url
字段不仅会被语法检查——API会实时抓取该URL后才会接受写入请求:发送HEAD请求,超时时间10秒,最多重定向10次,严格验证TLS,用户代理为
api.catalog.beer/1.0
。最终响应非2xx/3xx都会被视为无效URL。
这会导致一些完全正确的URL被误拒:
  • 机器人防护/WAF(如Cloudflare)对非浏览器用户代理返回
    403
    ——使用托管平台的酿酒厂常见这种情况
  • 服务器对HEAD请求返回
    405
    ,但GET请求正常
  • 响应时间超过10秒、证书过期/自签名、存在地域限制的站点
开始操作前需了解两个后果:
  • URL验证失败会导致整个请求失败——如果
    POST /brewer
    中的
    url
    无法访问,不会创建任何酿酒厂记录,而非创建无URL的酿酒厂。
  • 如果目录中已存在错误URL,且正确URL受机器人防护,则无法修正——PATCH请求会返回400错误,错误URL会保留。
当写入请求因
valid_msg.url
失败时(无论原因,提示信息均为"something seems to be wrong with your URL"——这并不意味着URL本身错误):
  1. 重试一次,使用浏览器实际访问的精确URL——
    https://
    开头,正确的
    www.
    或裸域名,无跟踪参数。
  2. 如果仍失败,移除
    url
    字段重新发送
    ,确保记录的其他部分仍能被创建或更新。切勿因URL问题放弃写入。(如果原请求是PUT,请改为PATCH重试——PUT请求中省略
    url
    会清空已有的URL记录。)
  3. 明确告知用户:URL本身正确,但API的可达性检查拒绝了它,因此该字段未设置。切勿为了填充字段而替换其他URL(如Facebook页面、旧域名)——错误的URL比没有更糟。

Endpoint quick reference

端点速查

TaskEndpoint
Search beers / brewers / styles
GET /beer/search?q=
·
/brewer/search?q=
·
/style/search?q=
(max
count
100)
Get one
GET /beer/{id}
·
/brewer/{id}
·
/location/{id}
·
/style/{slug}
A brewery's beers / locations
GET /brewer/{id}/beer
·
/brewer/{id}/locations
Breweries near me
GET /location/nearby?latitude=&longitude=
·
/location/zip?zip_code=
·
/location/city?city=&state=
Create
POST /brewer
·
/beer
·
/location
, then
POST /address/{location_id}
Edit
PATCH /beer/{id}
etc. (partial) ·
PUT
(full replace — clears omitted fields)
Styles
GET /style
(all, with
version
) ·
/style/parent
(families) ·
/style/class
My usage / billing status
GET /usage/my-usage
·
GET /billing
(never blocked by the usage limit, not counted)
All entity IDs are 36-char UUIDs; style IDs are slugs. List endpoints use cursor pagination: pass
next_cursor
back as
cursor
;
next_cursor
is only present when
has_more
is true.
任务端点
搜索啤酒/酿酒厂/风格
GET /beer/search?q=
·
/brewer/search?q=
·
/style/search?q=
(最大
count
为100)
获取单个实体
GET /beer/{id}
·
/brewer/{id}
·
/location/{id}
·
/style/{slug}
获取酿酒厂的啤酒/位置
GET /brewer/{id}/beer
·
/brewer/{id}/locations
查找附近的酿酒厂
GET /location/nearby?latitude=&longitude=
·
/location/zip?zip_code=
·
/location/city?city=&state=
创建实体
POST /brewer
·
/beer
·
/location
,然后调用
POST /address/{location_id}
编辑实体
PATCH /beer/{id}
等(部分更新) ·
PUT
(全量替换——会清空省略的字段)
风格相关
GET /style
(所有风格,含
version
) ·
/style/parent
(家族) ·
/style/class
(类别)
查询我的使用情况/计费状态
GET /usage/my-usage
·
GET /billing
(不受使用限额限制,不计入请求次数)
所有实体ID为36位UUID;风格ID为别名。列表端点使用游标分页:将
next_cursor
作为
cursor
参数传递;仅当
has_more
为true时才会返回
next_cursor

Common mistakes

常见错误

  • Guessing ABV/IBU because the brewery's site doesn't list them. Don't — omit
    ibu
    (optional) and ask the user for
    abv
    (required).
  • Sending
    ibu: 0
    to mean "not listed".
    0
    is a real, storable value meaning no measurable bitterness;
    null
    (or omitting the field) means unknown. Getting this backwards writes a false fact about the beer.
  • Reading an
    abv
    rounding as stale data.
    abv
    is stored to one decimal place, so a record holding
    13.9
    where the brewery says
    13.89%
    is already right. Compare site figures to stored ones at one decimal before deciding a field needs a PATCH, or a reconcile run fills up with writes that change nothing.
  • Skipping a non-alcoholic beer because the label says "Less than 0.5% ABV" rather than a number. Record the published bound —
    <0.5%
    abv: 0.5
    , "Under 4%" →
    4.0
    — and say in your report that the stored figure is an upper bound. Never pick an interior value like
    0.4
    ; that one is invented.
  • Writing a brewer
    short_description
    longer than 160 characters → 400. Compose it as a one-line subtitle; put anything longer in
    description
    .
  • Formatting a
    description
    with Markdown —
    **bold**
    ,
    - bullets
    ,
    [links](...)
    . The API stores exactly the bytes you send and catalog.beer renders them as plain text, so the syntax appears literally on a public page and a human has to go and strip it. Write prose. Newlines are the one thing that survives — use blank lines for paragraphs.
  • Treating a
    valid_msg.url
    400 as "the URL is wrong" and abandoning the write, or swapping in a different URL. The API fetched the site and something answered non-2xx — usually bot protection. Retry once, then send the record without
    url
    .
  • Assuming an unmatched
    style
    label is stored verbatim with a null
    style_id
    . It isn't — it's a
    400
    and nothing is written. Resend the label plus
    style_id
    /
    parent
    /
    class
    together. This applies to PATCH as well, where the whole patch is discarded: a
    name
    and an
    abv
    sent alongside an unresolvable
    style
    are not saved either, even though
    valid_state
    marks them
    "valid"
    (which means "passed validation", not "was written"). Fix the
    "invalid"
    field and resend the entire body.
  • Sending
    style_id
    without
    style
    , which overwrites the brewery's label with the canonical style name.
  • Verifying a write against a list endpoint. List and nested rows are compact — e.g.
    GET /brewer/{id}/beer
    rows omit
    description
    — so a missing field there is not a failed write. Verify with the single-object endpoint (
    GET /beer/{id}
    ).
  • Creating a duplicate because search used the full legal name. Search the distinctive word ("russian river", not "Russian River Brewing Company").
  • Using PUT to change one field — it nulls every optional field you omitted.
  • Sending
    beverage_type
    or verification flags (
    cb_verified
    ,
    brewer_verified
    ) — these are server-controlled and cannot be set.
  • Forgetting the address is a second request:
    POST /location
    creates the location (needs
    brewer_id
    +
    country_code
    , ISO 3166-1 alpha-2);
    POST /address/{location_id}
    adds the street address (US only; needs
    address2
    = street, plus either
    city
    +
    sub_code
    or
    zip5
    ).
  • Giving a location a
    name
    that's just the city, or leaving
    name
    null when a brewer runs several venues in one city.
    name
    is for what the address doesn't already say: a venue with its own name uses it ("The Barrel House"), siblings in one city use the neighborhood ("South Park", "Bay Park"), and a brewer's only location in a city needs no
    name
    . Read the neighborhood off the brewery's page — never supply one from your own knowledge of the city.
  • Calling billing endpoints (
    POST /billing/checkout-session
    ,
    PATCH /billing
    ,
    DELETE /billing
    ) without the user explicitly asking. They spend the user's money. On a free-tier
    402
    , report the options — wait for the monthly reset, or add a payment method at https://catalog.beer/billing — and let the user decide.
  • Error responses use
    error
    ,
    error_msg
    , and per-field
    valid_state
    /
    valid_msg
    objects — read
    valid_msg
    to see exactly which field failed and why.
  • 因酿酒厂官网未列出ABV/IBU而猜测填写。切勿这样做——省略
    ibu
    (可选字段),并向用户询问
    abv
    (必填字段)。
  • 发送
    ibu: 0
    表示"未列出"。
    0
    是真实可存储的值,代表无测量到的苦味
    null
    (或省略字段)表示未知。颠倒两者会写入关于啤酒的错误事实。
  • 将ABV的四舍五入视为过时数据。
    abv
    存储时保留一位小数,因此如果酿酒厂标注
    13.89%
    ,而记录中是
    13.9
    ,这已经是正确的。在决定是否需要PATCH更新字段前,请将官网数值与存储值按一位小数比较,否则会产生大量无意义的写入操作。
  • 因标签标注"ABV低于0.5%"而非具体数值,而跳过添加无醇啤酒。请记录公布的上限值——
    <0.5%
    abv: 0.5
    ,"Under 4%" →
    4.0
    ——并在报告中说明存储的数值是上限。切勿选择中间值如
    0.4
    ,这属于编造数据。
  • 酿酒厂的
    short_description
    长度超过160字符 → 返回400错误。请将其编写为一行副标题;更长的内容放入
    description
    字段。
  • 使用Markdown格式化
    description
    ——如
    **加粗**
    - 列表项
    [链接](...)
    。API会原样存储你发送的字节,而catalog.beer会将其渲染为纯文本,因此语法会直接显示在公共页面上,需要人工清理。请使用普通文本。仅换行符会被保留——使用空行分隔段落。
  • valid_msg.url
    的400错误视为"URL错误"而放弃写入,或替换为其他URL。API抓取站点时收到非2xx响应——通常是机器人防护。重试一次,然后移除
    url
    字段
    发送记录。
  • 假设无法匹配的
    style
    标签会被原样存储且
    style_id
    设为null。事实并非如此——会返回400错误且不会写入任何内容。请重新发送标签加上
    style_id
    /
    parent
    /
    class
    。这同样适用于PATCH请求:如果请求中包含无法解析的
    style
    ,即使
    name
    abv
    字段被标记为
    "valid"
    (表示"通过验证",而非"已写入"),整个PATCH请求也会被丢弃。修正
    "invalid"
    字段后重新发送完整请求体。
  • 仅发送
    style_id
    而不发送
    style
    ,这会用标准风格名称覆盖酿酒厂的标签。
  • 通过列表端点验证写入结果。列表和嵌套返回的是精简版数据——例如
    GET /brewer/{id}/beer
    返回的结果会省略
    description
    字段——因此字段缺失不代表写入失败。请使用单个实体的端点验证(如
    GET /beer/{id}
    )。
  • 因搜索时使用完整法定名称而创建重复项。请使用名称中的独特词汇搜索(如"russian river",而非"Russian River Brewing Company")。
  • 使用PUT修改单个字段——这会将所有省略的可选字段设为null。
  • 发送
    beverage_type
    或验证标记(
    cb_verified
    brewer_verified
    )——这些字段由服务器控制,无法自行设置。
  • 忘记地址需要单独发送请求
    POST /location
    创建位置(需
    brewer_id
    +
    country_code
    ,ISO 3166-1 alpha-2格式);
    POST /address/{location_id}
    添加街道地址(仅支持美国;需
    address2
    =街道,加上
    city
    +
    sub_code
    zip5
    )。
  • 给位置设置的
    name
    仅为城市名称,或当酿酒厂在同一城市有多个门店时
    name
    留空。
    name
    用于补充地址未包含的信息:有独立名称的门店使用其名称(如"The Barrel House"),同一城市的多家门店使用街区名称(如"South Park"、"Bay Park"),而酿酒厂在某城市的唯一门店无需设置
    name
    。请从酿酒厂页面读取街区名称——切勿凭自身对城市的认知填写。
  • 未经用户明确要求调用计费端点(
    POST /billing/checkout-session
    PATCH /billing
    DELETE /billing
    )。这些操作会产生费用。对于免费层级的
    402
    错误,请告知用户可选方案——等待每月重置,或在https://catalog.beer/billing添加支付方式——由用户决定。
  • 错误响应包含
    error
    error_msg
    和每个字段的
    valid_state
    /
    valid_msg
    对象——请阅读
    valid_msg
    以明确哪个字段失败及原因。

Detailed references

详细参考文档

Read the matching file before your first write to each entity type — the examples above are shape, these are the contract. Read the rest as needed:
  • references/beers.md — full beer endpoints, style resolution details
  • references/brewers.md — full brewer endpoints
  • references/locations.md — locations, addresses, nearby search
  • references/styles.md — style taxonomy, objects, endpoints
  • references/api-basics.md — auth, errors, pagination, usage limits, method semantics
首次写入某类实体前,请阅读对应文档——上述示例仅展示请求结构,以下文档才是完整契约。按需阅读其余文档:
  • references/beers.md — 完整啤酒端点、风格解析细节
  • references/brewers.md — 完整酿酒厂端点
  • references/locations.md — 位置、地址、附近搜索
  • references/styles.md — 风格分类体系、对象、端点
  • references/api-basics.md — 认证、错误、分页、使用限额、方法语义