Skip to content

API Authentication

ADA API tillhandahåller två autentiseringsmetoder för /authenticates/api-code endpoint

Snabbstart

  1. Skaffa din API-kod och registrera din publika nyckel (för GET-metod) eller statisk IP (för POST-metod) med Visma Amili AB.
  2. Välj autentiseringsmetod:
    • GET: Använd JWT signerad med din privata nyckel.
    • POST: Använd din API-kod i request body (från en registrerad statisk IP).
  3. Autentisera och ta emot en access token.
  4. Använd access token i X-API-Key header för alla efterföljande API-requests.
  5. 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 EC-nyckelpar (ES256-algoritm)
  • Registrera din publika nyckel med Visma Amili AB
  • Skaffa din API-kod från Visma Amili AB

Nyckelgenerering:

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-struktur:

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

Implementeringsdetaljer:

  • Använder ES512-algoritm för JWT-signering
  • JWT bör gå ut efter 10 minuter (sätt exp claim till UNIX timestamp)
  • Kräver en privat nyckel (EC-nyckelpar)
  • JWT:en signeras med den privata nyckeln och skickas i X-API-Key header

Exempel från kod:

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 (Enkel API-kod autentisering)

Syfte: Autentisera med en enkel API-kod utan JWT.

Method: POST

Content-Type: application/json

Förutsättningar:

  • Registrera din statiska IP-adress med Visma Amili AB
  • Skaffa din API-kod från Visma Amili AB

Request Body:

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

Implementeringsdetaljer:

  • Enklare autentiseringsmetod
  • Ingen privat nyckel krävs
  • Direkt API-kod inlämning i JSON body
  • Kräver att requests kommer från en registrerad statisk IP-adress

Exempel från kod:

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

Response Format

Båda endpoints returnerar samma 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 (GET-metod): 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,
    }

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

Säkerhetsöverväganden

  • GET-metod (JWT): Mer säker eftersom den använder kryptografiska signaturer
  • POST-metod: Kräver statisk IP-registrering för säkerhet
  • Använd alltid HTTPS för all API-kommunikation
  • Håll din privata nyckel säker och exponera den aldrig i client-side kod