apievangelist-aws
AWS infrastructure for the API Evangelist network. Most websites stay static on GitHub Pages; this repo holds two independent stacks:
ApiEvangelistStack — the first-class API + MCP server:
- api.apievangelist.com — REST API (
/v1) over the whole research network: 5,100+ stories, 77 topic areas, 2,400+ governance building blocks (guidance, rules, policies, standards, strategies, schema, properties, experiences, lifecycle), conversations, papers, services, vocabulary, newsletters, and feeds. - mcp.apievangelist.com — MCP server (Streamable HTTP), 17 tools including the
concierge layer:
get_overview,guide_topic,find_services.
BlogsSiteStack — static hosting for blogs.apievangelist.com. The blogs site
(api-evangelist/blogs, ~111k aggregated posts) outgrew GitHub Pages, whose hosted
runner OOMs building it. It is built locally and aws s3 sync'd to an S3 bucket fronted
by CloudFront (OAC, *-clean-URL Function, blogs.apievangelist.com ACM cert). See
Blogs site below.
Docs: https://developer.apievangelist.com · OpenAPI:
https://developer.apievangelist.com/apis/apievangelist-v1-openapi.yml · Registry:
com.apievangelist/api-evangelist.
Architecture
One CDK stack (lib/apievangelist-stack.js), mirroring
apis-io-aws:
- CloudFront distribution with both domains → one API Gateway (
/v1*→ search Lambda,/mcp*→ MCP Lambda); a CloudFront Function routes by Host header. Caching disabled (every request is metered);x-origin-secretlocks API GW to the distribution; WAF rate-limits abusive IPs. - Search Lambda (Node 20, MiniSearch): loads
catalog.json(~13 MB, 15k docs) from S3 at cold start; full bodies live ascontent/<type>/<slug>.mdobjects fetched oninclude=content. Ranking boosts tunable viaRANK_BOOST_*env vars. - Authorizer: x-api-key → DynamoDB tier; keyless callers get
internalif the Origin is apievangelist.com or any*.apievangelist.comsite, elsefree. Usage plans: free (5 r/s, 1k/day), internal (100 r/s, 5M/day), owner (200 r/s, 5M/day). - Analytics Lambda: daily Logs Insights aggregation of
evt:apisearchlines → demand reports in S3, served owner-only at/v1/searches.
DNS is Cloudflare, grey-cloud (DNS-only) CNAMEs to CloudFront — proxied records would
route through the network's worker and break the API. scripts/cloudflare-dns.sh
hardcodes proxied:false.
Operating
npm test # 31 handler tests (search, authorizer, MCP)
npm run build:catalog # rebuild dist/catalog.json + dist/content/ from local repos
npm run deploy:catalog # build + upload to S3 (Lambda picks up on next cold start)
bash scripts/deploy.sh diff # CDK diff (CERT_ARN + .env AE_* keys required)
bash scripts/deploy.sh deploy # CDK deploy
Secrets in /Users/kinlane/GitHub/.env: AE_FREE_KEY, AE_INTERNAL_KEY, AE_OWNER_KEY,
AE_MCP_ORIGIN_SECRET. Registry runbook: ../admin/mcp-registry-apievangelist.md.
Refresh cadence: re-run npm run deploy:catalog whenever network content changes
(posts, store items, areas) so the API reflects the sites.
Blogs site
BlogsSiteStack serves blogs.apievangelist.com as a static site on S3 + CloudFront.
The Jekyll build (~111k posts, ~5 GB, ~16 min) is done locally — the github-pages
runner OOMs on it, and github-pages' Jekyll 3.10 also ignores render_with_liquid. Build
under Homebrew Ruby (/opt/homebrew/opt/ruby/bin); the chruby ruby on this machine
links a stale libruby and can't load native gems.
# One-time infra (S3 bucket + CloudFront + OAC + clean-URL Function). The cert is a
# DNS-validated ACM cert for blogs.apievangelist.com (us-east-1); *.apievangelist.com is a
# Cloudflare origin cert (IMPORTED) that CloudFront rejects, so a dedicated one is used.
npx cdk deploy BlogsSiteStack --require-approval never \
-c blogsDomain=blogs.apievangelist.com \
-c blogsCertArn=arn:aws:acm:us-east-1:007596076917:certificate/9d6344ac-7c6a-48f3-8d35-33dc01e7a411
# Build + sync + invalidate (BUCKET/DIST from the stack outputs).
BUCKET=blogs.apievangelist.com DIST=EDFO4PRAM0OC3 ./scripts/deploy-blogs.sh
SKIP_BUILD=1 BUCKET=... DIST=... ./scripts/deploy-blogs.sh # sync an existing _site
# DNS: point blogs.apievangelist.com at the distribution (Cloudflare, DNS-only / grey cloud).
DRY_RUN=1 TARGET=d5e4ib1wlxlas.cloudfront.net ./scripts/cloudflare-dns-blogs.sh # preview
TARGET=d5e4ib1wlxlas.cloudfront.net ./scripts/cloudflare-dns-blogs.sh # apply
Stack outputs: bucket blogs.apievangelist.com, distribution EDFO4PRAM0OC3
(d5e4ib1wlxlas.cloudfront.net).