Alya Books

    Developer docs

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

    MCP server

    Use Alya Books from Claude, Cursor, ChatGPT or any MCP client: invoices, incoming documents, the ERP intake, customers, products, webhooks and VAT as tools.

    Alya Books exposes its API as a Model Context Protocol server. Every tool maps onto one REST route, so validation, permissions and behaviour are identical to the API.

    Endpoint

    POST https://api.alyabooks.ai/api/v1/mcp
    

    Transport: Streamable HTTP, stateless. Requests are JSON-RPC 2.0 messages (single or batched); responses are JSON. The server never opens a server-sent events stream, so GET /mcp answers 405.

    Headers on every request:

    Content-Type: application/json
    Accept: application/json
    X-API-KEY: sk_live_…
    X-Organization-Id: <client id>   (optional, CA firms)
    

    Authentication is the same API key as the REST API; a tool is only callable when the key has its scope (invoices:write, incoming:read, webhooks:manage, …). Test keys make the assistant work on test documents.

    Connect from claude.ai or ChatGPT (sign-in)

    Chat apps that add MCP servers as custom connectors cannot send an API key, so the endpoint also supports OAuth 2.1, the way the MCP specification describes:

    1. In Claude (Settings → Connectors → Add custom connector) or ChatGPT (connectors in developer mode), enter the MCP URL: https://<your API host>/functions/v1/api-v1/mcp (https://api.alyabooks.ai/api/v1/mcp once the API domain is live).
    2. The app registers itself and opens Alya Books's sign-in. After signing in, you choose the organisation, Test or Live and the permissions, then click Allow.
    3. The app receives a one-hour access token and a refresh token; it refreshes on its own. Revoke it any time under API access → API keys → Connected apps.

    What the server publishes, for client developers:

    Protected resource metadata{API}/.well-known/oauth-protected-resource (also linked from the WWW-Authenticate header of every 401 from /mcp)
    Authorization server metadata{API}/.well-known/openid-configuration and {API}/.well-known/oauth-authorization-server; issuer {API}
    RegistrationPOST {API}/oauth/register (RFC 7591), public clients only (token_endpoint_auth_method: none), https redirect URIs or http on localhost
    Authorizationthe Alya Books app's /oauth/authorize; authorization code with PKCE S256 required
    TokenPOST {API}/oauth/token: authorization_code and refresh_token; refresh tokens rotate on every use; codes are single-use and a reused code revokes what it produced
    RevocationPOST {API}/oauth/revoke (RFC 7009)

    Access tokens (oat_…) work on /mcp only; the REST API keeps using API keys. A token belongs to one organisation, so X-Organization-Id cannot switch it. Clients that only look for discovery documents at the host's root, rather than under the API's path, need the API domain to forward /.well-known/… to the API.

    Connect a client with an API key

    {
    "mcpServers": {
      "alyabooks": {
        "type": "http",
        "url": "https://api.alyabooks.ai/api/v1/mcp",
        "headers": { "X-API-KEY": "sk_live_…" }
      }
    }
    }
    
    {
    "mcpServers": {
      "alyabooks": {
        "url": "https://api.alyabooks.ai/api/v1/mcp",
        "headers": { "X-API-KEY": "sk_live_…" }
      }
    }
    }
    
    import { Client } from "@modelcontextprotocol/sdk/client/index.js";
    import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
    
    const transport = new StreamableHTTPClientTransport(new URL("https://api.alyabooks.ai/api/v1/mcp"), {
    requestInit: { headers: { "X-API-KEY": process.env.ALYABOOKS_API_KEY! } },
    });
    const client = new Client({ name: "my-agent", version: "1.0.0" });
    await client.connect(transport);
    const { tools } = await client.listTools();
    const result = await client.callTool({ name: "list_invoices", arguments: { status: "accepted", perPage: 5 } });
    
    curl -X POST https://api.alyabooks.ai/api/v1/mcp -H "X-API-KEY: $KEY" -H "Content-Type: application/json" \
    -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "get_account", "arguments": {} } }'
    

    Claude Code: claude mcp add --transport http alyabooks https://api.alyabooks.ai/api/v1/mcp --header "X-API-KEY: sk_live_…".

    Tools

    149 tools in 17 groups: 72 only read, the rest change data. Tool descriptions state their scope; tools/list returns full JSON Schemas for the arguments and the annotations below.

    GroupTools
    Invoice creationcreate_tax_invoice, create_credit_note, create_debit_note, create_invoices_batch, replace_draft_invoice
    Invoice managementlist_invoices, get_invoice, get_invoice_status, get_invoice_validation, validate_invoice, submit_invoice, send_invoice, download_invoice, delete_draft_invoice, update_invoice_housekeeping, list_design_templates, list_invoice_payments, record_invoice_payment
    Invoice numbersget_next_invoice_number, verify_invoice_number, get_invoice_number_settings, update_invoice_number_settings
    Incoming invoicessimulate_incoming_invoice, import_incoming_invoice, list_incoming_imports, list_incoming_invoices, get_import, get_import_status, get_import_validation, get_import_content, get_import_summary, get_import_line_items, get_import_seller, get_import_buyer, get_import_taxes, get_import_payment_terms, get_import_payment_means, get_import_delivery, get_import_attachments, get_import_source, review_incoming_invoice, update_incoming_invoice, record_incoming_payment, delete_import
    Customerslist_customers, get_customer, create_customer, update_customer, delete_customer, get_next_customer_number, verify_customer_number, lookup_peppol_participant, verify_customer_peppol, list_contact_persons, add_contact_person, update_contact_person, delete_contact_person, list_contact_addresses, add_contact_address, update_contact_address, delete_contact_address, import_customers
    Productslist_products, get_product, create_product, update_product, delete_product, check_product_code, get_product_price, list_bundles, get_bundle, create_bundle, update_bundle, delete_bundle, expand_bundle, list_price_lists, get_price_list, create_price_list, update_price_list, delete_price_list, import_products
    Webhookslist_webhooks, list_webhook_events, create_webhook, update_webhook, delete_webhook, test_webhook, list_webhook_deliveries, retry_webhook_delivery
    Custom fieldslist_custom_fields, create_custom_field, update_custom_field, delete_custom_field, list_field_templates, create_field_template, update_field_template, activate_field_template, delete_field_template
    Projectslist_projects, get_project, create_project, update_project, delete_project, list_project_documents
    Recurring invoiceslist_recurring_invoices, get_recurring_invoice, create_recurring_invoice, update_recurring_invoice, activate_recurring_invoice, pause_recurring_invoice, cancel_recurring_invoice, run_recurring_invoice_now, list_recurring_runs, preview_recurring_invoice, make_invoice_recurring, delete_recurring_invoice
    Settingslist_email_templates, save_email_template, get_options
    VATlist_tax_rates, create_tax_rate, update_tax_rate, list_vat_periods, get_vat_worksheet, list_vat_returns, save_vat_return, reopen_vat_return, check_period_date, export_accounting
    ERP intakedry_run_invoice, dry_run_erp_message, send_erp_message, list_erp_messages, get_erp_message, replay_erp_message, list_erp_connectors, update_erp_connector
    Integrationslist_connectors, sync_connector
    Paymentscreate_payment_link, list_payment_links, cancel_payment_link
    Bank feedslist_bank_transactions, match_bank_transaction, ignore_bank_transaction, import_bank_statement, sync_bank_feeds
    Accountget_account, list_managed_organizations

    Marked destructive (destructiveHint, so clients ask before running them): submit_invoice, send_invoice, delete_draft_invoice, delete_import, delete_customer, delete_contact_person, delete_contact_address, delete_product, delete_bundle, delete_price_list, delete_webhook, delete_custom_field, delete_field_template, delete_project, delete_recurring_invoice, reopen_vat_return, send_erp_message, replay_erp_message. Submitting, sending and replaying reach the FTA or a buyer and cannot be undone; deletes remove data. The dry runs (dry_run_invoice, dry_run_erp_message) are marked read-only: they store nothing.

    Test or live. Every tools/call result carries _meta: { "einvoicing/environment": "test" } or "live", from the key in use. With a test key nothing reaches the live service provider.

    Not available to assistants, on purpose: API keys, team members, the service-provider connection, the company profile, and the adviser's sign-off on an ERP charge map. Those are set in the app by the people responsible.

    Tool results contain the API response as text and as structuredContent; API errors are returned as tool results with isError: true and the usual { code, statusCode, message, extra } envelope, with each validation error's path, explanation and fix, so the assistant can correct its input.

    Resources

    URIContent
    einvoicing://reference/pint-ae-fieldsPINT AE business terms with UBL paths and the API field that carries each
    einvoicing://reference/editor-fieldsOptional field groups and their input keys
    einvoicing://reference/tax-categoriesVAT categories, rates and UBL codes
    einvoicing://reference/statusesInvoice, payment, review and incoming statuses; document kinds; transaction types
    einvoicing://reference/webhook-eventsEvent catalogue
    einvoicing://openapi.jsonThe OpenAPI document
    einvoicing://docs/llms.txtShort integration guide for language models

    Example: a CargoWise message from a chat

    "Here is a UniversalTransaction from our CargoWise UAT. Check it, tell me what fails, fix our charge map and send it."

    The assistant calls dry_run_erp_message with the XML and reads the mapping notes and failed rules (for example "Charge PSC has no mapping"). It proposes the mapping, calls update_erp_connector after you agree, runs the dry run again until it is valid, then calls send_erp_message. With a live key, send_erp_message is marked destructive, so the client asks you before it runs. If the message had already failed in the log, replay_erp_message processes it again instead.

    Example conversation

    "Create a tax invoice for Buyer LLC (TRN 100000000000011) for 10 consulting hours at AED 500, due in 30 days, submit it and tell me when the FTA accepts it."

    The assistant calls create_tax_invoice with submit: true, reads validation and submission from the result, then polls get_invoice_status until invoiceStatus is accepted or rejected. With a webhook on invoice.reported your own system learns about it at the same moment.

    Limits

    • Stateless: no sessions, no subscriptions, no server-initiated messages. Prompts are not provided (prompts/list is empty).
    • Same rate limit as the API (600 requests per minute per key).
    • download_invoice with format: "pdf" returns a download link (url, expiresAt, filename) that works for 15 minutes without a key, so an assistant can hand the PDF to the user instead of a very long base64 string. delivery: "inline" returns the base64 instead, and delivery: "link" gives a link for XML or HTML too. XML and HTML come back as text by default; get_import_source returns binary files as base64 in structuredContent.