Tyreeq Bakinson scored the only goal of the match on his 50th appearance for the Robins, who defeated the Potters for the first time in five matches.
Stoke were reduced to ten men when Joe Allen was sent off in second-half stoppage time. The visitors tasted defeat for the first time in over a month.
Story of the match
Stoke could have been two goals up in the opening minutes of the match. Steven Fletcher headed back across goal from Mario Vrancic's cross and hit the post and Dan Bentley made a one-handed save on a shot by Jacob Brown.
The hosts appealed for a penalty when a cross by Callum O'Dowda came off the hand of Leo Ostigard, but referee waved play on.
Fletcher again hit the woodwork, this time in the 35th minute from a cross by Josh Tyman and five minutes later, the visitors were made to pay for their wastefulness.
Tomas Kalas' throw was flicked on at the near post by Chris Martin and Bakinson cushioned the ball past and into the net.
Stoke nearly equalized when the ball came to Romaine Sawyers, but he couldn't react quick enough from six yards out.
Vrancic drew a foul from Alex Scott, who was lucky not to get sent off and in the 71st minute, a corner for James Chester was headed wide with a clear sight of goal.
Alfie Doughty was next to have a go, beating the Bristol City defence with his pace, but he couldn't find the finish required with just Bentley to beat.
A minute from time, the Potters hit on the counter-attack, but Tyrese Campbell could only find the side netting.
Allen capped off a frustrating night for the visitors when he picked up his second yellow card and will miss Stoke's next match against Blackburn Rovers.
Man of the match: Tyreeq Bakinson
He not only scored the match-winner, but he used the ball well throughout. His work rate was excellent, covering a ton of ground during his 85 minutes on the pitch.