<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: FetchSandbox</title>
    <description>The latest articles on Forem by FetchSandbox (@fetchsandbox).</description>
    <link>https://forem.com/fetchsandbox</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3830177%2Fe63c5a3c-3f48-4ae2-ad6b-ed510712a396.png</url>
      <title>Forem: FetchSandbox</title>
      <link>https://forem.com/fetchsandbox</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/fetchsandbox"/>
    <language>en</language>
    <item>
      <title>got a 200 response in minutes… still took hours to know if anything worked</title>
      <dc:creator>FetchSandbox</dc:creator>
      <pubDate>Sun, 05 Apr 2026 00:05:58 +0000</pubDate>
      <link>https://forem.com/fetchsandbox/got-a-200-response-in-minutes-still-took-hours-to-know-if-anything-worked-4j1b</link>
      <guid>https://forem.com/fetchsandbox/got-a-200-response-in-minutes-still-took-hours-to-know-if-anything-worked-4j1b</guid>
      <description>&lt;p&gt;i was integrating with Paddle recently&lt;/p&gt;

&lt;p&gt;got a 200 response pretty quickly&lt;br&gt;&lt;br&gt;
so i thought… ok this should be straightforward&lt;/p&gt;

&lt;p&gt;it wasn’t&lt;/p&gt;

&lt;p&gt;not because the API was hard&lt;br&gt;&lt;br&gt;
but because i couldn’t tell if anything actually worked&lt;/p&gt;

&lt;h2&gt;
  
  
  what’s easy now
&lt;/h2&gt;

&lt;p&gt;most APIs today are pretty good at getting you started&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;docs are cleaner
&lt;/li&gt;
&lt;li&gt;SDKs are better
&lt;/li&gt;
&lt;li&gt;you can make your first API call in minutes
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;that part is honestly not the problem anymore&lt;/p&gt;

&lt;h2&gt;
  
  
  where things slow down
&lt;/h2&gt;

&lt;p&gt;the time goes after that first 200&lt;/p&gt;

&lt;p&gt;you start asking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;did the webhook fire?
&lt;/li&gt;
&lt;li&gt;did it fire with the right payload?
&lt;/li&gt;
&lt;li&gt;did the state actually change?
&lt;/li&gt;
&lt;li&gt;is it the correct state or just “some state”?
&lt;/li&gt;
&lt;li&gt;what happens if this fails?
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and there’s no single place to see this&lt;/p&gt;

&lt;p&gt;so you jump between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your terminal
&lt;/li&gt;
&lt;li&gt;webhook logs
&lt;/li&gt;
&lt;li&gt;dashboards
&lt;/li&gt;
&lt;li&gt;your own code
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;trying to figure out what actually happened&lt;/p&gt;

&lt;h2&gt;
  
  
  the loop
&lt;/h2&gt;

&lt;p&gt;this is usually how it goes:&lt;/p&gt;

&lt;p&gt;call API → looks fine&lt;br&gt;&lt;br&gt;
wait for webhook → nothing&lt;br&gt;&lt;br&gt;
retry → webhook shows up twice&lt;br&gt;&lt;br&gt;
check state → not what you expected&lt;br&gt;&lt;br&gt;
add logs → try again  &lt;/p&gt;

&lt;p&gt;repeat&lt;/p&gt;

&lt;p&gt;after a few tries you’re not even sure what you’re debugging anymore&lt;/p&gt;

&lt;h2&gt;
  
  
  what we optimize for
&lt;/h2&gt;

&lt;p&gt;most DX today is focused on:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;how fast can you make your first API call&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;which is useful… but not enough&lt;/p&gt;

&lt;p&gt;because calling the API is not the hard part anymore&lt;/p&gt;

&lt;h2&gt;
  
  
  what actually matters
&lt;/h2&gt;

&lt;p&gt;what matters is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;how long it takes before you trust your integration&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;not “it returned 200”&lt;/p&gt;

&lt;p&gt;but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the system behaves the way you expect
&lt;/li&gt;
&lt;li&gt;state moves correctly
&lt;/li&gt;
&lt;li&gt;webhooks fire correctly
&lt;/li&gt;
&lt;li&gt;retries don’t break things
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;that’s what actually decides if you can ship&lt;/p&gt;

&lt;h2&gt;
  
  
  docs vs reality
&lt;/h2&gt;

&lt;p&gt;docs show:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;request
&lt;/li&gt;
&lt;li&gt;response
&lt;/li&gt;
&lt;li&gt;“success”
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;real life looks more like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;request succeeds
&lt;/li&gt;
&lt;li&gt;webhook missing
&lt;/li&gt;
&lt;li&gt;state is off
&lt;/li&gt;
&lt;li&gt;retry changes something else
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and now you’re debugging across multiple places&lt;/p&gt;

&lt;h2&gt;
  
  
  what we started doing
&lt;/h2&gt;

&lt;p&gt;after hitting this a few times, we stopped looking at just API calls&lt;/p&gt;

&lt;p&gt;and started looking at the whole flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create → update → confirm
&lt;/li&gt;
&lt;li&gt;state transitions
&lt;/li&gt;
&lt;li&gt;webhook events
&lt;/li&gt;
&lt;li&gt;final result
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;in one place&lt;/p&gt;

&lt;p&gt;not just:&lt;/p&gt;

&lt;p&gt;“did it return 200”&lt;/p&gt;

&lt;p&gt;but:&lt;/p&gt;

&lt;p&gt;“did it actually work”&lt;/p&gt;

&lt;p&gt;ended up building a small internal tool to run these flows end-to-end and see the final state instead of stitching logs manually&lt;/p&gt;

&lt;p&gt;turned it into something usable here: &lt;a href="https://fetchsandbox.com" rel="noopener noreferrer"&gt;https://fetchsandbox.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  still feels like a gap
&lt;/h2&gt;

&lt;p&gt;curious how others deal with this&lt;/p&gt;

&lt;p&gt;are teams actually measuring this properly?&lt;/p&gt;

&lt;p&gt;or are we all still:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reading logs
&lt;/li&gt;
&lt;li&gt;retrying requests
&lt;/li&gt;
&lt;li&gt;and hoping it works in prod&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>api</category>
      <category>devrel</category>
      <category>webdev</category>
      <category>engineering</category>
    </item>
    <item>
      <title>Our partners kept breaking on staging. So we gave them production access. (Don't do this.)</title>
      <dc:creator>FetchSandbox</dc:creator>
      <pubDate>Wed, 01 Apr 2026 23:00:29 +0000</pubDate>
      <link>https://forem.com/fetchsandbox/our-partners-kept-breaking-on-staging-so-we-gave-them-production-access-dont-do-this-1nna</link>
      <guid>https://forem.com/fetchsandbox/our-partners-kept-breaking-on-staging-so-we-gave-them-production-access-dont-do-this-1nna</guid>
      <description>&lt;p&gt;I work on an embed platform. Partners integrate our APIs to build features into their own products — payments, identity verification, onboarding flows.&lt;/p&gt;

&lt;p&gt;We spent six months trying to answer one question: &lt;strong&gt;how do partners test their integration before going live?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We tried four approaches. Each one failed in a new and exciting way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 1: "Just use our docs"
&lt;/h2&gt;

&lt;p&gt;We pointed partners to our API docs. Endpoints, auth, request/response examples. "You're good to go."&lt;/p&gt;

&lt;p&gt;Partners would start building, hit something we didn't document, open a support ticket, wait two days, lose momentum. A few never came back. One told us they went with a competitor because "we couldn't get a working test setup in a week."&lt;/p&gt;

&lt;p&gt;The docs described what &lt;em&gt;should&lt;/em&gt; happen. Partners had no way to see what &lt;em&gt;actually&lt;/em&gt; happens until they wrote code, deployed it, and hoped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 2: Internal UAT box
&lt;/h2&gt;

&lt;p&gt;Next idea: give partners access to our internal UAT environment. IP whitelisted, shared credentials. We told them "please don't break anything" without a hint of irony.&lt;/p&gt;

&lt;p&gt;Worked for three weeks.&lt;/p&gt;

&lt;p&gt;Then QA pushed a bad config on a Tuesday afternoon. Partner's integration broke. They didn't know it was our side — spent two days debugging their own code before opening a ticket. By the time we figured it out, their engineering lead was cc'ing our VP.&lt;/p&gt;

&lt;p&gt;Around the same time, another team was running load tests on UAT. Partner's API calls were timing out. Their CTO emailed us asking if our platform was "production-ready."&lt;/p&gt;

&lt;p&gt;UAT was built for us to break things. We invited external partners into that mess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 3: Staging with IP whitelisting
&lt;/h2&gt;

&lt;p&gt;We carved out a "partner staging" — same codebase, separate deployment, IP whitelisted per partner. This felt like the grown-up solution.&lt;/p&gt;

&lt;p&gt;It wasn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IP whitelists were a full-time job.&lt;/strong&gt; Every new partner meant new firewall rules. Partners working from home had different IPs than their office. One partner's CTO was traveling and couldn't hit the sandbox from his hotel. We were debugging VPN configs at 11pm on a Thursday.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test data went stale.&lt;/strong&gt; Partners tried to create orders for products that existed in production but not in staging. "Your API returns 404 for product_id XYZ." Yes, because nobody seeded staging in three weeks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deploys kept colliding.&lt;/strong&gt; Engineers would ship to staging without checking if partners were actively testing. A deploy during a partner's live demo call is the kind of thing that gets brought up in quarterly business reviews.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost.&lt;/strong&gt; Running a full mirror of production for partner testing. Database, compute, monitoring, on-call rotation. All for an environment that got used maybe 10 hours a week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 4: Production access
&lt;/h2&gt;

&lt;p&gt;I wish I was kidding.&lt;/p&gt;

&lt;p&gt;The reasoning: "staging is flaky, UAT is a disaster, let's just whitelist them on production with read-only test accounts."&lt;/p&gt;

&lt;p&gt;The API was stable! Partners were happy. For about a month.&lt;/p&gt;

&lt;p&gt;Then one partner's integration bug created 400 orphaned records in production. Our data team spent a weekend cleaning it up.&lt;/p&gt;

&lt;p&gt;Compliance wanted to know why test payloads with fake PII were hitting the production database.&lt;/p&gt;

&lt;p&gt;When we needed to do maintenance, we had to coordinate downtime with partners who were "just running a few test calls."&lt;/p&gt;

&lt;p&gt;We'd built the world's most expensive and dangerous sandbox: production with IP whitelisting.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we actually needed
&lt;/h2&gt;

&lt;p&gt;After burning six months on this, the answer felt obvious in hindsight. Partners didn't need access to &lt;em&gt;our&lt;/em&gt; infrastructure at all.&lt;/p&gt;

&lt;p&gt;They needed an API that looked and acted like ours — same endpoints, same schemas, same auth patterns — but was completely separate. Something where POST actually creates a resource, GET retrieves it, state transitions work, and nobody else's deploy can break it.&lt;/p&gt;

&lt;p&gt;Not a mock server (those are stateless — step 2 doesn't know about step 1). Not a shared staging box (those are everybody's problem). A dedicated, isolated sandbox generated from the same OpenAPI spec our real API uses.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm building now
&lt;/h2&gt;

&lt;p&gt;This experience is half the reason I started working on &lt;a href="https://fetchsandbox.com" rel="noopener noreferrer"&gt;FetchSandbox&lt;/a&gt;. You give it an OpenAPI spec and it spins up a stateful sandbox — real CRUD, state machines, webhook events, seed data. No infrastructure to manage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Partner gets a sandbox from your spec&lt;/span&gt;
npx fetchsandbox generate ./your-api-openapi.yaml

&lt;span class="c"&gt;# They can call endpoints immediately&lt;/span&gt;
curl https://your-api.fetchsandbox.com/v1/orders &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"api-key: sandbox_abc123"&lt;/span&gt;
&lt;span class="c"&gt;# → 200 OK, returns realistic seed data&lt;/span&gt;

&lt;span class="c"&gt;# They can run the full integration workflow&lt;/span&gt;
fetchsandbox run your-api create-and-fulfill-order
&lt;span class="c"&gt;# → ✓ Create order — 201&lt;/span&gt;
&lt;span class="c"&gt;# → ✓ Add line items — 200&lt;/span&gt;
&lt;span class="c"&gt;# → ✓ Submit for fulfillment — 200&lt;/span&gt;
&lt;span class="c"&gt;# → ✓ Webhook: order.fulfilled fired&lt;/span&gt;
&lt;span class="c"&gt;# → All steps passed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No staging environment to maintain. No IP whitelists. No risk to production. Partners get their own URL, their own data, their own credentials. Your team doesn't touch it.&lt;/p&gt;

&lt;p&gt;When a partner asks "does this workflow work?" — they prove it themselves. No support ticket needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The time sink nobody tracks
&lt;/h2&gt;

&lt;p&gt;If you run a partner API, add up how many hours your team spends per month on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provisioning and maintaining test environments&lt;/li&gt;
&lt;li&gt;Debugging "is your sandbox down?" tickets&lt;/li&gt;
&lt;li&gt;Managing IP whitelists and VPN access&lt;/li&gt;
&lt;li&gt;Re-seeding stale test data&lt;/li&gt;
&lt;li&gt;Coordinating deploys around partner testing schedules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At my company it was easily 20-30 hours a month across engineering and DevOps. For a problem that shouldn't exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://fetchsandbox.com" rel="noopener noreferrer"&gt;FetchSandbox&lt;/a&gt; works with any OpenAPI 3.x spec. 19 APIs are live — Stripe, GitHub, Twilio, WorkOS, and more. No signup.&lt;/p&gt;

&lt;p&gt;If you run an API platform and want to try this with your own spec, reach out — &lt;a href="https://x.com/fetchsandbox" rel="noopener noreferrer"&gt;@fetchsandbox&lt;/a&gt; on X or &lt;a href="mailto:hello@fetchsandbox.com"&gt;hello@fetchsandbox.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Free during early access.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I know I'm not the only one who's been through this. UAT → staging → "just give them prod." If you've found a better way to handle partner sandbox environments, I'd genuinely like to know. Still figuring this out.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>apitesting</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>This is amazing truly</title>
      <dc:creator>FetchSandbox</dc:creator>
      <pubDate>Tue, 31 Mar 2026 00:53:27 +0000</pubDate>
      <link>https://forem.com/fetchsandbox/this-is-amazing-truly-2agp</link>
      <guid>https://forem.com/fetchsandbox/this-is-amazing-truly-2agp</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/axrisi/stop-paying-for-cloud-deploy-a-highly-available-cluster-on-your-own-hardware-in-minutes-14f0" class="crayons-story__hidden-navigation-link"&gt;Stop Paying for Cloud: Deploy a Highly Available Cluster on Your Own Hardware in Minutes | MicroCloud&lt;/a&gt;
    &lt;div class="crayons-article__cover crayons-article__cover__image__feed"&gt;
      &lt;iframe src="https://www.youtube.com/embed/QL5K-hEcdOE" title="Stop Paying for Cloud: Deploy a Highly Available Cluster on Your Own Hardware in Minutes | MicroCloud"&gt;&lt;/iframe&gt;
    &lt;/div&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/axrisi" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3226798%2F0c0a8594-658c-4146-a639-8068ede85f67.jpg" alt="axrisi profile" class="crayons-avatar__image" width="800" height="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/axrisi" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Nikoloz Turazashvili (@axrisi)
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Nikoloz Turazashvili (&lt;a class="mentioned-user" href="https://dev.to/axrisi"&gt;@axrisi&lt;/a&gt;)
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png" width="166" height="102"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-3409402" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/axrisi" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3226798%2F0c0a8594-658c-4146-a639-8068ede85f67.jpg" class="crayons-avatar__image" alt="" width="800" height="800"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Nikoloz Turazashvili (@axrisi)&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/axrisi/stop-paying-for-cloud-deploy-a-highly-available-cluster-on-your-own-hardware-in-minutes-14f0" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 26&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/axrisi/stop-paying-for-cloud-deploy-a-highly-available-cluster-on-your-own-hardware-in-minutes-14f0" id="article-link-3409402"&gt;
          Stop Paying for Cloud: Deploy a Highly Available Cluster on Your Own Hardware in Minutes | MicroCloud
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/linux"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;linux&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devops"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devops&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/cloud"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;cloud&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tutorial"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tutorial&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/axrisi/stop-paying-for-cloud-deploy-a-highly-available-cluster-on-your-own-hardware-in-minutes-14f0" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;29&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/axrisi/stop-paying-for-cloud-deploy-a-highly-available-cluster-on-your-own-hardware-in-minutes-14f0#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              2&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>linux</category>
      <category>devops</category>
      <category>cloud</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>My mock server lied to me. So I built a stateful API sandbox.</title>
      <dc:creator>FetchSandbox</dc:creator>
      <pubDate>Mon, 30 Mar 2026 23:26:50 +0000</pubDate>
      <link>https://forem.com/fetchsandbox/my-mock-server-lied-to-me-so-i-built-a-stateful-api-sandbox-549n</link>
      <guid>https://forem.com/fetchsandbox/my-mock-server-lied-to-me-so-i-built-a-stateful-api-sandbox-549n</guid>
      <description>&lt;p&gt;Last month I was integrating with a payment API. Wrote my tests against a mock server, everything passed, shipped to staging — and the whole flow broke.&lt;/p&gt;

&lt;p&gt;The mock told me &lt;code&gt;POST /charges&lt;/code&gt; returns &lt;code&gt;{"id": "ch_123"}&lt;/code&gt;. And it does. But my code then called &lt;code&gt;GET /charges/ch_123&lt;/code&gt; to verify the status, and the mock returned 404. Because the mock doesn't actually &lt;em&gt;store&lt;/em&gt; anything. Every request lives in its own universe.&lt;/p&gt;

&lt;p&gt;I lost half a day to this. And it wasn't the first time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with stateless mocks
&lt;/h2&gt;

&lt;p&gt;I've used Prism, WireMock, Mockoon — they're solid tools. You point them at an OpenAPI spec and they generate responses. But the responses are canned. There's no memory between requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;POST /customers → 201 &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;: &lt;span class="s2"&gt;"cust_123"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
GET /customers/cust_123 → 404   &lt;span class="c"&gt;# has no idea you just created this&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works fine for unit tests where you're testing your HTTP client. It falls apart the moment you have a multi-step flow.&lt;/p&gt;

&lt;p&gt;Think about how a real Stripe integration works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a customer&lt;/li&gt;
&lt;li&gt;Create a payment intent &lt;em&gt;for that customer&lt;/em&gt; (needs the customer ID from step 1)&lt;/li&gt;
&lt;li&gt;Confirm the payment intent (needs the PI ID from step 2)&lt;/li&gt;
&lt;li&gt;A webhook fires (your server needs to handle it)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A mock server can't do steps 2-4. The IDs don't carry over. The webhook never fires. You're testing a fantasy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually needed
&lt;/h2&gt;

&lt;p&gt;I needed a sandbox where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;POST creates a real resource I can GET later&lt;/li&gt;
&lt;li&gt;IDs chain between requests like they would in production&lt;/li&gt;
&lt;li&gt;State transitions work (a charge goes from &lt;code&gt;pending&lt;/code&gt; to &lt;code&gt;succeeded&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Webhooks fire when things change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically — not a mock, but a tiny fake version of the actual API that behaves like the real thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I built one
&lt;/h2&gt;

&lt;p&gt;I've been heads-down on &lt;a href="https://fetchsandbox.com" rel="noopener noreferrer"&gt;FetchSandbox&lt;/a&gt; for a few months now. You give it an OpenAPI spec and it generates a stateful sandbox with seed data, state machines, and webhook events.&lt;/p&gt;

&lt;p&gt;Here's what it looks like from the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; fetchsandbox

fetchsandbox generate ./stripe-openapi.yaml
&lt;span class="c"&gt;# ✓ Sandbox ready: 587 endpoints, 63 seed records&lt;/span&gt;

fetchsandbox run stripe &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;span class="c"&gt;# ✓ Accept a payment — 3/3 steps passed&lt;/span&gt;
&lt;span class="c"&gt;# ✓ Onboard a connected account — 3/3 steps passed&lt;/span&gt;
&lt;span class="c"&gt;# ✓ Respond to a dispute — 2/2 steps passed&lt;/span&gt;
&lt;span class="c"&gt;# ✓ All workflows passed — 3/3 (9ms)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;run --all&lt;/code&gt; command is the thing I wish I'd had. It executes every integration workflow end-to-end — creating resources, chaining IDs between steps, and verifying each response. If something breaks, you see exactly which step failed and why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stuff that surprised me while building it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Error scenarios were harder than happy paths.&lt;/strong&gt; I added a &lt;code&gt;--scenario&lt;/code&gt; flag so you can switch the whole sandbox to "auth_failure" mode and see what happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fetchsandbox run stripe accept_payment &lt;span class="nt"&gt;--scenario&lt;/span&gt; auth_failure
&lt;span class="c"&gt;# ✗ Step 1: POST /v1/payment_intents → 401 Unauthorized&lt;/span&gt;
&lt;span class="c"&gt;# Scenario "auth_failure" correctly caused failure.&lt;/span&gt;
&lt;span class="c"&gt;# Scenario reset to default.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My code had a bug where it didn't handle 401 on the payment intent endpoint — only on the customer endpoint. Would never have caught that with a regular mock.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Webhooks were a rabbit hole.&lt;/strong&gt; In a real Stripe integration, half the logic is in webhook handlers. The sandbox now fires webhook events when resources mutate, and you can watch them in real-time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fetchsandbox webhook-listen stripe
&lt;span class="c"&gt;# 12:04:31  payment_intent.created  pi_xyz  → requires_confirmation&lt;/span&gt;
&lt;span class="c"&gt;# 12:04:32  payment_intent.succeeded pi_xyz → succeeded&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Inspecting state is underrated.&lt;/strong&gt; After running a workflow, you can see exactly what's in the sandbox:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fetchsandbox state stripe customers
&lt;span class="c"&gt;# customers — 3 records&lt;/span&gt;
&lt;span class="c"&gt;# ┌──────────────┬─────────────────┬──────────┐&lt;/span&gt;
&lt;span class="c"&gt;# │ id           │ email           │ status   │&lt;/span&gt;
&lt;span class="c"&gt;# ├──────────────┼─────────────────┼──────────┤&lt;/span&gt;
&lt;span class="c"&gt;# │ cust_abc123  │ test@acme.com   │ active   │&lt;/span&gt;
&lt;span class="c"&gt;# └──────────────┴─────────────────┴──────────┘&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How it compares to the alternatives
&lt;/h2&gt;

&lt;p&gt;I'm not going to pretend FetchSandbox replaces everything. Here's where I honestly think it sits:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Mock server (Prism)&lt;/th&gt;
&lt;th&gt;Vendor sandbox (Stripe test mode)&lt;/th&gt;
&lt;th&gt;FetchSandbox&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Setup time&lt;/td&gt;
&lt;td&gt;1 min&lt;/td&gt;
&lt;td&gt;15-30 min (account + keys)&lt;/td&gt;
&lt;td&gt;&amp;lt; 30 sec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stateful&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signup required&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works offline&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Hosted (offline coming)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Matches prod exactly&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (schema-accurate, not logic-accurate)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Webhooks&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (with CLI forwarding)&lt;/td&gt;
&lt;td&gt;Yes (built-in)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Any OpenAPI spec&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Only their API&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The honest gap: FetchSandbox doesn't replicate vendor-specific business logic. Stripe's test mode knows that a card ending in 4242 succeeds and 4000000000000002 declines. FetchSandbox doesn't. It validates your &lt;em&gt;integration pattern&lt;/em&gt;, not the vendor's edge cases.&lt;/p&gt;

&lt;p&gt;For me, that's the right tradeoff. I use FetchSandbox while building the integration, then switch to the vendor's test mode for final validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI/CD is where it clicks
&lt;/h2&gt;

&lt;p&gt;The thing I'm most excited about is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# GitHub Actions&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prove integration works before deploy&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx fetchsandbox run stripe --all --json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit code 0 = all workflows pass. Exit code 1 = something broke. Your pipeline catches integration regressions before they hit staging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Numbers
&lt;/h2&gt;

&lt;p&gt;I ran a benchmark — time from "I want to explore this API" to "I made my first successful call":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vendor docs path:&lt;/strong&gt; 15-30 minutes (signup → dashboard → keys → local server → SDK → code → run)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FetchSandbox path:&lt;/strong&gt; under 1 second (open portal → endpoint is callable)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://nordicapis.com/why-time-to-first-call-is-a-vital-api-metric/" rel="noopener noreferrer"&gt;Nordic APIs&lt;/a&gt; defines TTFC (time to first call) benchmarks: under 2 minutes is "Champion" tier. Over 10 minutes is a "Red Flag."&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;19 APIs are live right now — Stripe, GitHub, Twilio, WorkOS, OpenAI, DigitalOcean, and more. No signup needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://fetchsandbox.com" rel="noopener noreferrer"&gt;fetchsandbox.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's free during early access while I figure out what developers actually need from it. If you try it and something breaks or feels wrong, I genuinely want to know — I'm &lt;a href="https://x.com/fetchsandbox" rel="noopener noreferrer"&gt;@fetchsandbox&lt;/a&gt; on X.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Curious what other people's testing setups look like for third-party APIs. Do you mock everything? Use vendor test modes? Some hybrid? Drop a comment — I've been deep in this problem for months and I'm still learning.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>apitesting</category>
      <category>openapi</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
