The hosts were on top for much of the contest, with Kyle Bennett and Matty Kennedy both coming close to scoring.
But they were made to play for their profligacy late on when Matthew Kilgallon headed home a late winner for the third-placed Bantams.
Kenny Jackett made one change from the side that were beaten at Blackburn the previous week.
Brett Pitman – wearing a mask to protect his recently broken nose – was fit enough to start after recovering from a knee operation, with Oliver Hawkins dropping to the bench.
The first half was a lively affair, although neither goalkeeper was given too much to do, with the hosts in particular misplacing their shooting boots.
Bennett had an early attempt charged down by Adam Thompson, while Christian Burgess was unable to turn Dion Donohue’s corner on target at the far post.
The former was producing an energetic performance and he did well to hold the ball up on the right and feed Jamal Lowe, who charged into the box and drew a save from Colin Doyle.
Pompey were looking the more threatening side and when Kennedy whipped in a dangerous free-kick, Pitman’s header was deflected wide.
There were a couple of scrambles in the area at the other end of the pitch, but the ball was cleared to safety on each occasion.
Some of Bennett’s build-up play was superb, but he was lacking a clinical touch when picked out by Kennedy, ballooning a shot high into the Milton End.
Kennedy than had a chance of his own on 36 minutes, trying his luck with a 20-yard curler that dipped narrowly over the crossbar.
Bradford almost took a shock lead just before the break after Matt Clarke had been punished for a foul out wide.
Tony McMahon sent a superb delivery into the Blues box and Romain Vincelot’s header was inches off target.
But it was the hosts who finished the half on top and Ben Close had an effort blocked after being teed-up by Bennett’s clever pass.
Bradford City 0
Bennett’s mixed afternoon continued after the interval when he did brilliantly to intercept a back-pass and round Doyle.
But that forced him out wider than he would have liked and he was left with head in hands after only being able to fire into the side netting.
The home fans’ frustrations grew further when McMahon took an age to tie his shoelaces, with referee Dean Whitestone doing little to hurry him up.
Bradford then missed an opportunity to forge ahead themselves, with Charlie Wyke directing his free header at Luke McGee.
But the action was soon back down the other end of the pitch and the ball had to be cut out when Lowe tricked his way into the box and tried to send it into the mixer.
The second half was being played at a frenetic pace, as both sides looked to get forward and find an opener.
Jackett made his first change on 67 minutes, with Bennett afforded a standing ovation as he made way for Conor Chaplin.
The newcomer made an instant impact by feeding Close, only for Doyle to leave his line and make a block.
And the keeper then got to his feet and made an even better save when Kennedy thundered the loose ball towards goal.
Donohue sent the resulting corner into the danger zone and a leaping Burgess looped his header onto the roof of the net.
But it was Bradford who were celebrating on 80 minutes after Dion Donohue had been booked for a foul on Alex Gilliead.
McMahon whipped the ball into the box, where Kilgallon was left in too much space to send a header past McGee.
Hawkins came on for Donohue as Jackett added an extra forward option to try to engineer some late drama.
But despite pushing plenty of men up the pitch for the closing stages, the Bantams held on to secure the victory.
Pompey (4-2-3-1): McGee; Thompson, Burgess, Clarke, Donohue (Hawkins 86); Close, O’Keefe; Lowe, Bennett (Chaplin 67), Kennedy; Pitman (c)
Booked: Donohue, Pitman
Subs not used: Bass, Haunstrup, Rose, Evans, Naismith
Bradford (4-4-2): Doyle; McMahon, Thompson, Kilgallon, Field; Gilliead, Reeves, Vincelot (c), Law; Wyke, Taylor (Dieng 84)
Goals: Kilgallon 80
Subs not used: Raeder, Hendrie, Robinson, Jones, McCartan, Patrick
Referee: Dean Whitestone
Attendance: 18,067 (1,003 away fans)