{"openapi":"3.1.0","info":{"title":"Blog - Bernardo Trindade de Abreu","description":"Senior Full Stack Engineer specializing in React, Node.js, Python, AI, and cloud technologies at ustwo. Machine-readable surface of the personal site and engineering blog.\n\nPaths carry no version and do not change under you. A breaking change ships at a new path, and the old path keeps answering for at least 90 days with a Deprecation header (RFC 9745), a Sunset header (RFC 8594) and a Link with rel=\"successor-version\" to its replacement; the operation is marked deprecated here. After the Sunset date it answers 410 Gone as problem details with code endpoint_gone and the same successor-version Link. New fields and new endpoints are treated as compatible, so read responses tolerantly and ignore fields you do not know. Nothing is deprecated today; this document and the JSON operations send a Link with rel=\"deprecation\" (RFC 9745) to the policy.","version":"1.0.0"},"externalDocs":{"description":"Notes for agents: setup, request headers, errors and the versioning policy","url":"https://btrindade.com/AGENTS.md"},"servers":[{"url":"https://btrindade.com"}],"security":[],"paths":{"/llms.txt":{"get":{"summary":"Site map for LLMs, per the llmstxt.org convention","description":"Start here. Lists every article with a one-line summary, the topics the writing covers, and when an agent should use this site.","operationId":"getLlmsTxt","responses":{"200":{"description":"Plain-text document","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/llms-full.txt":{"get":{"summary":"Full text of every article, concatenated","description":"Every article in one plain-text document, newest first. Use it to read or search all the writing in a single request.","operationId":"getLlmsFullTxt","responses":{"200":{"description":"Plain-text document","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/index.md":{"get":{"summary":"Markdown mirror of the homepage","description":"Equivalent to requesting / with an Accept: text/markdown header. Who the author is and what has been published, with a sitemap section.","operationId":"getHomeMarkdown","responses":{"200":{"description":"Markdown document","content":{"text/markdown":{"schema":{"type":"string"}}}}}}},"/writing.md":{"get":{"summary":"Markdown mirror of the article archive","description":"Equivalent to requesting /writing with an Accept: text/markdown header. Every article with its date and summary.","operationId":"getWritingMarkdown","responses":{"200":{"description":"Markdown document","content":{"text/markdown":{"schema":{"type":"string"}}}}}}},"/writing/{slug}.md":{"get":{"summary":"Markdown mirror of one article","description":"Equivalent to requesting /writing/{slug} with an Accept: text/markdown header. Responses carry a Link header with rel=\"canonical\" pointing at the HTML page.","operationId":"getArticleMarkdown","parameters":[{"name":"slug","in":"path","required":true,"description":"Article slug, as listed in /llms.txt","schema":{"type":"string","pattern":"^[a-z0-9-]+$"}}],"responses":{"200":{"description":"Markdown document","content":{"text/markdown":{"schema":{"type":"string"}}}},"404":{"description":"Nothing at that path. The body is Markdown linking to the sitemap and /llms.txt.","content":{"text/markdown":{"schema":{"type":"string"}}}}}}},"/tags/{tag}.md":{"get":{"summary":"Markdown mirror of one topic","description":"Equivalent to requesting /tags/{tag} with an Accept: text/markdown header. Every article filed under the topic.","operationId":"getTagMarkdown","parameters":[{"name":"tag","in":"path","required":true,"description":"Topic slug, as listed in /tags.md","schema":{"type":"string","pattern":"^[a-z0-9-]+$"}}],"responses":{"200":{"description":"Markdown document","content":{"text/markdown":{"schema":{"type":"string"}}}},"404":{"description":"Nothing at that path. The body is Markdown linking to the sitemap and /llms.txt.","content":{"text/markdown":{"schema":{"type":"string"}}}}}}},"/api/writing/{slug}/engagement":{"get":{"summary":"Like count of one article","description":"How many readers have liked the article. Read per request, never cached.","operationId":"getArticleEngagement","parameters":[{"name":"slug","in":"path","required":true,"description":"Article slug, as listed in /llms.txt","schema":{"type":"string","pattern":"^[a-z0-9-]+$"}}],"responses":{"200":{"description":"The like count","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Engagement"}}}},"404":{"description":"No article with that slug (code `post_not_found`)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/sitemap.md":{"get":{"summary":"Every URL on the site, as Markdown","description":"Every page on the site as a Markdown list of links.","operationId":"getSitemapMarkdown","responses":{"200":{"description":"Markdown document","content":{"text/markdown":{"schema":{"type":"string"}}}}}}},"/rss.xml":{"get":{"summary":"Feed of new articles","description":"RSS 2.0 feed of articles, newest first. Poll it to notice new writing.","operationId":"getFeed","responses":{"200":{"description":"RSS 2.0 feed","content":{"application/rss+xml":{"schema":{"type":"string"}}}}}}},"/api/newsletter":{"post":{"summary":"Subscribe an email address to the newsletter","description":"Signs an address up for an email when a new article is published. Single opt-in: the address receives a welcome email straight away.","operationId":"subscribeNewsletter","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"Subscription accepted","content":{"application/json":{"schema":{"type":"object","required":["success","message"],"properties":{"success":{"type":"boolean","const":true},"message":{"type":"string"}}}}}},"400":{"description":"Missing or malformed email address (code `invalid_email`)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Newsletter service not configured, or the subscription could not be recorded (code `subscription_failed`)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/newsletter/unsubscribe":{"post":{"summary":"One-click unsubscribe (RFC 8058)","description":"Advertised to mail clients through the List-Unsubscribe header. The token is an HMAC of the address, so the endpoint cannot be used to opt anyone else out. A GET redirects to the confirmation page instead. Every response has an empty body, which is what RFC 8058 clients expect.","operationId":"unsubscribeNewsletter","parameters":[{"name":"e","in":"query","required":true,"schema":{"type":"string","format":"email"}},{"name":"t","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The address will receive nothing further. Empty body."},"400":{"description":"Missing address or bad signature. Empty body."},"500":{"description":"Newsletter service not configured, or the opt-out failed. Empty body."}}}}},"components":{"schemas":{"Problem":{"type":"object","description":"RFC 9457 problem details. Branch on `code`; `detail` is for people.","required":["type","title","status","detail","code"],"properties":{"type":{"type":"string","const":"about:blank"},"title":{"type":"string","description":"The HTTP status phrase"},"status":{"type":"integer"},"detail":{"type":"string","description":"What went wrong and where to look instead"},"code":{"type":"string","enum":["invalid_email","subscription_failed","post_not_found","endpoint_not_found","endpoint_gone"]}}},"Engagement":{"type":"object","required":["likes"],"properties":{"likes":{"type":"integer","minimum":0}}}}}}