BuildGazette Developer API
Read-only access to articles, series, frameworks, BI Executive docs, lab questions, mentors, and authors. Plus-plan users mint keys at /account; webhook endpoints at /account/webhooks.
Quick start
Send the key in Authorization: Bearer <bi_…>. Every response carries X-Request-Id and X-RateLimit-* headers.
curl https://buildgazette.com/api/v1/articles \ -H "Authorization: Bearer YOUR_BI_KEY" \ -H "Accept: application/json"
Spec: openapi.json • openapi.yaml • postman.json • TypeScript SDK • Python SDK
Authentication
Keys carry a bi_ prefix and are matched against a SHA-256 hash on every request. Plaintext is held in an AES-256-GCM vault and may be revealed up to three times after creation. Rotation mints a new key and revokes the old one after a 24-hour overlap (or immediately via POST /api/api-keys/{id}/rotate?immediate=true for compromise scenarios).
- Per-key origin and CIDR-aware IP allowlists.
- Constant-time hash comparison; invalid keys throttled per source IP.
- Revealing burns one of three views; after that the plaintext is wiped from the vault and the key becomes show-once.
Scopes
Each endpoint declares a required scope. Keys carry an array of granted scopes from this whitelist; unknown values are silently dropped on save. Scope violations return 403 insufficient_scope.
| Scope | Covers |
|---|---|
blogs.read | Articles, categories, search |
series.read | Series + parts |
frameworks.read | Framework catalog + details |
bi.read | BI Executive documents |
lab.read | Community lab questions |
mentor.read | Mentor profiles |
author.read | Author profiles + recent posts |
feed.read | Personal feed |
analytics.read | Per-key call analytics |
analytics.read.aggregate | Anonymized aggregate analytics |
topics.read | Topic landing data |
community.read | Public community profiles |
me.read | Profile of the key owner |
Rate limits
Two layers stack on every call:
- Sliding window — 60 requests / 60 seconds, per key.
- Daily quota —
daily_call_limit(default 1,000), reset at 00:00 UTC. Bumped per-key on request.
Each successful response includes X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (Unix seconds). On 429, Retry-After is set in seconds. Build your client to back off when remaining drops to zero rather than after a 429.
Pagination & caching
Collection endpoints accept either offset (?page=&limit=) or opaque cursor (?cursor=) pagination. Cursor is preferred for stable iteration over a feed that may change mid-walk.
# First page curl "https://buildgazette.com/api/v1/articles?limit=20&cursor=" \ -H "Authorization: Bearer YOUR_BI_KEY" # Follow next_cursor from the previous response curl "https://buildgazette.com/api/v1/articles?limit=20&cursor=eyJ0cyI6Li4u" \ -H "Authorization: Bearer YOUR_BI_KEY"
Detail endpoints emit a stable ETag. Send If-None-Match to short-circuit unchanged responses (saves bandwidth and counts as one request, not zero).
curl "https://buildgazette.com/api/v1/articles/hello-world" \ -H "Authorization: Bearer YOUR_BI_KEY" \ -H 'If-None-Match: "abc123..."' # 304 if unchanged
Idempotency
For non-GET endpoints (when introduced), send Idempotency-Key: <your-token>. Identical body + path + key within 24 hours replays the original 2xx response and adds an Idempotent-Replay: true header. Errors are never cached.
curl -X POST "https://buildgazette.com/api/v1/widgets" \
-H "Authorization: Bearer YOUR_BI_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"name":"alpha"}'Error envelope
All errors return application/problem+json with this shape — the request_id matches the X-Request-Id response header, quote it in support tickets.
{
"error": {
"code": "insufficient_scope",
"type": "https://buildgazette.com/developers#errors-insufficient-scope",
"title": "Insufficient Scope",
"status": 403,
"message": "Required scope: blogs.read",
"doc_url": "https://buildgazette.com/developers#errors-insufficient-scope",
"request_id": "f5b3..."
}
}| Code | HTTP | Meaning |
|---|---|---|
missing_api_key | 401 | No Authorization header sent |
invalid_api_key | 401 | Key not found, revoked, or malformed |
origin_not_allowed | 403 | Origin not in the per-key allowlist |
ip_not_allowed | 403 | Caller IP not in the per-key allowlist |
insufficient_scope | 403 | Key lacks the required scope |
rate_limited | 429 | Per-key sliding window exhausted (60/min) |
daily_quota_exceeded | 429 | Daily call cap hit, resets 00:00 UTC |
not_found | 404 | Resource does not exist or is unpublished |
invalid_query | 400 | A required query parameter is missing/invalid |
query_failed | 500 | Internal database error — retry, then contact support |
internal_error | 500 | Unexpected server error; include X-Request-Id |
Webhooks
Register HTTPS endpoints at /account/webhooks. Every delivery is signed using HMAC-SHA256 over canonical JSON (sorted keys) of the payload, with the timestamp embedded in the header.
# Header sent on every delivery X-BuildGazette-Signature: t=1700000000,v1=<hex> X-BuildGazette-Signature-Version: v1 X-BuildGazette-Event: article.published X-BuildGazette-Delivery: <uuid>
Verification (Node):
import { createHmac } from 'crypto';
function verify(req, secret) {
const sig = req.headers['x-buildgazette-signature']; // 't=...,v1=...'
const parts = Object.fromEntries(sig.split(',').map(p => p.split('=')));
const expected = createHmac('sha256', secret)
.update(`${parts.t}.${req.rawBody}`)
.digest('hex');
return expected === parts.v1
&& Math.abs(Date.now()/1000 - Number(parts.t)) < 300; // <5min drift
}Retry schedule: 1m → 5m → 30m → 4h → 24h → 24h, ±20% jitter. After 20 consecutive failures the endpoint is auto-disabled — re-enable via PATCH { "is_active": true }. Per-endpoint filters are supported (e.g. only fire article.published events when data.category === 'engineering').
Code samples
JavaScript (fetch)
const res = await fetch(
'https://buildgazette.com/api/v1/articles?limit=5',
{ headers: { Authorization: 'Bearer ' + process.env.BG_KEY }}
);
const { data } = await res.json();Python (requests)
import os, requests
r = requests.get(
'https://buildgazette.com/api/v1/articles',
params={'limit': 5},
headers={'Authorization': f"Bearer {os.environ['BG_KEY']}"},
)
r.raise_for_status()
data = r.json()['data']TypeScript SDK
// curl https://buildgazette.com/api/v1/sdk/typescript > bg.ts
import { BuildGazette } from './bg';
const bg = new BuildGazette({ apiKey: process.env.BG_KEY! });
const { data } = await bg.callArticles({ limit: '5' });