Docs / API / Reference

AMDY API reference

AMDY.IO exposes a real-time AMD Detection WebSocket for streaming call audio, plus a small set of API-key authenticated REST routes for registration, configuration and per-IP settings. This page documents only what is verified in the AMDY source tree as of 2026-08-28. Anything not verified is flagged "contact us" so you do not integrate against a guess.

New to AMDY? Start with the AMD Detection API client guide for the WebSocket protocol and classification codes. The platform install guides cover Asterisk, FreeSWITCH and Issabel.

What endpoints does AMDY expose?

Two surfaces, both public-internet-facing:

  • A WebSocket endpoint for live AMD detection: your telephony server streams 8 kHz PCM and receives a classification.
  • A set of REST routes under /api/v1/* for account and IP management, used by install scripts and polled by the detection servers.

What is the AMD Detection WebSocket endpoint?

FieldValue
URLws://api.amdy.io:2700
TransportPlain WebSocket (no TLS variant verified in source)
AuthX-API-Key header on connect, read from /etc/amdy/api-key
Audio format8000 Hz, 16-bit, mono, raw PCM (signed little-endian)

The first text frame must be a config frame: {"config":{"sample_rate":8000,"VID":"<caller id name or Unknown>"}}. After that, binary PCM frames follow, one per chunk. A final text frame {"eof":1} closes the stream and forces a result.

FreeSWITCH exception: mod_audio_fork cannot send custom headers on the WebSocket upgrade, so fork connections carry no X-API-Key and are gated by IP allowlist instead.

A TLS (wss://) variant is not present in any AMDY source in this repository. Contact us to confirm availability for your deployment.

What REST routes exist under /api/v1?

Four API-key authenticated routes. All four use the same auth check and are scoped to the client that owns the key.

MethodPathPurpose
POST/api/v1/ips/registerRegister a server IP and activate the account
GET/api/v1/configClient-level detection config, polled by detection servers
GET/api/v1/client-settingsPer-IP settings bundle (rate-limited)
GET/api/v1/ipsList IPs owned by the client, with their settings

There is also a public, unauthenticated health endpoint: GET /api/health. It returns platform-wide stats (status, detections_7d, human_pct_7d, p95_latency_ms, updated_at), cached for 5 minutes, with no per-client data.

How do I authenticate?

Every /api/v1/* route accepts either:

  • Authorization: Bearer <key>
  • X-API-Key: <key>

The key is looked up in api_keys. Unknown key returns 401; a key whose status is not active returns 403 on register and 401 on the read routes.

Keys are stored on installed servers at /etc/amdy/api-key (mode 600) and referenced from /etc/amdy/amdy.conf.

What is the API key format?

AMDY keys are not 64-character hex strings. The real format, verified in the minting code, is:

  • On signup: amd_live_ + crypto.randomBytes(12) as hex (24 hex chars, 32 chars total including the prefix).
  • On manual creation via the portal: amd_live_ + crypto.randomBytes(16) as hex (32 hex chars, 40 chars total).

The legacy sk-$ENV-* prefix has been retired. If your integration still expects a 64-character hex string, please contact us before upgrading.

How do I register a server IP?

POST /api/v1/ips/register activates the account and mirrors the IP into TimescaleDB for analytics attribution.

Body fieldRequiredNotes
ipnoValidated with net.isIP; auto-detected from X-Forwarded-For / X-Real-IP when absent
descriptionnoDefaults to Installed via installamd-v2.sh, truncated to 191 chars

Success (200):

{
  "ok": true,
  "activated": true,
  "account_activated_now": true,
  "client_id": 1234,
  "ip": "203.0.113.4",
  "registered": { "ip": "203.0.113.4", "status": "registered" }
}

registered.status is one of registered, already_registered or existing. Errors return 400 (invalid IP), 401 (missing/invalid key), 403 (key not active) or 502 (upstream registration failure with error, code, client_id, ip and hint fields).

How do I read my detection configuration?

GET /api/v1/config returns client-level settings, polled by detection servers roughly every 60 seconds:

{
  "clientId": 1234,
  "detectionSensitivity": 3,
  "maxDetectionMs": 8000,
  "updatedAt": "2026-08-01T12:00:00.000Z"
}

detectionSensitivity defaults to 3, maxDetectionMs defaults to 8000.

How do I read per-IP settings?

GET /api/v1/client-settings returns one entry per IP the client owns, plus a default entry. It is rate-limited to 20 requests per minute per key (429 with Retry-After).

{
  "version": 1722470400,
  "generated_at": "2026-08-28T09:00:00.000Z",
  "clients": {
    "default": { "max_detection_time": 8, "detection_mode": "balanced" },
    "203.0.113.4": {
      "max_detection_time": 6,
      "detection_mode": "aggressive",
      "stage_sensitivity": { "1k": 3, "32k": 10 }
    }
  }
}
Setting keyValues
max_detection_time0.5 to 10.0, clamped
detection_modeaggressive, balanced, conservative
greeting_graceboolean
immediate_detectionboolean
stage_resultsboolean
short_no_greetingboolean
stage_sensitivityoptional object, any subset of 1k, 2k, 3k, 4k, 8k, 12k, 16k, 24k, 32k mapped to an integer 1-10. 1 = most aggressive at that stage, 5 = current production, 10 = stage disabled. Omitted stages fall back to detection_mode.

stage_sensitivity is per-stage, not all-or-nothing: an explicit stage level wins over the detection_mode profile, which in turn wins over the shipped defaults. Levels carry no thresholds — the level-to-threshold ladder lives on the detection servers, so a retune keeps every level’s meaning stable.

Each key is omitted when NULL. Ownership is checked against firewall_production.client_ips; no rows returns 200 with an empty clients object.

How do I list my registered IPs?

GET /api/v1/ips returns the same rate limit (20/min) and the same per-IP settings keys as /api/v1/client-settings. Responses are never cached (Cache-Control: no-store).

{
  "client_id": 1234,
  "generated_at": "2026-08-28T09:00:00.000Z",
  "ips": [
    {
      "ip": "203.0.113.4",
      "status": "active",
      "settings": { "max_detection_time": 6, "detection_mode": "aggressive" }
    }
  ]
}

Reseller API: how do I manage clients programmatically?

Resellers get a separate key type, amd_rsl_…, created under Reseller → API access in the portal (owner only; shown once). It authenticates the same way as a client key, Authorization: Bearer amd_rsl_…, at 60 requests per minute, and every call is scoped to clients in your reseller org. A client id outside your org returns 403.

MethodRoutePurpose
GET/api/v1/reseller/clientsList clients (id, name, status, active IP count)
POST/api/v1/reseller/clientsCreate a client: {name, email, password?}; returns id, temp password, Sandbox key
GET/api/v1/reseller/clients/{clientId}/ipsList a client's registered IPs
POST/api/v1/reseller/clients/{clientId}/ipsRegister an IP for that client: {ip, description?}
DELETE/api/v1/reseller/clients/{clientId}/ipsRemove an IP: {ip} (body or ?ip=)
GET/api/v1/reseller/clients/{clientId}/api-keysList the client's AMD API keys (prefixes only)
POST/api/v1/reseller/clients/{clientId}/api-keysCreate an AMD key: {name, environment?}; full key returned once
DELETE/api/v1/reseller/clients/{clientId}/api-keys/{keyId}Revoke an AMD key

Provisioning a new customer end to end:

# 1. create the client
curl -X POST https://app.amdy.io/api/v1/reseller/clients \
  -H "Authorization: Bearer amd_rsl_..." -H "Content-Type: application/json" \
  -d '{"name": "Acme Dialers", "email": "[email protected]"}'
# -> {"client": {"clientId": 6000123, "email": "...", "tempPassword": "...", "sandboxApiKey": "amd_live_..."}}

# 2. register the dialer's IP for that client
curl -X POST https://app.amdy.io/api/v1/reseller/clients/6000123/ips \
  -H "Authorization: Bearer amd_rsl_..." -H "Content-Type: application/json" \
  -d '{"ip": "203.0.113.10", "description": "Dialer 1"}'
# -> {"registered": {"clientId": 6000123, "ip": "203.0.113.10", "status": "registered"}}

# 3. mint a production AMD key for the install script
curl -X POST https://app.amdy.io/api/v1/reseller/clients/6000123/api-keys \
  -H "Authorization: Bearer amd_rsl_..." -H "Content-Type: application/json" \
  -d '{"name": "Dialer 1", "environment": "production"}'
# -> {"apiKey": {"id": "...", "key": "amd_live_...", "environment": "production", "status": "active"}}

Errors return {"error": "..."} with 400 (bad input), 401 (missing or revoked key), 403 (not your client), 409 (email or IP already registered) or 429 (rate limit, with Retry-After).

Alerts: email and webhook

AMDY checks every registered IP hourly. An IP that had traffic in the last 14 days but none in the last 24 hours is quiet: the client's billing contact and the reseller owner get one email per client (all quiet IPs listed), and one more when traffic resumes. Resellers can also receive the same events as a signed webhook, set under Reseller → Alert webhook or with PUT /api/v1/reseller/webhook {url} (https only; the signing secret is returned once). POST the same route to send a webhook.test event; DELETE removes it.

POST <your url>
X-AMDY-Event: client.ip.quiet          # or client.ip.recovered, webhook.test
X-AMDY-Timestamp: 1789500000
X-AMDY-Signature: sha256=<hex HMAC-SHA256 of "<timestamp>.<raw body>" using your secret>

{
  "event": "client.ip.quiet",
  "timestamp": 1789500000,
  "data": {
    "client_id": 6000123,
    "client_name": "Acme Dialers",
    "ips": [{ "ip": "203.0.113.10", "last_seen_at": "2026-09-13T04:00:00.000Z" }]
  }
}

Verify by recomputing the HMAC over timestamp + "." + body and comparing in constant time; reject timestamps older than a few minutes. Deliveries time out after 10 s and are not retried; the last status is visible in the portal and via GET /api/v1/reseller/webhook.

What is the install command per platform?

PlatformInstall command
ViciDial / ViciBoxcurl -fsSL https://download.amdy.io/installamd-v2.sh | bash -s <API_KEY>
Asterisk (custom dialer)curl -sL https://download.amdy.io/installamd-asterisk.sh | bash -s -- <API_KEY>
FreeSWITCHinstallamd-freeswitch.sh <API_KEY> --module /path/to/mod_audio_fork.so (no curl one-liner; module must be supplied)
IssabelNot verified in source. Contact us for integration access.

Every installer writes the key to /etc/amdy/api-key (mode 600) and calls POST /api/v1/ips/register with the host's detected public IP. On ViciBox, never chown to asterisk: Vicidial runs as root.

Frequently asked questions

Is there a wss:// endpoint?

Not verified in source. Only ws://api.amdy.io:2700 is documented in this repository. Contact us to confirm TLS availability for your network.

Can I revoke an API key over the public API?

No. Key management (create and revoke) is portal-only via /api/api-keys, authenticated by your NextAuth session. Revoke sets status='revoked'; the row is not deleted.

Can I delete a registered IP over the API?

No. IP removal is portal-only. The public API exposes register and read routes, not delete.

Is Issabel supported?

Not verified in source. Issabel is Asterisk-based, but AMDY ships no Issabel-specific installer or dialplan. Contact us for integration access.

What does /api/health return?

Platform-wide stats only: {"status":"operational|degraded","detections_7d":...,"human_pct_7d":...,"p95_latency_ms":...,"updated_at":"..."}. Cached for 5 minutes. No per-client data.

Ready to integrate?

Generate an API key from your AMDY.IO dashboard and run the installer for your platform. The free Sandbox plan includes 50,000 detections per month, no card.

Open the AMDY dashboard

Support

Questions about an endpoint not listed here? Email [email protected] with the route, the request you sent and the response you received.

AMDY API Reference — public REST endpoints, WebSocket protocol and response shapes | AMDY.IO