Authentication
Learn how to authenticate with the kamiPay API
All our endpoints are authenticated using JWT token standard. A specific endpoint, /auth/token, gives you a new token when you need one. A token lasts one day by default, set per credential. Custom durations can be evaluated on a case by case basis.
You will be provided a username and a password that will be used to retrieve tokens. Tokens will then be used as bearer token for authentication through all the endpoints that you have been given access to: every request carries Authorization: Bearer <access_token>.
Getting an Access Token
Request a token with POST /auth/token. Send username and password as form fields (application/x-www-form-urlencoded), not as a JSON body.
const url = "baseURL/auth/token";
// Create the form fields, sent as application/x-www-form-urlencoded
const body = new URLSearchParams({
username: "YOUR_USERNAME",
password: "YOUR_PASSWORD"
});
const response = await fetch(url, {
method: "POST",
body
});import requests
url = "baseURL/auth/token"
# Create a dictionary for the form data
data = {
"username": "YOUR_USERNAME",
"password": "YOUR_PASSWORD"
}
response = requests.post(url, data=data)package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)
func main() {
apiURL := "baseURL/auth/token"
// Create form data
formData := url.Values{}
formData.Set("username", "YOUR_USERNAME")
formData.Set("password", "YOUR_PASSWORD")
// Make the request
resp, err := http.Post(
apiURL,
"application/x-www-form-urlencoded",
strings.NewReader(formData.Encode()),
)
if err != nil {
fmt.Println("Error making request:", err)
return
}
defer resp.Body.Close()
// Process the response
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Println("Access Token:", result["access_token"])
}/auth/token is rate-limited per IP address, currently to 10 requests per minute. Beyond that, it returns 429.
- Request a token once and reuse it until shortly before its
expires_at, instead of requesting one per call. - Services that reach kamiPay from the same IP, behind one NAT for example, share the limit. Share the token too: keep it in one cache they all read.
Response
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrYW1pcGF5LWJhY2tlb",
"token_type": "bearer",
"expires_at": 1785421930
}The username or the password is wrong. detail names the username you sent, and is the same whichever of the two is wrong:
{
"detail": "<username> not authenticated"
}The credentials weren't sent as form fields. A JSON body gets this response even when it carries username and password: here, "Field required" means the wrong encoding, not a missing field. Send them as form fields, as the samples above do.
{
"detail": [
{
"type": "missing",
"loc": ["body", "username"],
"msg": "Field required",
"input": null
},
{
"type": "missing",
"loc": ["body", "password"],
"msg": "Field required",
"input": null
}
]
}More than 10 token requests in a minute from your IP. Wait a minute before requesting again, and reuse the token you get until shortly before its expires_at. detail states the limit:
{
"detail": "10 per 1 minute"
}Response fields
| Field | Type | Description |
|---|---|---|
access_token | string | The JWT to send as a bearer token on every authenticated request. |
token_type | string | Always bearer. |
expires_at | integer | Unix timestamp, in seconds, at which the token expires. Same value as the exp claim inside the JWT. |
Use expires_at to refresh the token before it expires, instead of decoding the
JWT or hardcoding a duration: token duration depends on your credential, so it is
not the same for every integration.
Using the Token
After obtaining the access token, include it in the Authorization header for all API requests:
Authorization: Bearer YOUR_ACCESS_TOKEN