# Visma Eaccounting

> 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.

- Skill: `jiraporn-junext/visma-eaccounting` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add jiraporn-junext/visma-eaccounting`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jiraporn-junext/visma-eaccounting/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Finance & Business
- License: MIT
- Author: jiraporn-junext (https://skillmd.com/u/jiraporn-junext)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/jiraporn-junext/visma-eaccounting

---


# 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](https://eaccountingapi.vismaonline.com/scalar/v2) 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**
```javascript
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**
```javascript
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**
```javascript
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

```javascript
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:

```javascript
// 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

```javascript
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

```javascript
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

```javascript
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

```javascript
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

```javascript
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

```javascript
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

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

### Credit Invoice (Create Credit Note)

```javascript
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

```javascript
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

```javascript
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):

```javascript
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:

```javascript
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

```javascript
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

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

---

## Articles/Products

### Create Article

```javascript
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

```javascript
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

```javascript
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

```javascript
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:

```javascript
// 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

```javascript
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

- **API Documentation**: https://eaccountingapi.vismaonline.com/scalar/v2
- **Developer Portal**: https://developer.vismaonline.com
- **Community Forum**: https://community.visma.com/t5/Visma-eAccounting-API/ct-p/IN_MA_eAccountingAPI
- **Authentication Guide**: https://developer.vismaonline.com/docs/authentication
- **Support**: eaccountingapi@visma.com

---

## 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

| Operation | Method | Endpoint |
|-----------|--------|----------|
| List customers | GET | `/customers` |
| Create customer | POST | `/customers` |
| Get customer | GET | `/customers/{id}` |
| List invoices | GET | `/customerinvoices` |
| Create invoice draft | POST | `/customerinvoicedrafts` |
| Convert draft | POST | `/customerinvoices` |
| Get invoice PDF | GET | `/customerinvoices/{id}/pdf` |
| Send invoice email | POST | `/customerinvoices/{id}/email` |
| Credit invoice | POST | `/customerinvoices/credit` |
| Void invoice | POST | `/customerinvoices/{id}/void` |
| Upload file | POST | `/files` |
| Attach to invoice | POST | `/salesdocumentattachments` |
| List suppliers | GET | `/suppliers` |
| Create supplier invoice | POST | `/supplierinvoices` |
| Attach receipt | POST | `/purchasedocumentattachments` |
| List articles | GET | `/articles` |
| Create article | POST | `/articles` |
| Get fiscal years | GET | `/fiscalyears` |
| Update opening balance | PUT | `/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](https://eaccountingapi.vismaonline.com/scalar/v2) 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: **eaccountingapi@visma.com**

For questions about this skill:
- Check the [Visma Community Forums](https://community.visma.com/t5/Visma-eAccounting-API/ct-p/IN_MA_eAccountingAPI)
- 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!

