Skip to content

API Authentication

The ADA API provides two authentication methods for the /authenticates/api-code endpoint

Quick Start

  1. Obtain your API code and register your public key (for GET method) or static IP (for POST method) with Visma Amili AB.
  2. Choose authentication method:
    • GET: Use JWT signed with your private key.
    • POST: Use your API code in the request body (from a registered static IP).
  3. Authenticate and receive an access token.
  4. Use the access token in the X-API-Key header for all subsequent API requests.
  5. Refresh token as needed.

GET /authenticates/api-code (JWT-based Authentication)

Purpose: Authenticate using a JWT (JSON Web Token) signed with a private key.

Method: GET

Headers:

  • X-API-Key: JWT token containing the API code and expiration

Prerequisites:

  • Generate an EC key pair (ES256 algorithm)
  • Register your public key with Visma Amili AB
  • Obtain your API code from Visma Amili AB

Key Generation:

bash
# Generate private key
openssl ecparam -name prime256v1 -genkey -noout -out jwt.private.ec.key

# Generate public key
openssl ec -in jwt.private.ec.key -pubout -out jwt.public.ec.key

JWT Token Structure:

json
{
  "api_code": "your-api-code",
  "exp": "expiration-timestamp"
}

Implementation Details:

  • Uses ES512 algorithm for JWT signing
  • JWT should expire after 10 minutes (set exp claim to UNIX timestamp)
  • Requires a private key (EC key pair)
  • The JWT is signed with the private key and sent in the X-API-Key header

Example from code:

python
from datetime import datetime, timedelta
import jwt

exp = int((datetime.utcnow() + timedelta(minutes=10)).timestamp())
token = jwt.encode({"api_code": api_code, "exp": exp}, private_key, algorithm='ES512')

auth = requests.get(
    url=f"{self.api_url}/authenticates/api-code",
    headers={"X-API-Key": token}
)

POST /authenticates/api-code (Simple API Code Authentication)

Purpose: Authenticate using a simple API code without JWT.

Method: POST

Content-Type: application/json

Prerequisites:

  • Register your static IP address with Visma Amili AB
  • Obtain your API code from Visma Amili AB

Request Body:

json
{
  "code": "your-api-code"
}

Implementation Details:

  • Simpler authentication method
  • No private key required
  • Direct API code submission in JSON body
  • Requires requests to originate from a registered static IP address

Example from code:

python
auth = requests.post(
    url=f"{self.api_url}/authenticates/api-code",
    json={"code": self.api_code}
)

Response Format

Both endpoints return the same response structure upon successful authentication:

json
{
  "token": "access-token-for-subsequent-requests"
}

Usage in the API

The returned access token is used for all subsequent API requests in the X-API-Key header.

Example use on POST /case-registrations:

python
auth = requests.post(
    url=f"{api_url}/case--registrations",
    headers={"X-API-Key": token},
    json=case_data
)

Token Expiry and Refresh

  • JWT tokens (GET method): Should expire after 10 minutes for security
  • Access tokens (returned by API): Can expire at any time, clients must handle expiry gracefully
  • When the access token expires, you must re-authenticate to obtain a new token

Handling Token Expiry

Here's an example of how to handle token expiry and refresh in your code:

typescript
import axios, { AxiosError } from 'axios'
import jwt from 'jsonwebtoken'
import { readFileSync } from 'fs'

interface TokenInfo {
  token: string
  expiryTime: number // in milliseconds
}

class AuthTokenProvider {
  private tokenInfo: TokenInfo | null = null
  private readonly apiCode: string
  private readonly privateKey: Buffer

  constructor(apiCode: string, privateKeyPath: string) {
    this.apiCode = apiCode
    this.privateKey = readFileSync(privateKeyPath)
  }

  private async getNewAccessToken(): Promise<TokenInfo> {
    // Create JWT with 10-minute expiry
    const payload = {
      api_code: this.apiCode,
      exp: Math.floor(Date.now() / 1000) + 10 * 60,
    }

    const jwt_token = jwt.sign(payload, this.privateKey, { algorithm: 'ES512' })

    const response = await axios.get(
      'https://api-sandbox.amili.se/authenticates/api-code',
      {
        headers: { 'X-API-Key': jwt_token },
      }
    )

    const token = response.data.token

    // Decode token to get expiry time
    const decodedToken = jwt.decode(token)
    if (!decodedToken || typeof decodedToken === 'string') {
      throw new Error('Invalid token format received from server')
    }

    return {
      token,
      expiryTime: (decodedToken.exp || 0) * 1000, // Convert to milliseconds
    }
  }

  async getValidToken(): Promise<string> {
    // If we don't have a token or it's expiring soon, get a new one
    if (
      !this.tokenInfo ||
      Date.now() + 5 * 60 * 1000 >= this.tokenInfo.expiryTime
    ) {
      this.tokenInfo = await this.getNewAccessToken()
    }

    return this.tokenInfo.token
  }
}

// Example usage:
async function makeApiCall() {
  const auth = new AuthTokenProvider('your-api-code', 'path/to/private.key')

  try {
    // Get a valid token
    const token = await auth.getValidToken()

    // Use the token for your API call
    const response = await axios.get(
      'https://api-sandbox.amili.se/invoice/123',
      {
        headers: { 'X-API-Key': token },
      }
    )

    return response.data
  } catch (error) {
    // Handle errors appropriately
    console.error('API call failed:', error)
    throw error
  }
}
python
import jwt
import requests
from datetime import datetime, timedelta

from dataclasses import dataclass

@dataclass
class TokenInfo:
    token: str
    expiry_time: int  # in milliseconds

class AuthTokenProvider:
    def __init__(self, api_code: str, private_key_path: str):
        self.api_code = api_code
        with open(private_key_path, 'r') as f:
            self.private_key = f.read()
        self.token_info: TokenInfo | None = None

    def _get_new_access_token(self) -> TokenInfo:
        # Create JWT with 10-minute expiry
        exp = int((datetime.utcnow() + timedelta(minutes=10)).timestamp())
        payload = {"api_code": self.api_code, "exp": exp}

        jwt_token = jwt.encode(payload, self.private_key, algorithm='ES512')

        response = requests.get(
            'https://api-sandbox.amili.se/authenticates/api-code',
            headers={'X-API-Key': jwt_token}
        )
        response.raise_for_status()

        token = response.json()['token']

        # Decode token to get expiry time
        decoded_token = jwt.decode(token, options={"verify_signature": False})
        if not isinstance(decoded_token, dict):
            raise ValueError('Invalid token format received from server')

        return TokenInfo(
            token=token,
            expiry_time=int(decoded_token.get('exp', 0)) * 1000  # Convert to milliseconds
        )

    def get_valid_token(self) -> str:
        # If we don't have a token or it's expiring soon, get a new one
        if (not self.token_info or
            int(datetime.now().timestamp() * 1000) + 5 * 60 * 1000 >= self.token_info.expiry_time):
            self.token_info = self._get_new_access_token()

        return self.token_info.token

# Example usage:
def make_api_call():
    auth = AuthTokenProvider('your-api-code', 'path/to/private.key')

    try:
        # Get a valid token
        token = auth.get_valid_token()

        # Use the token for your API call
        response = requests.get(
            'https://api-sandbox.amili.se/invoice/123',
            headers={'X-API-Key': token}
        )
        response.raise_for_status()

        return response.json()
    except Exception as e:
        # Handle errors appropriately
        print('API call failed:', e)
        raise
csharp
using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Jose;
using Jose.Jws;

public class TokenInfo
{
    public string Token { get; set; }
    public long ExpiryTime { get; set; } // in milliseconds
}

public class AuthTokenProvider
{
    private TokenInfo _tokenInfo;
    private readonly string _apiCode;
    private readonly string _privateKey;

    public AuthTokenProvider(string apiCode, string privateKeyPath)
    {
        _apiCode = apiCode;
        _privateKey = File.ReadAllText(privateKeyPath);
    }

    private async Task<TokenInfo> GetNewAccessTokenAsync()
    {
        // Create JWT with 10-minute expiry
        var payload = new
        {
            api_code = _apiCode,
            exp = DateTimeOffset.UtcNow.AddMinutes(10).ToUnixTimeSeconds()
        };

        var jwtToken = JWT.Encode(payload, _privateKey, JwsAlgorithm.ES512);

        using var httpClient = new HttpClient();
        httpClient.DefaultRequestHeaders.Add("X-API-Key", jwtToken);

        var response = await httpClient.GetAsync("https://api-sandbox.amili.se/authenticates/api-code");
        response.EnsureSuccessStatusCode();

        var responseContent = await response.Content.ReadAsStringAsync();
        var responseData = JsonSerializer.Deserialize<JsonElement>(responseContent);
        var token = responseData.GetProperty("token").GetString();

        // Decode token to get expiry time
        var decodedToken = JWT.Decode(token);
        var tokenData = JsonSerializer.Deserialize<JsonElement>(decodedToken);

        if (!tokenData.TryGetProperty("exp", out var expProperty))
        {
            throw new InvalidOperationException("Invalid token format received from server");
        }

        return new TokenInfo
        {
            Token = token,
            ExpiryTime = expProperty.GetInt64() * 1000 // Convert to milliseconds
        };
    }

    public async Task<string> GetValidTokenAsync()
    {
        // If we don't have a token or it's expiring soon, get a new one
        if (_tokenInfo == null ||
            DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + 5 * 60 * 1000 >= _tokenInfo.ExpiryTime)
        {
            _tokenInfo = await GetNewAccessTokenAsync();
        }

        return _tokenInfo.Token;
    }
}

// Example usage:
public static async Task<object> MakeApiCallAsync()
{
    var auth = new AuthTokenProvider("your-api-code", "path/to/private.key");

    try
    {
        // Get a valid token
        var token = await auth.GetValidTokenAsync();

        // Use the token for your API call
        using var httpClient = new HttpClient();
        httpClient.DefaultRequestHeaders.Add("X-API-Key", token);

        var response = await httpClient.GetAsync("https://api-sandbox.amili.se/invoice/123");
        response.EnsureSuccessStatusCode();

        var responseContent = await response.Content.ReadAsStringAsync();
        return JsonSerializer.Deserialize<object>(responseContent);
    }
    catch (Exception ex)
    {
        // Handle errors appropriately
        Console.WriteLine($"API call failed: {ex.Message}");
        throw;
    }
}

Security Considerations

  • GET method (JWT): More secure as it uses cryptographic signatures
  • POST method: Requires static IP registration for security
  • Always use HTTPS for all API communications
  • Keep your private key secure and never expose it in client-side code