Alya Books

    Developer docs

    REST API, webhooks, MCP server and CLI for UAE e-invoicing

    Accounting connectors

    QuickBooks Online, Xero and Odoo: OAuth or credentials, pull contacts and invoices, push issued invoices, and the link table that keeps both sides in step.

    Alya Books syncs with QuickBooks Online, Xero and Odoo (plus the older Zoho Books and Tally connectors). A connector is set up in the app under Connectors; the API can list connectors, run syncs and inspect the record links. Credentials never leave the database: GET /connectors strips config.credentials, and in the app only the server reads them (members see the settings without tokens or keys).

    Connection models

    ProviderAuthWhere it is set up
    quickbooksOAuth 2.0 (Intuit)App → Connectors → Connect. The oauth-initiate function builds the authorize URL, oauth-callback exchanges the code and stores tokens; needs QUICKBOOKS_CLIENT_ID / QUICKBOOKS_CLIENT_SECRET and APP_URL on the server.
    xeroOAuth 2.0Same flow with XERO_CLIENT_ID / XERO_CLIENT_SECRET.
    odooURL, database, login, API key (JSON-RPC)App → Connectors → Connect Odoo (form). Test signs in and stores the user id.

    Every provider also has a sandbox mode (config.mode = "sandbox") that serves sample data and accepts pushes without contacting anyone. Use it to exercise sync and push in CI or on a demo organization.

    Run a sync

    curl -X POST "$BASE/connectors/$CONNECTOR_ID/sync" -H "X-API-KEY: $KEY" -H "Content-Type: application/json" \
      -d '{ "mode": "incremental", "pushInvoices": true }'
    

    mode is full, incremental (changes since lastSyncAt) or date_range (dateFrom, dateTo). pullContacts, pullInvoices and pushInvoices override the connector's configuration for this run; pushSince (YYYY-MM-DD) limits which issued invoices are pushed (default: the connector's push_since, else the last 30 days). Only keys with settings:write may sync; Zoho Books and Tally keep their own functions and return 409.

    The response is a summary:

    {
      "contacts": { "imported": 12, "updated": 3, "skipped": 1, "errors": 0 },
      "invoices": { "imported": 8, "skipped": 2, "errors": 0 },
      "pushed": { "created": 5, "skipped": 0, "errors": 1, "details": [{ "invoiceId": "…", "number": "INV-2027-00012", "error": "Unknown tax code" }] },
      "companyName": "Acme Trading LLC",
      "messages": []
    }
    

    The sync is recorded in the connector's history and emits connector.synced.

    What moves

    • Contacts are matched by TRN, then case-insensitively by name; a match links the existing contact instead of creating one. Later runs update linked contacts.
    • Invoices pulled become drafts with sourceSystem = provider and sourceId = the remote id. Lines keep quantity, unit price, VAT category (mapped from the remote tax) and the remote number. A remote invoice whose number already exists in Alya Books is skipped.
    • Invoices pushed are issued invoices (submitted, delivered, accepted; not test, not archived, not themselves imported from that provider) issued on or after pushSince. Each is created once; the link table prevents a second push. Tax codes and income account come from the connector config (tax_code_standard, tax_code_zero_rated, tax_code_exempt, income_account).

    GET /connectors/{id}/links?entityType=invoice pages the Alya Books id ↔ remote id pairs with direction (pull or push), remoteUpdatedAt and lastSyncedAt. Use it to reconcile or to find the QuickBooks/Xero/Odoo id of a Alya Books invoice.

    Scheduling

    Connectors with sync_frequency = daily are synced by the daily jobs (daily-jobs, at 02:00 and 18:45 UAE time). Manual connectors sync only on request. Tokens are refreshed automatically before each run; a refresh failure sets the connector to error with the message in lastSyncMessage, and Test in the app (or a new OAuth round trip) repairs it.

    MCP and CLI

    MCP tools list_connectors and sync_connector. CLI: alyabooks connector list, alyabooks connector sync <id> --mode incremental --push.