12

I just need to access data in my Quickbooks Online (QBO) company using an API with C#. But now they've moved to OAuth2 it looks very complex. For example, it suggests I need a redirect URI to receive the authorization code but I'm writing a simple console app to export data to QBO and don't want to have to host a URI endpoint to do that.

It's also confusing to know how to get and manage the following:

  • Client ID
  • Client Secret
  • RealmID
  • Authorization Code
  • Refresh Token
  • Access Token

There must be a simple way of doing this. For example, with Stripe you only have to manage an API key.

mpipe3
  • 1,013
  • 1
  • 9
  • 20

2 Answers2

21

After some research I found this can be done in a simple way. You only need to keep a copy of the refresh token (perhaps in a read/write file). When you want to access the API just call OAuth2Client.RefreshTokenAsync() to get an access token. All the other items required can be found using the OAuth2 playground.

The access token can then be used for up to one hour with the API. You may also get back an updated refresh token. If that happens just store that for future use. The refresh token lasts up to 100 days before you must use a newer version that was returned from the Refresh Token operation.

Here's a longer version of how to use the API from C#:

  1. Create an app but don't publish it in the QBO app store. To do this login to developer.intuit.com using your QBO account. Go to 'My Apps' Then create an app (e.g. called 'MyQBOApiApp'). This only needs to be done once. Leave the default redirect URL set to the OAuth2 playground as this is the only redirect URL required.

  2. Get the Production Client ID and Client Secret from the 'Production Keys' section of the 'OAuth 2.0 Keys' tab for the app. (Record these for use in your C# program as they don't change)

  3. Goto the OAuth 2.0 playground at https://developer.intuit.com/app/developer/playground

  4. In step 1 'Get Authorization Code' select MyQBOApiApp(Production) from the drop box list

  5. In the Select Scopes list select 'Accounting' if you just need to read/write data to your QBO company

  6. Click on 'Get authorization code'

  7. Connect your QBO company to the MyQBOApiApp app

  8. In Step 2 'Get OAuth 2.0 token from auth code' on the playground page click 'Get tokens'. This will get a refresh token for your API access to your company.

  9. Skip to Step 4 'Refresh access token' on the playground page. The Access Token is only usable for 59 mins so just keep the 'Refresh Token' as it can be used for 100 days to get new access tokens and refresh tokens. Store it somewhere that your C# program can read from and write to (e.g. a file or database)

  10. The realmID is available from Step 3. 'Make API calls'. (Record this for use in your C# program as it doesn't change)

  11. Add the IppDotNetSdkForQuickBooksApiV3 NuGet package to your C# program. To provide easy access to the API.

  12. Make sure you're using .Net Framework 4.6.1 or later because QBO requires TLS 1.2 connections

  13. Unfortunately, .Net console apps don't use TLS 1.2 by default. Therefore add this line of code somewhere in your C# program's startup:

    // Have to explicitly set TLS 1.2 as QBO APIs require it
    System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
    

Before accessing the API you'll need code like this to get an access token:

public static string GetAccessToken()
{
    var oauth2Client = new OAuth2Client(CLIENTID_FROM_STEP_2, 
            CLIENT_SECRET_FROM_STEP_2,
            // Redirect not used but matches entry for app
            "https://developer.intuit.com/v2/OAuth2Playground/RedirectUrl",
            "production"); // environment is “sandbox” or “production”

    var previousRefreshToken = ReadRefreshTokenFromWhereItIsStored();
    var tokenResp = oauth2Client.RefreshTokenAsync(previousRefreshToken );
    tokenResp.Wait();
    var data = tokenResp.Result;

    if ( !String.IsNullOrEmpty(data.Error) || String.IsNullOrEmpty(data.RefreshToken) || 
          String.IsNullOrEmpty(data.AccessToken))
    {
        throw new Exception("Refresh token failed - " + data.Error);
    }

    // If we've got a new refresh_token store it in the file
    if (previousRefreshToken != data.RefreshToken)
    {
        Console.WriteLine("Writing new refresh token : " + data.RefreshToken);
        WriteNewRefreshTokenToWhereItIsStored(data.RefreshToken)

    }
    return data.AccessToken;
}

You'll need to write the functions ReadRefreshTokenFromWhereItIsStored() and WriteNewRefreshTokenToWhereItIsStored() to load and save the refresh token from persistent storage.

All API access in QBO starts with a Service Context. You can create one with code like this:

static public ServiceContext GetServiceContext()
{
    var accessToken = GetAccessToken(); // Code from above
    var oauthValidator = new OAuth2RequestValidator(accessToken);

    ServiceContext qboContext = new ServiceContext(REALMID_PROD_FROM_STEP10,
            IntuitServicesType.QBO, oauthValidator);

    return qboContext;
}

The to access data you can create a data service like this:

var service = new DataService(GetServiceContext());
mpipe3
  • 1,013
  • 1
  • 9
  • 20
  • Thanks, doesn't look like many others are interested in this discussion. Can I ask why you're storing the RefreshToken in a file rather than just storing it in a string? Also, did this code compile? Shouldn't the call to `RefreshTokenAsyc()` use `await`? – Jonathan Wood Jul 30 '19 at 20:27
  • 2
    Yes, the code compiles and works as shown. I store the refresh token in a file because if you lose it (e.g. your program ends) then you have to manually get one again on the playground page. My client exe only runs when a new order is received or on an hourly schedule. Therefore, it needs to store the refresh token somewhere. This code has now been running for over 6 months with no problems – mpipe3 Jul 31 '19 at 16:22
  • I see. For me, `RefreshTokenAsync()` is failing. But maybe you just explained why. I'm just passing `null` the first time. I guess I have to find an initial access token, even knowing it will have expired by the time I use it. I guess it's needed to be able to refresh it. – Jonathan Wood Jul 31 '19 at 16:41
  • I've been storing the refresh token in an Azure KeyVault. That's an option for people if storing it locally doesn't work. – Adam Aug 02 '19 at 18:32
  • Do you have any Code for same process Using JAVA – Sunil Sunny Jul 29 '20 at 12:22
  • I keep getting a 403 forbidden error when attempting this. Keeps telling me the "Access Token does not have sufficient scope" even though I created it with the Accounting scope as described in multiple tutorials. Any idea what might be causing this? I've tried with both the sandbox and production environments, and I can't find anything else online that's even been remotely this helpful. – ChangeJar Oct 15 '20 at 16:36
  • The developer OAuth 2.0 playground URL is bad above, use https://developer.intuit.com/app/developer/playground – Jimmy T. Aug 16 '22 at 12:41
  • Thanks Jimmy T. I have updated the URL in step 3 – mpipe3 Sep 07 '22 at 09:13
-1
[HttpPost("qb/refresh-token")]
        public async Task<ActionResult> GetRefressToken()
        {
            JObject result = new JObject();
            //Request Oauth2 tokens
            var tokenClient = new OAuth2Client(
                "your client id",
                "secret",
                "https://developer.intuit.com/v2/OAuth2Playground/RedirectUrl",
                "sandbox"); // environment is “sandbox” or “production”

                    
            var previousRefreshToken = "your refresh token id ";Get it from Playgroud for first time**strong text**//ReadRefreshTokenFromWhereItIsStored();
            var tokenResp = await tokenClient.RefreshTokenAsync(previousRefreshToken);
           

            if (!String.IsNullOrEmpty(tokenResp.Error) || String.IsNullOrEmpty(tokenResp.RefreshToken) ||
                  String.IsNullOrEmpty(tokenResp.AccessToken))
            {
                throw new Exception("Refresh token failed - " + tokenResp.Error);
            }

            // If we've got a new refresh_token store it in the file
            if (previousRefreshToken != tokenResp.RefreshToken)
            {
                Console.WriteLine("Writing new refresh token : " + tokenResp.RefreshToken);
                //WriteNewRefreshTokenToWhereItIsStored(data.RefreshToken);
             
            }
            return Ok(tokenResp.AccessToken);
            //return new Tuple<string, string>(tokenResp.RefreshToken, tokenResp.AccessToken);
        }   
  • Please don't post only code as an answer, but also provide an explanation of what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes – Ran Marciano Mar 24 '21 at 05:41
  • This just looks like the GetAccessToken() function in https://stackoverflow.com/a/54381096/323058 that has been repackaged as a web API endpoint. I don't really see how that helps this question – mpipe3 Jun 04 '21 at 14:21