Skip to content

API Authentication

The ADA API uses JWT-based authentication for the /authenticates/api-code endpoint.

Quick Start

  1. Obtain your API code and register your public key with Visma Amili AB.
  2. Generate a key pair using one of the supported algorithms (ES256, RS256, RS384, RS512, ES384, or ES512).
  3. Create a JWT signed with your private key.
  4. Authenticate and receive an access token.
  5. Use the access token in the X-API-Key header for all subsequent API requests.
  6. 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 a key pair using one of the supported algorithms
  • Register your public key with Visma Amili AB
  • Obtain your API code from Visma Amili AB

Supported Algorithms:

The API supports the following JWT signing algorithms:

  • ES256 - ECDSA using P-256 curve and SHA-256 hash
  • ES384 - ECDSA using P-384 curve and SHA-384 hash
  • ES512 - ECDSA using P-521 curve and SHA-512 hash
  • RS256 - RSA signature with SHA-256 hash
  • RS384 - RSA signature with SHA-384 hash
  • RS512 - RSA signature with SHA-512 hash

Key Generation Examples:

bash
# Generate private key for ES256
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
bash
# Generate private key for ES384
openssl ecparam -name secp384r1 -genkey -noout -out jwt.private.ec.key

# Generate public key
openssl ec -in jwt.private.ec.key -pubout -out jwt.public.ec.key
bash
# Generate private key for ES512
openssl ecparam -name secp521r1 -genkey -noout -out jwt.private.ec.key

# Generate public key
openssl ec -in jwt.private.ec.key -pubout -out jwt.public.ec.key
bash
# Generate private key for RS256
openssl genrsa -out jwt.private.rsa.key 2048

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

TIP

This documentation uses ES256 in the code examples, but you can use any of the supported algorithms. Make sure to use the same algorithm when signing your JWT as the one you registered with your public key.

JWT Token Structure:

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

Implementation Details:

  • JWT should expire after 10 minutes (set exp claim to UNIX timestamp)
  • Requires a private key matching one of the supported algorithms
  • The JWT is signed with the private key and sent in the X-API-Key header
  • The algorithm used must match the public key you registered with Visma Amili AB

Example from code:

python
from datetime import datetime, timedelta
import jwt

exp = int((datetime.utcnow() + timedelta(minutes=10)).timestamp())
# Use the algorithm matching your registered public key (ES256, RS256, etc.)
token = jwt.encode({"api_code": api_code, "exp": exp}, private_key, algorithm='ES256')

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

Response Format

The endpoint returns the following 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: 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,
    }

    // Use the algorithm matching your registered public key
    const jwt_token = jwt.sign(payload, this.privateKey, { algorithm: 'ES256' })

    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}

        # Use the algorithm matching your registered public key
        jwt_token = jwt.encode(payload, self.private_key, algorithm='ES256')

        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()
        };

        // Use the algorithm matching your registered public key
        var jwtToken = JWT.Encode(payload, _privateKey, JwsAlgorithm.ES256);

        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

  • Always use HTTPS for all API communications
  • Keep your private key secure and never expose it in client-side code
  • Use strong key sizes (minimum 2048 bits for RSA, P-256 or higher for ECDSA)
  • Store private keys securely using key management systems or encrypted storage