AccountingKitDocs

Quickstart

Register a Xero app, connect an organization, and run your first accounting workflow.

The API base URL is https://api.accountingkit.dev. Every example below uses these two variables:

export ACCOUNTINGKIT_API_URL="https://api.accountingkit.dev"
export ACCOUNTINGKIT_API_KEY="acct_test_..."

1. Register your Xero app

On a paid plan, connections run through a Xero OAuth app your organization owns. In the console, choose the intended Test or Live environment, open Provider apps, and add Xero. Name the configuration first; AccountingKit immediately creates a resumable draft and shows its callback URLs.

Copy the two generated values into the Xero developer portal:

  • Add the AccountingKit OAuth redirect URI to the app's redirect URIs.
  • Set the app's webhook delivery URL to the app-specific AccountingKit URL.

Then paste the Xero client ID, client secret, and webhook signing key into AccountingKit. The credentials are encrypted and are never returned. The webhook URL contains an opaque AccountingKit app ID, which is how AccountingKit selects the correct signing key before trusting a webhook body.

This step is required on every plan, including during evaluation. A connection cannot be created until a provider app is registered and verified.

2. Create a test API key

Sign in to the developer console, select test, name the key, and create it. Copy the secret when it is shown; only an HMAC digest is stored and the secret cannot be recovered later.

API keys use the acct_test_ or acct_live_ prefix. Send the key as a bearer token:

Authorization: Bearer acct_test_...

For TypeScript services, use the typed client:

import {AccountingKitClient} from '@accountingkit/sdk'

const accounting = new AccountingKitClient({
  apiKey: process.env.ACCOUNTINGKIT_API_KEY!,
})

3. Create a connect session

curl --request POST "$ACCOUNTINGKIT_API_URL/v1/connect-sessions" \
  --header "Authorization: Bearer $ACCOUNTINGKIT_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "provider": "xero",
    "external_account_id": "customer_123",
    "redirect_uri": "https://app.example.com/integrations/xero/complete",
    "provider_app_id": "the-app-id-from-the-console",
    "retention_mode": "cached",
    "metadata": {"workspace_id": "workspace_456"}
  }'

Open the returned connect_url in your customer's browser. AccountingKit runs Xero OAuth through the selected app, discovers the authorized organizations, and stores encrypted rotating tokens. If Xero returns more than one organization, your customer chooses one before the connection becomes active.

retention_mode defaults to cached. Choose zero for live-read integrations that never need sync, automations, webhook hydration, or cached lists — no accounting record is written to our database on a zero-retention connection. The mode is fixed for the life of the connection.

4. Verify the app and list connections

Return to Provider apps, open the app, and select Check setup. OAuth, Xero intent-to-receive, and every required scope must pass before you cut over to production.

curl "$ACCOUNTINGKIT_API_URL/v1/connections" \
  --header "Authorization: Bearer $ACCOUNTINGKIT_API_KEY"

Store the returned connection_id against the same customer record as external_account_id.

5. Create a draft invoice

Every write requires an idempotency key. Repeating the same key and body returns the original result; reusing a key with a different body is rejected.

curl --request POST "$ACCOUNTINGKIT_API_URL/v1/connections/$CONNECTION_ID/invoices" \
  --header "Authorization: Bearer $ACCOUNTINGKIT_API_KEY" \
  --header "Idempotency-Key: invoice-order-10042" \
  --header "Content-Type: application/json" \
  --data '{
    "external_reference": "order_10042",
    "contact_id": "provider-or-normalized-contact-id",
    "status": "DRAFT",
    "due_date": "2026-09-30",
    "line_items": [
      {"description": "Implementation", "quantity": 1, "unit_amount": 1200}
    ]
  }'

6. Register a webhook endpoint

Create an HTTPS endpoint with POST /v1/webhook-endpoints. The signing secret is returned once. Verify every delivery before you process it — see webhooks.

Next steps

On this page