Skip to content

API Authentication

ADA API använder JWT-baserad autentisering för /authenticates/api-code endpoint.

Snabbstart

  1. Skaffa din API-kod och registrera din publika nyckel med Visma Amili AB.
  2. Generera ett nyckelpar med en av de stödda algoritmerna (ES256, RS256, RS384, RS512, ES384, eller ES512).
  3. Skapa en JWT signerad med din privata nyckel.
  4. Autentisera och ta emot en access token.
  5. Använd access token i X-API-Key header för alla efterföljande API-requests.
  6. Uppdatera token vid behov.

GET /authenticates/api-code (JWT-baserad autentisering)

Syfte: Autentisera med en JWT (JSON Web Token) signerad med en privat nyckel.

Method: GET

Headers:

  • X-API-Key: JWT token som innehåller API-koden och utgångstid

Förutsättningar:

  • Generera ett nyckelpar med en av de stödda algoritmerna
  • Registrera din publika nyckel med Visma Amili AB
  • Skaffa din API-kod från Visma Amili AB

Stödda algoritmer:

API:et stödjer följande JWT-signeringsalgoritmer:

  • ES256 - ECDSA med P-256 kurva och SHA-256 hash
  • ES384 - ECDSA med P-384 kurva och SHA-384 hash
  • ES512 - ECDSA med P-521 kurva och SHA-512 hash
  • RS256 - RSA-signatur med SHA-256 hash
  • RS384 - RSA-signatur med SHA-384 hash
  • RS512 - RSA-signatur med SHA-512 hash

Exempel på nyckelgenerering:

bash
# Generera privat nyckel för ES256
openssl ecparam -name prime256v1 -genkey -noout -out jwt.private.ec.key

# Generera publik nyckel
openssl ec -in jwt.private.ec.key -pubout -out jwt.public.ec.key
bash
# Generera privat nyckel för ES384
openssl ecparam -name secp384r1 -genkey -noout -out jwt.private.ec.key

# Generera publik nyckel
openssl ec -in jwt.private.ec.key -pubout -out jwt.public.ec.key
bash
# Generera privat nyckel för ES512
openssl ecparam -name secp521r1 -genkey -noout -out jwt.private.ec.key

# Generera publik nyckel
openssl ec -in jwt.private.ec.key -pubout -out jwt.public.ec.key
bash
# Generera privat nyckel för RS256
openssl genrsa -out jwt.private.rsa.key 2048

# Generera publik nyckel
openssl rsa -in jwt.private.rsa.key -pubout -out jwt.public.rsa.key

TIP

Denna dokumentation använder ES256 i kodexemplen, men du kan använda vilken som helst av de stödda algoritmerna. Se till att använda samma algoritm när du signerar din JWT som den du registrerade med din publika nyckel.

JWT Token-struktur:

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

Implementeringsdetaljer:

  • JWT bör gå ut efter 10 minuter (sätt exp claim till UNIX timestamp)
  • Kräver en privat nyckel som matchar en av de stödda algoritmerna
  • JWT:en signeras med den privata nyckeln och skickas i X-API-Key header
  • Algoritmen som används måste matcha den publika nyckel du registrerade med Visma Amili AB

Exempel från kod:

python
from datetime import datetime, timedelta
import jwt

exp = int((datetime.utcnow() + timedelta(minutes=10)).timestamp())
# Använd algoritmen som matchar din registrerade publika nyckel (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

Endpointen returnerar följande response-struktur vid lyckad autentisering:

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

Användning i API:et

Den returnerade access token används för alla efterföljande API-requests i X-API-Key header.

Exempel användning på POST /case-registrations:

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

Token-utgång och uppdatering

  • JWT tokens: Bör gå ut efter 10 minuter av säkerhetsskäl
  • Access tokens (returnerade av API): Kan gå ut när som helst, klienter måste hantera utgång smidigt
  • När access token går ut måste du autentisera igen för att få en ny token

Hantera token-utgång

Här är ett exempel på hur du hanterar token-utgång och uppdatering i din kod:

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,
    }

    // Använd algoritmen som matchar din registrerade publika nyckel
    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}

        # Använd algoritmen som matchar din registrerade publika nyckel
        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()
        };

        // Använd algoritmen som matchar din registrerade publika nyckel
        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;
    }
}

Säkerhetsöverväganden

  • Använd alltid HTTPS för all API-kommunikation
  • Håll din privata nyckel säker och exponera den aldrig i client-side kod
  • Använd starka nyckelstorlekar (minimum 2048 bitar för RSA, P-256 eller högre för ECDSA)
  • Lagra privata nycklar säkert med nyckelhanteringssystem eller krypterad lagring