Kohli (54 off 106) raised a few eyebrows by opting to bat on a seaming track after going into the final game with an all-pace attack.
The captain's bold decision backfired though he was the stand out batsman yet again, negotiating a lethal pace attack with sublime skills.
Pujara (50 off 106) provided solid support to Kohli during their 84-run stand, the only significant partnership of the innings that lasted 77 overs.
Post tea, India lost six for 73 as after Pujara reached his 17th Test half-century off 173 balls.
Pujara added 31 runs with Parthiv Patel (2) for the fifth wicket. They looked to resurrect the Indian innings, before Pujaras long vigil came to an end as he edged behind off Andile Phehlukwayo (2-25).
India lost three wickets for no addition to the score in the space of 12 balls at that juncture. Five balls after Pujaras dismissal, Patel was caught behind off Morne Morkel (2-47).
Bhuvneshwar (30 runs, 49 balls, 4 fours) then played a brilliant hand to rescue India from 144 for seven.
First, he put on 19 runs with Mohammed Shami (8). Vernon Philander (2-31) and Kagiso Rabada (3-39) accounted for Shami and Ishant Sharma (0) thereafter as India were dangling dangerously at 166 for nine.
This was after Kohli scored his 16th half-century to take India to 114 for four at tea.
Post lunch, Kohli and Pujara took their third wicket partnership to 84 runs. The duo played a few more shots, Kohli in particular, as they sped away after the break after India crossed 50 in the 29th over.
Kohli (on 32) got a second life then as AB de Villiers dropped him at third slip off Morne Morkel in the 33rd over. The batsman had chased a wide delivery, only to edge it.
India made good progress as they added 50 runs in the first hour after lunch, the same number as they had scored in the entire first session. In doing so, the Indian skipper brought up his half-century off 101 balls.
Finally though, Kohlis luck ran out as he was caught at third slip again, de Villiers holding on this time off Lungi Ngidi in the 43rd over.
India crossed 100 in the 46th over. Three overs later, Rahane was given a life when he was caught behind off Vernon Philander but it was deemed a no ball.
He only added 16 runs with Pujara, who then saw through the remainder of the session in Patels company.
This was after India opted to bat first on a green-top wicket and reached 45 for two at lunch.
The visitors lost their openers early. KL Rahul (0) faced a near-unplayable spell from Philander and should have gone first ball, but somehow managed to survive. Six balls later though, he got an inside-edge off the same bowler and was caught behind.
Pujara and Kohli then came together and resisted the South African bowling through a testing passage of play. They stayed together for 110 balls, despite being beaten and getting three lives between them.
Philander had almost trapped Pujara (on 0) lbw in the sixth over, only for DRS to stay with umpire Goulds call of not-out as the ball seemed only to clip the bails.
Pujara finally got his first run off the 54th delivery he faced, in the 22nd over, to loud cheers from the sparse Wednesday crowd at the Bull Ring.
Only on the previous delivery though, South Africa opted not to review even as replays showed Ngidi had trapped him lbw with three reds on DRS.
Earlier, India picked an all-seam attack on a cloudy morning in Johannesburg. Bhuvneshwar came in for R Ashwin as India took the field without a full-time spinner for the first time since 2012, when MS Dhoni had opted for a four-pronged pace attack against Australia at Perth.
South Africa made just the one change, with seam all- rounder Adile Pheklukwayo coming in ahead of left-arm spinner Keshav Maharaj.
Disclaimer: No Business Standard Journalist was involved in creation of this content
