# QuickBooks Online

## QuickBooks Online x Perigee

Working with OAuth Flow can be a double-edged sword. While it's crucial for securely accessing APIs, managing tokens, authorization state, and timeouts can be challenging. Get it wrong, and you may find yourself locked out of a resource or stuck with an expired token during an important operation.

QuickBooks Online (QBO) offers a comprehensive guide and an even better playground for developers, especially those just starting with OAuth. But, here comes the tricky part: you have to manage independent sets of authorization and refresh tokens for each authorized company (<mark style="color:purple;">**known as a realm**</mark>) within your application.

This article breaks down how to use Perigee to effortlessly manage these tokens across multiple realms.

### The Core of the Process&#x20;

The following C# code snippet is executed immediately after receiving the `RealmID` and `Code` parameters from the API callback:

```csharp
//Reach out to: https://developer.api.intuit.com/.well-known/openid_configuration
// Get the token endpoint
ThreadRegistry reg = ThreadRegistry.Instance;
string TokenEndpoint = "";

//Create a client pointing to the QBO Token Endpoint
using var authClient = new RestClient(new RestClientOptions(TokenEndpoint)
{
    Authenticator = new RestSharp.Authenticators.HttpBasicAuthenticator(
    reg.GetValue<string>("AppSettings:client_id")!,
    reg.GetValue<string>("AppSettings:client_secret")!)
});
var authReq = new RestRequest("", Method.Post);


//Add parameters
authReq.AddParameter("redirect_uri", reg.GetValue<string>("AppSettings:redirect"));
authReq.AddParameter("grant_type", "authorization_code");
authReq.AddParameter("code", code);

//Execute - Getting back an initial authorization code and refresh code. 
var rsp = authClient.ExecuteRetry<QBAuth.Tokens>(authReq, 2);

if (rsp.IsSuccessful)
{
    //Each credential is assigned a name: QBA- + realmID
    string qbaName = $"QBA-{realmId}";

    //Step 1) Persist an initial credential we received from the token callback
    CredentialStore.PersistCredential(
    new RestSharpCredentialStoreItem(new RestSharp.Authenticators.JwtAuthenticator(rsp.Data?.AccessToken ?? ""), DateTimeOffset.UtcNow.AddSeconds(rsp.Data?.ExpiresIn ?? 3000))
    {
        RefreshToken = rsp.Data?.RefreshToken,
        StoreA = rsp.Data?.IdToken,
        Environment = realmId,
        Name = qbaName
    });

    //Step 2) Register realm refresh for future usage on expiration
    CredentialStore.RegisterRefresh(qbaName, (o) =>
    {
        using var authClient = new RestClient(new RestClientOptions(TokenEndpoint)
        {
            Authenticator = new RestSharp.Authenticators.HttpBasicAuthenticator(
            reg.GetValue<string>("AppSettings:client_id")!,
            reg.GetValue<string>("AppSettings:client_secret")!)
        });
        var authReq = new RestRequest("", Method.Post);

        authReq.AddParameter("redirect_uri", reg.GetValue<string>("AppSettings:redirect")!);
        authReq.AddParameter("grant_type", "refresh_token");
        authReq.AddParameter("refresh_token", CredentialStore.GetRefreshToken(qbaName));
        var rsp = authClient.ExecuteRetry<QBAuth.Tokens>(authReq, retries: 1);

        if (rsp.IsSuccessful)
        {
            //Get previous expired credential using peek to pull environment from last run...
            var expCred = CredentialStore.PeekCredential(qbaName);

            return new RestSharpCredentialStoreItem(new RestSharp.Authenticators.JwtAuthenticator(rsp.Data?.AccessToken ?? ""), DateTimeOffset.UtcNow.AddSeconds(rsp.Data?.ExpiresIn ?? 3000))
            {
                RefreshToken = rsp.Data?.RefreshToken,
                StoreA = rsp.Data?.IdToken,
                Environment = expCred?.Environment ?? "",
            };
        }
        else
        {
            return new FaultedCredentialStoreItem($"Couldn't refresh token for realm {qbaName}, {rsp.Content}");
        }
    });
}
```

### Re-Register the Callback on Application Startup 🔄

Since each realm has its own set of authorization details, it's important to re-register the callback functions for each realm when the application restarts:

```csharp
foreach (var cred in CredentialStore.GetCredentialsByPredicate(f => f.Name.StartsWith("QBA-")))
{
    var QBAName = cred.Name;
    
    //TODO: Call the register function again listed above. It would be wise to encapsulate that mathod to use in multiple places!
}
```

### Making Authorized Realm Calls Is Simpler Than Ever&#x20;

Thanks to our prior setup with Perigee, making authorized realm calls is now a walk in the park. The system ensures that you get a freshly refreshed authorization code each time you hit an endpoint. Plus, it authorizes the call pre-emptively, reducing the risk of a failure midway through a process.

```csharp
var realmID = 123456789;

//New client (this is using sandbox, you *should* be querying the discovery endpoint for this)
using var client = new RestClient(new RestClientOptions("https://sandbox-quickbooks.api.intuit.com") {
    
    //Use the credential authenticator here, with the QBA-Realm key! Voila!!!
    Authenticator = new CredentialAuthenticator($"QBA-{realmID}")
});

var rsp = client.ExecuteRetry<CompanyInfo.QueryResponse>($"/v3/company/{realmID}/query?query=select * from CompanyInfo", retries: 3);
```

In conclusion, while OAuth can often feel like a labyrinth of tokens and authorizations, Perigee simplifies the process, making it far easier and more reliable.

Happy coding from the Perigee Team!&#x20;


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.perigee.software/examples-and-demos/quickbooks-online.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
