REST API

Every part of the platform is driveable over the API

Every fastergateway screen sits on this API: connectors, routes, filters, campaigns, contacts, balances, delivery receipts. The reference below is extracted from the backend source rather than written alongside it, so it cannot drift.

https://demo.fastergateway.net:3002/api/v1v1.0 · 172 operations

Before the first call

Four things that hold for every route

The rest is in the reference.

Authentication

POST /api/v1/auth/login returns a JWT and a refresh token. Routes marked with a padlock expect the Authorization: Bearer header.

Rate limit

10 requests per second per IP address. Past that the answer is a 429: the client should back off rather than push.

Response envelope

Always the same shape, { success, data, error }, with the matching HTTP status. An error never comes back as a 200.

Delivery receipts

The gateway calls back /api/v1/sms/dlr/gateway. The DLR is then embedded in the message and read back through the messages API.

Secrets in transit

Encrypt passwords, if you want to

Optional and per caller: the backend looks at the field and decides on its own. Known prefix, it decrypts; anything else, it takes the value as is. No existing request changes.

The key

Handed over directly by the operator. It never travels through the API, never goes into a browser — a JavaScript bundle can be read — and serves transport only.

Expected format

enc:v1:<iv>:<tag>:<chiffré>

AES-256-GCM · iv 12 octets · tag 16 octets · les trois en base64

Node 20+

import { createCipheriv, randomBytes } from 'node:crypto';

const CLE = Buffer.from(process.env.SECRETS_KEY, 'base64');

export function chiffrer(clair) {
  const iv = randomBytes(12);
  const c = createCipheriv('aes-256-gcm', CLE, iv);
  const corps = Buffer.concat([c.update(clair, 'utf8'), c.final()]);
  return [
    'enc:v1:' + iv.toString('base64'),
    c.getAuthTag().toString('base64'),
    corps.toString('base64'),
  ].join(':');
}

// await fetch(url, { method: 'POST', headers, body: JSON.stringify({
//   uid: 'FAST_1234', gid: 'grp_8', username: 'client',
//   password: chiffrer('MonSecret9'),
// })});

Python 3 · cryptography

import base64, os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

CLE = base64.b64decode(os.environ["SECRETS_KEY"])

def chiffrer(clair: str) -> str:
    iv = os.urandom(12)
    # AESGCM.encrypt colle le tag (16 octets) A LA FIN du chiffre :
    # il faut l'en detacher, le format attendant les deux separement.
    sortie = AESGCM(CLE).encrypt(iv, clair.encode(), None)
    corps, tag = sortie[:-16], sortie[-16:]
    b = lambda x: base64.b64encode(x).decode()
    return f"enc:v1:{b(iv)}:{b(tag)}:{b(corps)}"

Fields accepting an encrypted secret

  • /auth/login · /auth/signup · /auth/reset-passwordpassword
  • /users · /users/{id}password
  • /jasmin-users · /jasmin-users/{uid}password
  • /gateways · /gateways/{id}password
  • /smpp-connectors · /smpp-connectors/{cid}password
  • /bridges · /bridges/{id}bindPassword, authValue

What this closes is not interception — TLS already covers that — but the trail a cleartext password leaves behind: a proxy log, an internal capture, an error report. The protection is worth exactly what the key’s secrecy is worth.

Reference

Every backend route

Grouped by domain, exactly as the server exposes them.

172 operations