API Authentication
The ADA API provides two authentication methods for the /authenticates/api-code endpoint
Quick Start
- Obtain your API code and register your public key (for GET method) or static IP (for POST method) with Visma Amili AB.
- 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).
- Authenticate and receive an access token.
- Use the access token in the X-API-Key header for all subsequent API requests.
- 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.keyJWT 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-Keyheader
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)
raisecsharp
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
