Skip to content

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:

http
X-Api-Key: YOUR_API_KEY

Getting Your API Key

  1. Sign in to your MsGine account
  2. Navigate to the Developer Dashboard
  3. Click Generate New Token
  4. Copy the key immediately (it won't be shown again)
  5. 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.

ScopeGrants
SEND_SMSPOST /developers/sms
SEND_EMAILPOST /developers/email
SEND_PUSHPOST /developers/push and device management
SEND_WHATSAPPWhatsApp sending
READ_MESSAGESMessage history and lookup by id
READ_CONTACTSReading contacts
MANAGE_CONTACTSCreating and updating contacts
MANAGE_WEBHOOKSWebhook registration, secrets and delivery history
FULL_ACCESSEvery 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:

EnvironmentBehaviour
productionMessages are sent and the account is charged
sandboxRequests 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:

bash
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...9b

The 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.

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

bash
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

javascript
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

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

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

  1. Navigate to the Developer Dashboard
  2. Find the compromised key
  3. Click Revoke
  4. Generate a new key
  5. 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.

json
{
  "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.

json
{
  "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:

typescript
// ❌ 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:

typescript
// ✅ 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: .env files (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:

bash
curl https://api.msgine.net/api/v1/account \
  -H "X-Api-Key: YOUR_API_KEY"

Successful response:

json
{
  "id": "acc_1234567890",
  "email": "user@example.com",
  "balance": 5000,
  "currency": "UGX"
}

Next Steps

Released under the MIT License.