agentsclimarketplace

Visma eaccounting skill

Skill jiraporn-junext/visma-eaccounting-skill

Community skill for Visma eAccounting API integration. Comprehensive guide covering OAuth 2.0, invoicing, customers, receipts, and accounting operations. MIT licensed

Install
npx -y skills add jiraporn-junext/visma-eaccounting-skill

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Integration with Visma eAccounting API for bookkeeping and invoicing. Use when the user wants to interact with Visma eAccounting to manage invoices, customers, suppliers, upload receipts/documents, handle accounting operations, or automate bookkeeping tasks. Triggers include mentions of "Visma", "eAccounting", "eEkonomi", invoice management, receipt scanning, bookkeeping automation, or customer/supplier data in Visma context.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

26.6 KB, as published. Nobody here has run it

Visma eAccounting API Integration Skill

⚠️ COMMUNITY SKILL - NOT OFFICIAL VISMA DOCUMENTATION

This is a community-created skill based on public Visma eAccounting API documentation. Always verify code against official Visma documentation before production use.

Skill Version: 1.0.0
Compatible with: Visma eAccounting API v2 (as of February 2026)
Status: Community-maintained
License: MIT

About This Skill

This skill provides comprehensive guidance for integrating with the Visma eAccounting API (v2), covering authentication, invoice management, customer/supplier operations, receipt uploads, and accounting workflows.

Created for: Developers building integrations with Visma eAccounting
Maintained by: Community contributors
Contributions: Welcome! Please verify all code against official Visma API documentation

Overview

Base URLs:

  • Production: https://eaccountingapi.vismaonline.com/v2
  • Sandbox: https://eaccountingapi-sandbox.test.vismaonline.com/v2
  • Documentation: https://eaccountingapi.vismaonline.com/scalar/v2

Key Capabilities:

  • Create and manage customer invoices
  • Handle customer and supplier data
  • Upload receipts and attachments (scanner integration)
  • Manage vouchers and ledger items
  • Access financial reports and accounting data
  • Handle fiscal year operations

Authentication

Visma eAccounting uses OAuth 2.0 with OpenID Connect for authentication.

OAuth Flow (Authorization Code)

Step 1: Authorization Request

const authUrl = new URL('https://identity.vismaonline.com/connect/authorize');
authUrl.searchParams.append('client_id', YOUR_CLIENT_ID);
authUrl.searchParams.append('redirect_uri', YOUR_REDIRECT_URI);
authUrl.searchParams.append('scope', 'ea:api ea:sales ea:purchase ea:accounting offline_access');
authUrl.searchParams.append('response_type', 'code');
authUrl.searchParams.append('state', generateRandomState()); // CSRF protection
authUrl.searchParams.append('prompt', 'select_account');

// Redirect user to authUrl.toString()

Available Scopes:

  • ea:api - Required base scope
  • ea:sales - Sales/invoice operations
  • ea:purchase - Purchase/supplier operations
  • ea:accounting - Accounting operations
  • offline_access - Refresh token (recommended)

Step 2: Exchange Code for Token

const tokenResponse = await fetch('https://identity.vismaonline.com/connect/token', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Authorization': 'Basic ' + Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64')
  },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: authorizationCode,
    redirect_uri: YOUR_REDIRECT_URI
  })
});

const tokens = await tokenResponse.json();
// tokens.access_token - expires in 1 hour
// tokens.refresh_token - long-lived, store securely

Step 3: Refresh Access Token

const refreshResponse = await fetch('https://identity.vismaonline.com/connect/token', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Authorization': 'Basic ' + Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64')
  },
  body: new URLSearchParams({
    grant_type: 'refresh_token',
    refresh_token: storedRefreshToken
  })
});

const newTokens = await refreshResponse.json();
// Store new refresh_token (invalidates old one)

Important Notes:

  • Access tokens expire in 1 hour
  • Each refresh returns a NEW refresh token (invalidates previous)
  • Store refresh tokens securely (database/encrypted storage)
  • Never expose client_secret in client-side code

Making API Requests

Standard Request Pattern

async function callVismaAPI(endpoint, method = 'GET', body = null) {
  const url = `https://eaccountingapi.vismaonline.com/v2${endpoint}`;
  
  const options = {
    method,
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json'
    }
  };
  
  if (body && method !== 'GET') {
    options.body = JSON.stringify(body);
  }
  
  const response = await fetch(url, options);
  
  if (!response.ok) {
    const error = await response.json();
    throw new Error(`API Error: ${error.message || response.statusText}`);
  }
  
  return response.json();
}

Pagination (OData)

Visma API uses OData query parameters:

// Get customers with pagination
const customers = await callVismaAPI('/customers?$top=50&$skip=0');

// Filter examples
const activeCustomers = await callVismaAPI("/customers?$filter=IsActive eq true");
const recentInvoices = await callVismaAPI("/customerinvoices?$filter=InvoiceDate gt 2024-01-01");

// Sorting
const sorted = await callVismaAPI("/customers?$orderby=Name asc");

Customer Management

Create Customer

async function createCustomer(customerData) {
  const customer = {
    Name: customerData.name,
    CorporateIdentityNumber: customerData.orgNumber, // Optional
    Email: customerData.email,
    InvoiceAddress1: customerData.address,
    InvoiceCity: customerData.city,
    InvoicePostalCode: customerData.postalCode,
    InvoiceCountryCode: customerData.countryCode || 'SE',
    Phone: customerData.phone,
    VatNumber: customerData.vatNumber, // EU VAT number if applicable
    IsActive: true
  };
  
  return await callVismaAPI('/customers', 'POST', customer);
}

Get Customer

async function getCustomer(customerId) {
  return await callVismaAPI(`/customers/${customerId}`);
}

async function searchCustomers(searchTerm) {
  // Search by name or number
  return await callVismaAPI(
    `/customers?$filter=contains(Name,'${searchTerm}') or contains(CustomerNumber,'${searchTerm}')`
  );
}

Update Customer

async function updateCustomer(customerId, updates) {
  return await callVismaAPI(`/customers/${customerId}`, 'PUT', updates);
}

Invoice Management

Two Approaches to Creating Invoices

Visma provides two endpoints for invoice creation:

  1. CustomerInvoices - Sales module (recommended for most cases)
  2. CustomerLedgerItems - Direct voucher creation (advanced)

Create Invoice Draft

async function createInvoiceDraft(invoiceData) {
  const draft = {
    CustomerId: invoiceData.customerId,
    InvoiceDate: invoiceData.invoiceDate || new Date().toISOString().split('T')[0],
    DueDate: invoiceData.dueDate,
    DeliveryDate: invoiceData.deliveryDate,
    YourReference: invoiceData.yourReference,
    OurReference: invoiceData.ourReference,
    InvoiceRows: invoiceData.rows.map(row => ({
      ArticleId: row.articleId, // Optional - can use ArticleNumber instead
      ArticleNumber: row.articleNumber,
      Description: row.description,
      Quantity: row.quantity,
      UnitPrice: row.unitPrice,
      VatPercent: row.vatPercent || 25, // Default Swedish VAT
      DiscountPercent: row.discountPercent || 0
    }))
  };
  
  return await callVismaAPI('/customerinvoicedrafts', 'POST', draft);
}

Convert Draft to Invoice and Send

async function convertDraftToInvoice(draftId, sendType = 'Manual') {
  // sendType options: 'Manual', 'Email', 'EInvoice', 'AutoInvoice'
  const invoice = {
    CreatedFromDraftId: draftId,
    SendType: sendType
  };
  
  return await callVismaAPI('/customerinvoices', 'POST', invoice);
}

Get Invoice

async function getInvoice(invoiceId) {
  return await callVismaAPI(`/customerinvoices/${invoiceId}`);
}

async function getInvoicePDF(invoiceId) {
  const pdfData = await callVismaAPI(`/customerinvoices/${invoiceId}/pdf`);
  // pdfData.Url or pdfData.TemporaryUrl - download link
  return pdfData.TemporaryUrl;
}

Send Invoice by Email

async function sendInvoiceByEmail(invoiceId, emailAddress) {
  return await callVismaAPI(
    `/customerinvoices/${invoiceId}/email`,
    'POST',
    { EmailAddress: emailAddress }
  );
}

Credit Invoice (Create Credit Note)

async function creditInvoice(invoiceId, creditRows) {
  const credit = {
    InvoiceId: invoiceId,
    CreditRows: creditRows.map(row => ({
      RowId: row.originalRowId,
      CreditedAmount: row.amount
    }))
  };
  
  return await callVismaAPI('/customerinvoices/credit', 'POST', credit);
}

Void Invoice

async function voidInvoice(invoiceId) {
  return await callVismaAPI(`/customerinvoices/${invoiceId}/void`, 'POST');
}

Receipt and Document Upload

Visma eAccounting supports attaching documents to various entities (invoices, vouchers, etc.).

Upload Receipt/Attachment

async function uploadDocument(fileBuffer, fileName, mimeType) {
  // Step 1: Upload file to storage
  const formData = new FormData();
  formData.append('file', fileBuffer, fileName);
  
  const uploadResponse = await fetch(
    'https://eaccountingapi.vismaonline.com/v2/files',
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${accessToken}`
      },
      body: formData
    }
  );
  
  const uploadResult = await uploadResponse.json();
  return uploadResult.FileId; // Use this to attach to entities
}

async function attachToInvoiceDraft(draftId, fileId, description) {
  const attachment = {
    CustomerInvoiceDraftId: draftId,
    FileId: fileId,
    Description: description || 'Receipt'
  };
  
  return await callVismaAPI('/salesdocumentattachments', 'POST', attachment);
}

Scanner Integration Pattern

For Visma Scanner integration (receipts → supplier invoices):

async function createSupplierInvoiceFromReceipt(receiptData) {
  // Upload receipt image/PDF
  const fileId = await uploadDocument(
    receiptData.fileBuffer,
    receiptData.fileName,
    receiptData.mimeType
  );
  
  // Create supplier invoice draft
  const supplierInvoice = {
    SupplierId: receiptData.supplierId,
    InvoiceDate: receiptData.invoiceDate,
    DueDate: receiptData.dueDate,
    InvoiceNumber: receiptData.invoiceNumber,
    TotalAmount: receiptData.totalAmount,
    VatAmount: receiptData.vatAmount,
    SupplierInvoiceRows: receiptData.rows.map(row => ({
      AccountNumber: row.accountNumber,
      Description: row.description,
      Amount: row.amount,
      VatPercent: row.vatPercent
    }))
  };
  
  const invoice = await callVismaAPI('/supplierinvoices', 'POST', supplierInvoice);
  
  // Attach receipt
  await callVismaAPI('/purchasedocumentattachments', 'POST', {
    SupplierInvoiceId: invoice.Id,
    FileId: fileId,
    Description: 'Receipt'
  });
  
  return invoice;
}

Accounting Operations

Create Voucher with Customer Ledger Items

For more advanced accounting, use ledger items directly:

async function createVoucherWithInvoice(voucherData) {
  const voucher = {
    TransactionDate: voucherData.date,
    Description: voucherData.description,
    CustomerLedgerItems: [{
      CustomerId: voucherData.customerId,
      InvoiceNumber: voucherData.invoiceNumber,
      InvoiceDate: voucherData.invoiceDate,
      DueDate: voucherData.dueDate,
      Amount: voucherData.amount,
      VatAmount: voucherData.vatAmount,
      Rows: voucherData.rows.map(row => ({
        AccountNumber: row.accountNumber,
        Amount: row.amount,
        VatCode: row.vatCode
      }))
    }]
  };
  
  return await callVismaAPI('/v2/vouchers', 'POST', voucher);
}

Update Opening Balances

async function updateOpeningBalances(balances) {
  // Only works on first fiscal year
  const payload = balances.map(balance => ({
    AccountNumber: balance.accountNumber,
    Balance: balance.balance
  }));
  
  return await callVismaAPI('/fiscalyears/openingbalances', 'PUT', payload);
}

Get Fiscal Years

async function getFiscalYears() {
  return await callVismaAPI('/fiscalyears');
}

Articles/Products

Create Article

async function createArticle(articleData) {
  const article = {
    Number: articleData.number,
    Name: articleData.name,
    Description: articleData.description,
    SalesPrice: articleData.salesPrice,
    VatRate: articleData.vatRate || 25,
    SalesAccount: articleData.salesAccount || 3000,
    IsActive: true,
    Unit: articleData.unit || 'pcs'
  };
  
  return await callVismaAPI('/articles', 'POST', article);
}

Suppliers

Create Supplier

async function createSupplier(supplierData) {
  const supplier = {
    Name: supplierData.name,
    CorporateIdentityNumber: supplierData.orgNumber,
    Email: supplierData.email,
    Address: supplierData.address,
    City: supplierData.city,
    PostalCode: supplierData.postalCode,
    CountryCode: supplierData.countryCode || 'SE',
    Phone: supplierData.phone,
    BankAccountNumber: supplierData.bankAccount,
    IsActive: true
  };
  
  return await callVismaAPI('/suppliers', 'POST', supplier);
}

Error Handling

Common Error Patterns

async function robustAPICall(endpoint, method = 'GET', body = null, retries = 3) {
  for (let attempt = 0; attempt < retries; attempt++) {
    try {
      const response = await fetch(
        `https://eaccountingapi.vismaonline.com/v2${endpoint}`,
        {
          method,
          headers: {
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': 'application/json'
          },
          body: body ? JSON.stringify(body) : undefined
        }
      );
      
      // Handle 401 - Token expired
      if (response.status === 401) {
        accessToken = await refreshAccessToken();
        continue; // Retry with new token
      }
      
      // Handle 429 - Rate limit
      if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After') || 5;
        await sleep(retryAfter * 1000);
        continue;
      }
      
      if (!response.ok) {
        const error = await response.json();
        throw new VismaAPIError(error.message, response.status, error);
      }
      
      return await response.json();
      
    } catch (error) {
      if (attempt === retries - 1) throw error;
      await sleep(1000 * Math.pow(2, attempt)); // Exponential backoff
    }
  }
}

class VismaAPIError extends Error {
  constructor(message, statusCode, details) {
    super(message);
    this.name = 'VismaAPIError';
    this.statusCode = statusCode;
    this.details = details;
  }
}

Common Error Codes

  • 400 - Bad Request (validation error, check request body)
  • 401 - Unauthorized (token expired or invalid)
  • 403 - Forbidden (insufficient scope/permissions)
  • 404 - Not Found (resource doesn't exist)
  • 409 - Conflict (e.g., duplicate invoice number)
  • 429 - Too Many Requests (rate limit exceeded)
  • 500 - Internal Server Error (Visma issue, retry)

Best Practices

1. Token Management

class VismaTokenManager {
  constructor(clientId, clientSecret) {
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.accessToken = null;
    this.refreshToken = null;
    this.expiresAt = null;
  }
  
  async getValidToken() {
    // Return cached token if still valid (with 5 min buffer)
    if (this.accessToken && this.expiresAt > Date.now() + 300000) {
      return this.accessToken;
    }
    
    // Refresh if we have a refresh token
    if (this.refreshToken) {
      await this.refresh();
      return this.accessToken;
    }
    
    throw new Error('No valid token or refresh token available');
  }
  
  async refresh() {
    const response = await fetch('https://identity.vismaonline.com/connect/token', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Authorization': 'Basic ' + 
          Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64')
      },
      body: new URLSearchParams({
        grant_type: 'refresh_token',
        refresh_token: this.refreshToken
      })
    });
    
    const tokens = await response.json();
    this.accessToken = tokens.access_token;
    this.refreshToken = tokens.refresh_token; // Store new refresh token!
    this.expiresAt = Date.now() + (tokens.expires_in * 1000);
    
    // Persist to secure storage
    await this.saveTokens();
  }
  
  async saveTokens() {
    // Implement secure storage (database, encrypted file, etc.)
  }
}

2. Company Selection

If user has multiple companies, they must select one during OAuth:

// In authorization URL, after successful login:
// User will see company dropdown if they have multiple companies
// To avoid this, ensure they've set a default company in settings

// To programmatically handle this, you may need to:
// 1. Get company list after initial auth
// 2. Store selected company ID
// 3. Use company-specific endpoints

3. Webhook Alternative

Visma eAccounting doesn't have native webhooks. For real-time sync:

  • Poll endpoints periodically
  • Use $filter with timestamps: ModifiedUtc gt 2024-01-01T00:00:00Z
  • Implement change tracking in your database

4. Testing with Sandbox

Always test with sandbox first:

  • Sandbox URL: https://eaccountingapi-sandbox.test.vismaonline.com/v2
  • Sandbox login: https://eaccounting-sandbox.test.vismaonline.com
  • Create test data without affecting production

Complete Example: Invoice Workflow

class VismaInvoiceManager {
  constructor(tokenManager) {
    this.tokenManager = tokenManager;
    this.baseUrl = 'https://eaccountingapi.vismaonline.com/v2';
  }
  
  async createAndSendInvoice(invoiceData) {
    const token = await this.tokenManager.getValidToken();
    
    try {
      // 1. Create draft
      console.log('Creating invoice draft...');
      const draft = await this.apiCall('/customerinvoicedrafts', 'POST', {
        CustomerId: invoiceData.customerId,
        InvoiceDate: new Date().toISOString().split('T')[0],
        DueDate: invoiceData.dueDate,
        YourReference: invoiceData.reference,
        InvoiceRows: invoiceData.items.map(item => ({
          Description: item.description,
          Quantity: item.quantity,
          UnitPrice: item.price,
          VatPercent: 25
        }))
      });
      
      // 2. Upload attachments if any
      if (invoiceData.attachments?.length > 0) {
        console.log('Uploading attachments...');
        for (const attachment of invoiceData.attachments) {
          const fileId = await this.uploadFile(attachment);
          await this.apiCall('/salesdocumentattachments', 'POST', {
            CustomerInvoiceDraftId: draft.Id,
            FileId: fileId,
            Description: attachment.description
          });
        }
      }
      
      // 3. Convert to invoice and send
      console.log('Converting to invoice and sending...');
      const invoice = await this.apiCall('/customerinvoices', 'POST', {
        CreatedFromDraftId: draft.Id,
        SendType: 'Email'
      });
      
      console.log(`Invoice ${invoice.InvoiceNumber} created and sent!`);
      return invoice;
      
    } catch (error) {
      console.error('Invoice creation failed:', error);
      throw error;
    }
  }
  
  async apiCall(endpoint, method, body) {
    const token = await this.tokenManager.getValidToken();
    const response = await fetch(`${this.baseUrl}${endpoint}`, {
      method,
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: body ? JSON.stringify(body) : undefined
    });
    
    if (!response.ok) {
      const error = await response.json();
      throw new Error(`API Error: ${error.message || response.statusText}`);
    }
    
    return response.json();
  }
  
  async uploadFile(attachment) {
    const token = await this.tokenManager.getValidToken();
    const formData = new FormData();
    formData.append('file', attachment.buffer, attachment.filename);
    
    const response = await fetch(`${this.baseUrl}/files`, {
      method: 'POST',
      headers: { 'Authorization': `Bearer ${token}` },
      body: formData
    });
    
    const result = await response.json();
    return result.FileId;
  }
}

Resources


Troubleshooting

"Startup guide not completed" Error

The company must complete the startup guide in eAccounting before API access works. Solution: Log into eAccounting web interface, complete the setup wizard.

Cannot Select Company

If "Choose default company" dropdown is missing during OAuth, the user has a default company set. Solution: In eAccounting, go to profile menu → "Choose default company" → Remove default (click star).

Rate Limiting

The API has rate limits. Implement exponential backoff and respect Retry-After headers.

PDF URLs Changing

PDF URL format changed in late 2024. Always use the latest URL from the API response, don't cache URL patterns.


Implementation Checklist

When implementing Visma eAccounting integration:

  • Set up OAuth 2.0 with correct scopes
  • Implement secure token storage and refresh logic
  • Handle token expiration gracefully (401 errors)
  • Test with sandbox environment first
  • Implement error handling and retries
  • Add logging for debugging
  • Handle rate limiting (429 errors)
  • Validate data before sending to API
  • Test with multiple companies if applicable
  • Document which scopes your integration requires
  • Implement proper file upload handling for receipts
  • Consider polling strategy for data sync (no webhooks)

Quick Reference: Common Endpoints

OperationMethodEndpoint
List customersGET/customers
Create customerPOST/customers
Get customerGET/customers/{id}
List invoicesGET/customerinvoices
Create invoice draftPOST/customerinvoicedrafts
Convert draftPOST/customerinvoices
Get invoice PDFGET/customerinvoices/{id}/pdf
Send invoice emailPOST/customerinvoices/{id}/email
Credit invoicePOST/customerinvoices/credit
Void invoicePOST/customerinvoices/{id}/void
Upload filePOST/files
Attach to invoicePOST/salesdocumentattachments
List suppliersGET/suppliers
Create supplier invoicePOST/supplierinvoices
Attach receiptPOST/purchasedocumentattachments
List articlesGET/articles
Create articlePOST/articles
Get fiscal yearsGET/fiscalyears
Update opening balancePUT/fiscalyears/openingbalances

This skill provides the foundation for building robust Visma eAccounting integrations. Remember to always test in the sandbox environment and handle errors gracefully!


Important Disclaimers

Not Official Visma Documentation

This skill is created by the community and is not officially endorsed by Visma. Always refer to the official Visma API documentation for the most accurate and up-to-date information.

No Warranty

This skill is provided "as-is" without any warranties, express or implied. The creators and contributors are not responsible for any issues, data loss, or problems arising from the use of code examples provided in this skill.

Security Notice

  • Never commit API credentials to version control
  • Always use environment variables for sensitive data
  • Implement proper authentication and authorization
  • Follow Visma's security best practices
  • Test thoroughly in sandbox before production deployment

API Changes

The Visma eAccounting API may change over time. This skill was last updated in February 2026 for API v2. Always verify:

  • Endpoint URLs and methods
  • Request/response formats
  • Authentication requirements
  • Available features and scopes

Testing Requirements

Before deploying to production:

  • Test all workflows in Visma's sandbox environment
  • Verify error handling with various scenarios
  • Validate data integrity
  • Ensure proper token refresh mechanisms
  • Test with realistic data volumes

Support

For official Visma API support, contact: [email protected]

For questions about this skill:

  • Check the Visma Community Forums
  • Verify against official documentation
  • Contribute improvements via community channels

License (MIT)

MIT License

Copyright (c) 2026 Community Contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Changelog

Version 1.0.0 (2026-02-07)

  • Initial release
  • Complete OAuth 2.0 authentication flow
  • Invoice management (create, send, credit, void)
  • Customer and supplier CRUD operations
  • Receipt upload and attachment workflows
  • Accounting operations (vouchers, fiscal years)
  • Error handling patterns
  • JavaScript/Node.js code examples
  • 8 evaluation test cases

Skill Version: 1.0.0
Last Updated: February 7, 2026
API Compatibility: Visma eAccounting API v2
Language: JavaScript/Node.js
Status: Community-maintained
Contributions: Welcome!

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.