[{"data":1,"prerenderedAt":163},["ShallowReactive",2],{"post-\u002Fblog\u002Freliable-api-boundaries":3},{"id":4,"title":5,"body":6,"date":149,"description":150,"extension":151,"featured":152,"image":153,"meta":154,"navigation":44,"path":155,"readingTime":41,"seo":156,"stem":157,"tags":158,"updated":161,"__hash__":162},"blog\u002Fblog\u002Freliable-api-boundaries.md","Reliable APIs start at the boundaries",{"type":7,"value":8,"toc":144},"minimark",[9,13,18,21,123,127,130,134,137,140],[10,11,12],"p",{},"An API boundary is where your assumptions meet someone else's code. Making that boundary explicit reduces surprises on both sides.",[14,15,17],"h2",{"id":16},"validate-before-doing-work","Validate before doing work",[10,19,20],{},"Validate the shape of an incoming request before passing it into the application. Keep transport concerns separate from business rules: a missing field and an unavailable product are different kinds of problems.",[22,23,28],"pre",{"className":24,"code":25,"language":26,"meta":27,"style":27},"language-csharp shiki shiki-themes github-light github-dark","public record CreateNoteRequest(string Title, string Body);\n\napp.MapPost(\"\u002Fnotes\", (CreateNoteRequest request) =>\n{\n    if (string.IsNullOrWhiteSpace(request.Title))\n    {\n        return Results.ValidationProblem(new Dictionary\u003Cstring, string[]>\n        {\n            [\"title\"] = [\"A title is required.\"]\n        });\n    }\n\n    \u002F\u002F Demonstration only: persist the note in a real application.\n    return Results.Ok(new { title = request.Title.Trim() });\n});\n","csharp","",[29,30,31,39,46,52,58,64,70,76,82,88,94,100,105,111,117],"code",{"__ignoreMap":27},[32,33,36],"span",{"class":34,"line":35},"line",1,[32,37,38],{},"public record CreateNoteRequest(string Title, string Body);\n",[32,40,42],{"class":34,"line":41},2,[32,43,45],{"emptyLinePlaceholder":44},true,"\n",[32,47,49],{"class":34,"line":48},3,[32,50,51],{},"app.MapPost(\"\u002Fnotes\", (CreateNoteRequest request) =>\n",[32,53,55],{"class":34,"line":54},4,[32,56,57],{},"{\n",[32,59,61],{"class":34,"line":60},5,[32,62,63],{},"    if (string.IsNullOrWhiteSpace(request.Title))\n",[32,65,67],{"class":34,"line":66},6,[32,68,69],{},"    {\n",[32,71,73],{"class":34,"line":72},7,[32,74,75],{},"        return Results.ValidationProblem(new Dictionary\u003Cstring, string[]>\n",[32,77,79],{"class":34,"line":78},8,[32,80,81],{},"        {\n",[32,83,85],{"class":34,"line":84},9,[32,86,87],{},"            [\"title\"] = [\"A title is required.\"]\n",[32,89,91],{"class":34,"line":90},10,[32,92,93],{},"        });\n",[32,95,97],{"class":34,"line":96},11,[32,98,99],{},"    }\n",[32,101,103],{"class":34,"line":102},12,[32,104,45],{"emptyLinePlaceholder":44},[32,106,108],{"class":34,"line":107},13,[32,109,110],{},"    \u002F\u002F Demonstration only: persist the note in a real application.\n",[32,112,114],{"class":34,"line":113},14,[32,115,116],{},"    return Results.Ok(new { title = request.Title.Trim() });\n",[32,118,120],{"class":34,"line":119},15,[32,121,122],{},"});\n",[14,124,126],{"id":125},"make-failures-understandable","Make failures understandable",[10,128,129],{},"A useful error tells a caller what went wrong and what they can do next. Use consistent status codes and a stable error shape. Keep stack traces and internal details in your logs.",[14,131,133],{"id":132},"design-for-retries","Design for retries",[10,135,136],{},"Networks fail. Clients retry. For operations with side effects, decide how to recognize a repeated request before the first duplicate arrives.",[10,138,139],{},"The implementation depends on your system, but the question is universal: can this operation safely happen twice?",[141,142,143],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":27,"searchDepth":41,"depth":41,"links":145},[146,147,148],{"id":16,"depth":41,"text":17},{"id":125,"depth":41,"text":126},{"id":132,"depth":41,"text":133},"2026-09-17","Clear contracts, useful errors, and a few deliberate choices that make services easier to work with.","md",false,"\u002Fsocial-card.png",{},"\u002Fblog\u002Freliable-api-boundaries",{"title":5,"description":150},"blog\u002Freliable-api-boundaries",[159,160],".NET","Software engineering",null,"CZlWgOX1j0Kj7tLRT--KJ3k4CCHPdVZlrJ2BqKMP9oA",1789824594652]