Xero Node.js SDK

repository·master·Indexed 42 days ago

https://github.com/xeroapi/xero-node

Official JavaScript/TypeScript SDK (v20.0.0) for programmatic access to Xero's suite of APIs, including Accounting, Payroll, and Assets. It provides the XeroClient for managing OAuth 2.0 authentication, token lifecycles, and Machine-to-Machine (M2M) integrations via Custom Connections.

Tokens
6.4K
Snippets
19
Records
21
Agent score
53%

What's inside Xero

  1. Understand the Token Set structure

    master

    The token_set is a JSON object containing the credentials required for API access. Key fields include:

    • access_token: The Bearer token used for API calls (expires in ~30 mins).
    • refresh_token: Used to obtain a new token_set (expires in ~60 days).
    • id_token: OpenID Connect token (if openid profile email scopes are used).
    • expires_in: Seconds until the access_token expires.
    • scope: The permissions granted in the token.
    {
      "id_token": "xxx.yyy.zz",
      "access_token": "xxx.yyy.zzz",
      "expires_in": 1800,
      "token_type": "Bearer",
      "refresh_token": "xxxxxxxxx",
      "scope": ["email", "profile", "openid", "accounting.settings", "accounting.transactions", "offline_access"]
    }
  2. Query and Filter API requests

    master

    When calling API methods that support filtering (like getBankTransactions or getPurchaseOrders), use the following parameters:

    • ifModifiedSince: A Date object for incremental updates.
    • where: A string representing the filter logic (e.g., 'Status=="AUTHORISED"').
    • order: A string for sorting (e.g., 'Reference ASC').
    • page: The page number.
    • unitdp: The number of items per page.

    CRITICAL: To ignore a specific filter, pass undefined instead of null. Passing null can result in malformed query strings (e.g., Status=&) which may cause the API to return empty results or errors.

    // Correct way to ignore filters
    const purchaseOrders = await xero.accountingApi.getPurchaseOrders(
      tenant.tenantId, 
      undefined, // ifModifiedSince
      undefined, // where
      '2021-01-01', // dateFrom
      '2021-04-25', // dateTo
      undefined, // order
      1 // page
    );
  3. Use Custom Connections for M2M integrations

    master

    Custom Connections use the client_credentials grant type for Machine-to-Machine (M2M) integrations to a single organization. This eliminates the manual authorization step.

    Note: Because the SDK is generated from OpenAPI, you must pass an empty string '' as the xeroTenantId parameter for methods when using a Custom Connection, as these connections are scoped to a single organization.

    import { XeroClient } from 'xero-node';
    
    const xero = new XeroClient({
      clientId: 'YOUR_CLIENT_ID',
      clientSecret: 'YOUR_CLIENT_SECRET',
      grantType: 'client_credentials'
    });
    
    const tokenSet = await xero.getClientCredentialsToken();
    // save the tokenSet
    
    // Pass an empty string for the tenant ID when using Custom Connections
    const invoices = await xero.accountingApi.getInvoices('');
  4. Authenticate users via OAuth2.0

    master

    The SDK uses OAuth2.0. To authenticate a user for the first time, follow these steps:

    1. Generate Consent URL: Use xero.buildConsentUrl() to get the URL to redirect the user to.
    2. Handle Callback: After authorization, Xero redirects the user to your redirect_uri with a code parameter. Use xero.apiCallback(req.url) to exchange this code for a token_set.
    3. Store Tokens: Save the resulting token_set JSON in a datastore associated with the user. You will need this to make subsequent API calls.
    // 1. Send user to authorization URL
    let consentUrl = await xero.buildConsentUrl();
    res.redirect(consentUrl);
    
    // 2. Exchange code for token_set in your callback route
    const tokenSet = await xero.apiCallback(req.url);
    // save the tokenSet to your database
  5. Refresh an expired Token Set

    master

    Once you have stored a TokenSet in your datastore, you can refresh it when it expires. There are two primary patterns for refreshing tokens:

    1. Using a fully initialized client

    Use this if you have already called client.initialize() and client.setTokenSet(tokenSet). This leverages the underlying openid-client library.

    2. Refreshing without full initialization

    If you have a valid refresh token (less than 60 days old) but don't want to perform a full initialization, you can use refreshWithRefreshToken by passing the client credentials and the refresh token directly.

    // Pattern 1: Using initialized client
    import { XeroClient } from 'xero-node';
    const xero = new XeroClient({ /* config */ });
    await xero.initialize();
    const tokenSet = getTokenSetFromDatabase(userId);
    await xero.setTokenSet(tokenSet);
    
    if(tokenSet.expired()){
      const validTokenSet = await xero.refreshToken();
      // save the new tokenset
    }
    
    // Pattern 2: Using refreshWithRefreshToken
    import { XeroClient } from 'xero-node';
    const tokenSet = getTokenSetFromDatabase(userId);
    
    if(tokenSet.expired()){
      const xero = new XeroClient();
      const validTokenSet = await xero.refreshWithRefreshToken(client_id, client_secret, tokenSet.refresh_token);
      // save the new tokenset
    }
  6. Manage App Store Subscriptions

    master

    To manage Xero App Store subscriptions, create a XeroClient using the client_credentials grant type with the marketplace.billing scope. You can then use the appStoreApi.getSubscription(subscriptionId) method to retrieve details about a user's plan, such as status, plans, and startDate.

    const xeroAppStoreClient = new XeroClient({
      clientId: process.env.CLIENT_ID,
      clientSecret: process.env.CLIENT_SECRET,
      grantType: 'client_credentials',
      scopes: ['marketplace.billing']
    });
    
    try {
      await xeroAppStoreClient.getClientCredentialsToken();
    } catch(e) {
      console.log('ERROR: ', e);
    }
    
    const subscriptionRequest = await xeroAppStoreClient.appStoreApi.getSubscription(subscripionId);
    console.log(subscriptionRequest.body);
  7. Perform OAuth2 Authorization Flow

    master

    The XeroClient provides helper methods to manage the standard OAuth2 flow:

    1. Generate Consent URL: Use buildConsentUrl() to get the URL to which you should redirect the user to authorize your application.
    2. Handle Callback: After the user redirects back to your redirectUri, pass the full callback URL to apiCallback(callbackUrl) to exchange the authorization code for a TokenSet.

    Note: apiCallback automatically calls initialize() if it hasn't been called yet.

    // 1. Get the URL to redirect the user to
    const consentUrl = await client.buildConsentUrl();
    // Redirect user to consentUrl...
    
    // 2. After redirect, handle the callback
    const callbackUrl = 'https://your-app.com/callback?code=...&state=...';
    const tokenSet = await client.apiCallback(callbackUrl);
  8. Use the Accounting API to manage Invoices

    master

    This example demonstrates the workflow for accessing the Accounting API: initializing the client, setting the token, updating tenants to find the active tenant ID, and performing CRUD operations on Invoices and History Records.

    Note: When creating invoices, use the Invoice.TypeEnum and Invoice.StatusEnum for type safety.

    import { XeroClient, HistoryRecords, Invoice } from 'xero-node';
    
    const xero = new XeroClient({
      clientId: 'YOUR_CLIENT_ID',
      clientSecret: 'YOUR_CLIENT_SECRET',
      redirectUris: [`http://localhost:${port}/callback`],
      scopes: 'openid profile email accounting.settings accounting.transactions offline_access'.split(" ")
    });
    
    await xero.initialize();
    const tokenSet = getTokenSetFromDatabase(userId);
    await xero.setTokenSet(tokenSet);
    
    if(tokenSet.expired()){
      const validTokenSet = await xero.refreshToken();
    }
    
    await xero.updateTenants();
    const activeTenantId = xero.tenants[0].tenantId;
    
    // GET all Accounts
    const getAccountsResponse = await xero.accountingApi.getAccounts(activeTenantId);
    const accountId = getAccountsResponse.body.accounts[0].accountID;
    
    // CREATE an Invoice
    const invoices = {
      invoices: [
        {
          type: Invoice.TypeEnum.ACCREC,
          contact: { contactID: contactId },
          lineItems: [
            {
              description: "Acme Tires",
              quantity: 2.0,
              unitAmount: 20.0,
              accountCode: "500",
              taxType: "NONE",
              lineAmount: 40.0
            }
          ],
          date: "2019-03-11",
          dueDate: "2018-12-10",
          reference: "Website Design",
          status: Invoice.StatusEnum.AUTHORISED
        }
      ]
    };
    
    const createdInvoicesResponse = await xero.accountingApi.createInvoices(activeTenantId, invoices);
    const invoiceId = createdInvoicesResponse.body.invoices[0].invoiceID;
    
    // CREATE a History Record
    const historyRecords = {
      historyRecords: [
        {
          details: "This is a history record"
        }
      ]
    };
    
    await xero.accountingApi.createInvoiceHistory(activeTenantId, invoiceId, historyRecords);
  9. Configure the XeroClient

    master

    Initialize the XeroClient with your application credentials. You must provide clientId, clientSecret, and redirectUris. You can also specify scopes (as a space-separated string or array), state for custom parameters, and optional settings like httpTimeout (in ms) and clockTolerance (in seconds).

    import { XeroClient } from 'xero-node';
    
    const xero = new XeroClient({
      clientId: 'YOUR_CLIENT_ID',
      clientSecret: 'YOUR_CLIENT_SECRET',
      redirectUris: [`http://localhost:${port}/callback`],
      scopes: 'openid profile email accounting.settings accounting.transactions offline_access'.split(" "),
      state: 'returnPage=my-sweet-dashboard', // custom params (optional)
      httpTimeout: 3000, // ms (optional)
      clockTolerance: 10 // seconds (optional)
    });
  10. Initialize the XeroClient

    master

    To interact with Xero APIs, instantiate a XeroClient with your application credentials. The client provides access to various API sets like accountingApi, assetApi, projectApi, etc.

    Required Configuration:

    • clientId: Your Xero application client ID.
    • clientSecret: Your Xero application client secret.

    Optional Configuration:

    • redirectUris: An array of callback URLs (not used for client_credentials flow).
    • grantType: Set to 'client_credentials' for custom connections (not used for standard OAuth2 flows).
    • scopes: An array of requested scopes (e.g., ['openid', 'profile', 'accounting.transactions']).
    • state: Custom parameters for CSRF protection.
    • httpTimeout: Request timeout in milliseconds.
    • clockTolerance: Tolerance in seconds for token expiration checks.
    const xero = new XeroClient({
      clientId: 'YOUR_CLIENT_ID',
      clientSecret: 'YOUR_CLIENT_SECRET',
      redirectUris: [`http://localhost:${port}/callback`],
      scopes: 'openid profile email accounting.settings accounting.transactions offline_access'.split(" "),
      httpTimeout: 3000,
      clockTolerance: 10
    });
  11. Reference: XeroClient methods

    master

    The XeroClient class provides the following core methods for managing authentication, connections, and token lifecycles:

    | method | description |
    | --- | --- |
    | client.`initialize` | Initializes the Xero Client with the provided configuration |
    | client.`buildConsentUrl` | Returns a url concatenated from the provided redirect uri, scope, and the issuer ( Xero identity authorize url) |
    | client.`apiCallback`(callbackUrl) | Leverages openid-client library to exchange temporary auth code for token set |
    | client.`disconnect`(connectionId) | Removes an individual tenant connection by connection ID |
    | client.`readTokenSet` | Returns token set currently set on the Xero Client |
    | client.`setTokenSet`(tokenSet) | Sets a specified token set on the Xero Client |
    | client.`refreshToken` | Leverages openid-client library to refresh token set currently set on the Xero Client and returns updated token set |
    | client.`revokeToken` | Revokes a users refresh token and removes all their connections to your app |
    | client.`formatMsDate`(dateString) | Takes a date string and returns it formatted as an MS Date |
    | client.`refreshWithRefreshToken`(clientId, clientSecret, refreshToken) | Refresh a token set without leveraging openid-client |
    | client.`getClientCredentialsToken` | Get a token set without user intervention via the client credentials grant type for custom connections only |
    | client.`updateTenants`(fullOrgDetails: boolean = true) | GET request to the /connections endpoint. Accepts a boolean to indicate whether or not to also make a GET request to the /organisations endpoint and map full org data to each connection object prior to returning the array of connections |