Authentication
Learn how to authenticate with the MsGine API using API keys.
Overview
All MsGine API requests must be authenticated using an API key passed in the X-Api-Key header:
X-Api-Key: YOUR_API_KEYGetting Your API Key
- Sign in to your MsGine account
- Navigate to the Developer Dashboard
- Click Generate New Token
- Copy the key immediately (it won't be shown again)
- Store it securely in your environment variables
A key looks like this:
msg_84f78e9a0680ebe9_ba0e01b3d623f051c8fd76299e7f8fe60cb8320e
└──── key id ───┘ └──────────────── secret ─────────────┘The middle segment is the key id (kid), which identifies the key and is safe to log. The final segment is the secret half; it is stored hashed and cannot be recovered after creation. Send the whole string in the header.
Scopes
Each key carries a set of scopes. A request to an endpoint whose scope the key does not hold returns 403.
| Scope | Grants |
|---|---|
SEND_SMS | POST /developers/sms |
SEND_EMAIL | POST /developers/email |
SEND_PUSH | POST /developers/push and device management |
SEND_WHATSAPP | WhatsApp sending |
READ_MESSAGES | Message history and lookup by id |
READ_CONTACTS | Reading contacts |
MANAGE_CONTACTS | Creating and updating contacts |
MANAGE_WEBHOOKS | Webhook registration, secrets and delivery history |
FULL_ACCESS | Every scope, including ones added later |
Scope names are case-sensitive and validated when the key is created; an unrecognised name returns 400.
Sandbox keys
A key is created in one of two environments:
| Environment | Behaviour |
|---|---|
production | Messages are sent and the account is charged |
sandbox | Requests are validated and recorded; nothing is sent or charged |
Sandbox responses carry "sandbox": true and "cost": 0, and the message is stored with status sent so history and lookup behave normally. Message webhooks do not fire for sandbox traffic.
Choose the environment when creating the key, or change it later:
curl -X PUT https://api.msgine.net/api/v1/developer/keys/$KEY_ID \
-H "Content-Type: application/json" \
-d '{"environment": "sandbox"}'HMAC request signing
A key may optionally require every request to be signed. Enable it at creation with "enableHmac": true; the signing secret is returned once, alongside the key.
Signed requests carry two extra headers:
x-timestamp: 1771324215
x-signature: 4f2c...9bThe signature covers the method, path, timestamp and a hash of the raw body:
message = HTTP_METHOD + REQUEST_PATH + TIMESTAMP + SHA256_HEX(raw_body)
signature = HMAC_SHA256(signing_secret, message)REQUEST_PATH includes the /api/v1 prefix, for example /api/v1/developers/sms. Timestamps more than 120 seconds from server time are rejected, and a signature may not be reused.
import hashlib, hmac, json, time
body = json.dumps({"to": "+256700000000", "message": "Hello"})
ts = str(int(time.time()))
body_hash = hashlib.sha256(body.encode()).hexdigest()
message = "POST" + "/api/v1/developers/sms" + ts + body_hash
signature = hmac.new(secret.encode(), message.encode(), hashlib.sha256).hexdigest()
headers = {
"x-api-key": api_key,
"x-timestamp": ts,
"x-signature": signature,
"Content-Type": "application/json",
}Sign the exact bytes sent as the request body.
Security Warning
Never expose your API key in:
- Client-side code (browser JavaScript)
- Public repositories
- Version control systems
- Log files or error messages
Always store keys in environment variables or secure secret management systems.
Using API Keys
HTTP Requests
Include the key in the X-Api-Key header:
curl -X POST https://api.msgine.net/api/v1/developers/sms \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+256701521269",
"message": "Hello from MsGine!"
}'JavaScript/TypeScript
const response = await fetch('https://api.msgine.net/api/v1/developers/sms', {
method: 'POST',
headers: {
'X-Api-Key': process.env.MSGINE_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: '+256701521269',
message: 'Hello from MsGine!'
})
})Python
import os
import requests
headers = {
'X-Api-Key': os.environ['MSGINE_API_KEY'],
'Content-Type': 'application/json'
}
response = requests.post(
'https://api.msgine.net/api/v1/developers/sms',
headers=headers,
json={
'to': '+256701521269',
'message': 'Hello from MsGine!'
}
)PHP
<?php
$apiKey = getenv('MSGINE_API_KEY');
$ch = curl_init('https://api.msgine.net/api/v1/developers/sms');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"X-Api-Key: $apiKey",
'Content-Type: application/json'
]);Using the Official SDK
The SDK handles authentication automatically:
import { MsGineClient } from '@msgine/sdk'
const client = new MsGineClient({
apiKey: process.env.MSGINE_API_KEY!,
})
// SDK automatically includes the key in all requests
await client.sms.send({
to: '+256701521269',
message: 'Hello!'
})Token Management
Creating Tokens
You can create multiple API keys for different purposes:
- Development: For testing and development
- Production: For live applications
- CI/CD: For automated deployments
- Third-party: For external integrations
Each key can be revoked independently without affecting others.
Revoking Tokens
If a key is compromised:
- Navigate to the Developer Dashboard
- Find the compromised key
- Click Revoke
- Generate a new key
- Update your application with the new key
TIP
Rotate API keys regularly as a security best practice.
Authentication Errors
401 Unauthorized
Occurs when the API key is invalid or missing.
{
"statusCode": 401,
"error": "unauthorized",
"message": "Invalid API key"
}Check that the key is correct, has not been revoked, and is sent in the x-api-key header. A key with HMAC enabled also returns 401 when the x-signature or x-timestamp header is missing.
403 Forbidden
Occurs when the key lacks required permissions.
{
"statusCode": 403,
"error": "forbidden",
"message": "Insufficient scopes. Required one of: SEND_SMS, FULL_ACCESS"
}The message names the scopes that would have satisfied the request. Scopes can be changed on an existing key without rotating it.
A 403 is also returned when the key has an IP allowlist and the request came from an address outside it.
Security Best Practices
1. Use Environment Variables
Never hardcode keys in your source code:
// ❌ Bad
const client = new MsGineClient({
apiKey: 'msgine_live_abc123...'
})
// ✅ Good
const client = new MsGineClient({
apiKey: process.env.MSGINE_API_KEY!,
})2. Server-Side Only
Only use API keys in server-side code:
// ✅ Good - Server code only
import { MsGineClient } from '@msgine/sdk'
const client = new MsGineClient({
apiKey: process.env.MSGINE_API_KEY!,
})3. Key Rotation
Rotate keys regularly:
- Production keys: Every 90 days
- Development keys: As needed
- Immediately: If a key is compromised
4. Secure Storage
- Development:
.envfiles (add to.gitignore) - Production: Environment variables, secret managers (AWS Secrets Manager, Azure Key Vault, etc.)
- CI/CD: Encrypted secrets
Testing Authentication
Test your authentication setup:
curl https://api.msgine.net/api/v1/account \
-H "X-Api-Key: YOUR_API_KEY"Successful response:
{
"id": "acc_1234567890",
"email": "user@example.com",
"balance": 5000,
"currency": "UGX"
}Next Steps
- REST API - API reference and examples
- Rate Limits - Understanding rate limits
- Webhooks - Configure delivery notifications