gamlin commited on
Commit
2f437f2
·
verified ·
0 Parent(s):

initial commit

Browse files
Files changed (2) hide show
  1. .gitattributes +35 -0
  2. README.md +774 -0
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,774 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ tags:
4
+ - vicidial
5
+ - call-center
6
+ - asterisk
7
+ - manager
8
+ - interface
9
+ ---
10
+
11
+ # Asterisk Manager Interface (AMI): The Complete Developer Guide
12
+
13
+ **Last updated: March 2026 | Reading time: ~24 minutes** The [Asterisk Manager Interface](/blog/voip-mos-score-guide/) is a TCP socket API that lets you control Asterisk programmatically. Originate calls. Monitor channels. Transfer calls. Get real-time events for every call that enters, bridges, and hangs up on your system. If you're building anything on top of Asterisk — a CRM integration, a wallboard, a click-to-call feature, a custom reporting tool — AMI is probably how you're going to do it. VICIdial itself uses AMI heavily. The agent screen, the real-time report, the auto-dial engine — they all talk to Asterisk through AMI. This guide covers everything from initial setup to production-grade integrations. No fluff. Code that works. --- Asterisk has three interfaces for external programs. People confuse them constantly. **AMI ([Manager Interface](/blog/asterisk-manager-interface-guide/))** — TCP socket on port 5038. Send commands, receive events. Best for: monitoring, originating calls, managing channels, building dashboards. Think of it...
14
+
15
+ ## Overview
16
+
17
+ **Last updated: March 2026 | Reading time: ~24 minutes**
18
+
19
+ The [Asterisk Manager Interface](/blog/voip-mos-score-guide/) is a TCP socket API that lets you control Asterisk programmatically. Originate calls. Monitor channels. Transfer calls. Get real-time events for every call that enters, bridges, and hangs up on your system.
20
+
21
+ If you're building anything on top of Asterisk — a CRM integration, a wallboard, a click-to-call feature, a custom reporting tool — AMI is probably how you're going to do it. VICIdial itself uses AMI heavily. The agent screen, the real-time report, the auto-dial engine — they all talk to Asterisk through AMI.
22
+
23
+ This guide covers everything from initial setup to production-grade integrations. No fluff. Code that works.
24
+
25
+ ---
26
+
27
+ ## AMI vs. ARI vs. AGI: Which One Do You Need?
28
+
29
+ Asterisk has three interfaces for external programs. People confuse them constantly.
30
+
31
+ **AMI ([Manager Interface](/blog/asterisk-manager-interface-guide/))** — TCP socket on port 5038. Send commands, receive events. Best for: monitoring, originating calls, managing channels, building dashboards. Think of it as the "admin remote control."
32
+
33
+ **AGI (Asterisk Gateway Interface)** — Runs a script during a live call. The dialplan hits an AGI application, which forks a process (or connects to a FastAGI server), and that process controls the call step by step. Best for: IVR logic, call routing decisions, database lookups during a call. Think of it as "dialplan scripting."
34
+
35
+ **ARI (Asterisk REST Interface)** — HTTP/WebSocket API introduced in Asterisk 12. Best for: building entire telephony applications where you want full control of every call. Stasis applications. Think of it as "build your own PBX."
36
+
37
+ For VICIdial integrations, AMI is what you want 95% of the time. VICIdial doesn't use ARI. AGI is used for specific call-flow customizations. But AMI is the backbone.
38
+
39
+ ---
40
+
41
+ ## Setting Up manager.conf
42
+
43
+ The AMI configuration lives in `/etc/asterisk/manager.conf`. On a fresh Asterisk install, it's usually there but disabled.
44
+
45
+ ```ini
46
+ ; /etc/asterisk/manager.conf
47
+
48
+ [general]
49
+ enabled = yes
50
+ port = 5038
51
+ bindaddr = 127.0.0.1 ; IMPORTANT: Only listen on localhost by default
52
+ displayconnects = yes
53
+ timestampevents = yes
54
+
55
+ [vicistack]
56
+ secret = a_strong_random_password_here
57
+ deny = 0.0.0.0/0.0.0.0
58
+ permit = 127.0.0.1/255.255.255.255
59
+ read = system,call,log,verbose,agent,user,config,dtmf,reporting,cdr,dialplan
60
+ write = system,call,agent,user,config,command,reporting,originate
61
+ writetimeout = 5000
62
+ ```
63
+
64
+ Let's break down the important parts.
65
+
66
+ ### bindaddr
67
+
68
+ This controls what IP address AMI listens on. **Never set this to 0.0.0.0 on a production system** unless you have firewall rules restricting access. AMI has no rate limiting, no brute force protection, and if someone gets in, they can originate calls, execute shell commands (yes, really), and read your entire Asterisk configuration.
69
+
70
+ ```ini
71
+ ; GOOD: Only local connections
72
+ bindaddr = 127.0.0.1
73
+
74
+ ; ACCEPTABLE: Specific internal network interface
75
+ bindaddr = 10.0.0.5
76
+
77
+ ; DANGEROUS: Listen on all interfaces
78
+ ; bindaddr = 0.0.0.0
79
+ ```
80
+
81
+ If you need remote AMI access (e.g., your web server is on a different box than Asterisk), use an SSH tunnel or a VPN. Don't expose port 5038 to the internet.
82
+
83
+ ### User Permissions
84
+
85
+ The `read` and `write` lines control what the AMI user can do. These are comma-separated permission classes:
86
+
87
+ | Class | Read (receive events) | Write (send actions) |
88
+ |-------|----------------------|---------------------|
89
+ | system | System events | Restart, shutdown |
90
+ | call | Call events | Originate, redirect, hangup |
91
+ | log | Log events | — |
92
+ | verbose | Verbose messages | — |
93
+ | agent | Agent events | Agent login/logoff |
94
+ | user | User events | UserEvent |
95
+ | config | — | GetConfig, UpdateConfig |
96
+ | dtmf | DTMF events | — |
97
+ | reporting | CEL/CDR events | — |
98
+ | cdr | CDR events | — |
99
+ | dialplan | Dialplan events | — |
100
+ | command | — | Execute CLI commands |
101
+ | originate | — | Originate calls |
102
+
103
+ **Principle of least privilege.** If your integration only needs to monitor calls, give it `read = call,cdr` and `write = ` (nothing). If it only needs to originate calls, give it `write = originate` and nothing else.
104
+
105
+ The `command` permission is particularly dangerous — it lets the AMI user run arbitrary Asterisk CLI commands, some of which can execute shell commands. Only grant `command` to trusted integrations running on the same server.
106
+
107
+ ### IP Restrictions
108
+
109
+ Always use `deny` and `permit` to restrict which IPs can connect:
110
+
111
+ ```ini
112
+ [monitoring_user]
113
+ secret = monitor_password_here
114
+ deny = 0.0.0.0/0.0.0.0 ; Deny everything first
115
+ permit = 127.0.0.1/255.255.255.255 ; Allow localhost
116
+ permit = 10.0.1.50/255.255.255.255 ; Allow the monitoring server
117
+ read = call,cdr,agent
118
+ write =
119
+ ```
120
+
121
+ After editing manager.conf, reload:
122
+
123
+ ```bash
124
+ asterisk -rx "manager reload"
125
+ ```
126
+
127
+ ---
128
+
129
+ ## Connecting and Authenticating
130
+
131
+ AMI is a plain TCP socket. You can test it with telnet:
132
+
133
+ ```bash
134
+ telnet localhost 5038
135
+ ```
136
+
137
+ You'll see:
138
+
139
+ ```
140
+ Asterisk Call Manager/7.0.3
141
+ ```
142
+
143
+ Now authenticate:
144
+
145
+ ```
146
+ Action: Login
147
+ Username: vicistack
148
+ Secret: a_strong_random_password_here
149
+
150
+ ```
151
+
152
+ (Note the blank line at the end — AMI uses blank lines as message terminators.)
153
+
154
+ Response:
155
+
156
+ ```
157
+ Response: Success
158
+ Message: Authentication accepted
159
+ ```
160
+
161
+ You're in. Now you can send actions and receive events.
162
+
163
+ ### Authentication Failure
164
+
165
+ If you get:
166
+
167
+ ```
168
+ Response: Error
169
+ Message: Authentication failed
170
+ ```
171
+
172
+ Check:
173
+ 1. Username matches a section name in manager.conf (case-sensitive)
174
+ 2. Secret matches exactly
175
+ 3. Your IP is in the permit list
176
+ 4. You reloaded manager after editing the config
177
+
178
+ ---
179
+
180
+ ## Core AMI Actions
181
+
182
+ ### Originate — Make a Call
183
+
184
+ The most used AMI action. This tells Asterisk to call a number and connect it to an extension, application, or another number.
185
+
186
+ ```
187
+ Action: Originate
188
+ Channel: SIP/carrier/18005551234
189
+ Context: from-internal
190
+ Exten: 100
191
+ Priority: 1
192
+ CallerID: "Sales" <18005559999>
193
+ Timeout: 30000
194
+ ActionID: call-001
195
+
196
+ ```
197
+
198
+ This tells Asterisk: "Call 18005551234 through the SIP trunk named 'carrier'. When they answer, send them to extension 100 in the from-internal context."
199
+
200
+ For click-to-call (agent clicks a number in CRM, phone rings, then the lead is dialed):
201
+
202
+ ```
203
+ Action: Originate
204
+ Channel: SIP/agent_phone_100
205
+ Context: click-to-call
206
+ Exten: 18005551234
207
+ Priority: 1
208
+ CallerID: "Agent Smith" <18005559999>
209
+ Timeout: 30000
210
+ ActionID: c2c-agent100-001
211
+
212
+ ```
213
+
214
+ This calls the agent's phone first. When the agent picks up, Asterisk dials the lead number.
215
+
216
+ ### Status — Get Channel Information
217
+
218
+ ```
219
+ Action: Status
220
+ ActionID: status-001
221
+
222
+ ```
223
+
224
+ Returns a list of all active channels with caller ID, state, duration, context, and extension. This is what powers real-time wallboards.
225
+
226
+ ### Command — Run CLI Commands
227
+
228
+ ```
229
+ Action: Command
230
+ Command: sip show peers
231
+ ActionID: cmd-001
232
+
233
+ ```
234
+
235
+ Returns the output of the Asterisk CLI command. Use this sparingly — it's a text-based response that you have to parse. For structured data, use dedicated actions instead.
236
+
237
+ ### CoreShowChannels — Active Call Details
238
+
239
+ ```
240
+ Action: CoreShowChannels
241
+ ActionID: channels-001
242
+
243
+ ```
244
+
245
+ Returns structured data about every active channel. Better than `Action: Command / Command: core show channels` because the output is event-based and parseable.
246
+
247
+ ### Hangup — Kill a Channel
248
+
249
+ ```
250
+ Action: Hangup
251
+ Channel: SIP/carrier-00000042
252
+ ActionID: hangup-001
253
+
254
+ ```
255
+
256
+ Immediately hangs up the specified channel. Use the channel name from Status or CoreShowChannels.
257
+
258
+ ### Redirect — Transfer a Call
259
+
260
+ ```
261
+ Action: Redirect
262
+ Channel: SIP/agent_100-00000042
263
+ Context: transfer-destination
264
+ Exten: 200
265
+ Priority: 1
266
+ ActionID: xfer-001
267
+
268
+ ```
269
+
270
+ Transfers the call to extension 200 in the transfer-destination context. This is how VICIdial's blind transfer works under the hood.
271
+
272
+ ### QueueStatus — Queue Monitoring
273
+
274
+ ```
275
+ Action: QueueStatus
276
+ ActionID: queue-001
277
+
278
+ ```
279
+
280
+ Returns members and callers in all queues. If you're building an inbound call center dashboard, this is the action you'll use most.
281
+
282
+ ---
283
+
284
+ ## AMI Events: Real-Time Call Data
285
+
286
+ When you connect with `read` permissions, Asterisk streams events to your connection in real time. Here are the events you'll see most:
287
+
288
+ ### Newchannel
289
+
290
+ Fired when a new channel is created (call starts):
291
+
292
+ ```
293
+ Event: Newchannel
294
+ Privilege: call,all
295
+ Channel: SIP/carrier-00000042
296
+ ChannelState: 0
297
+ ChannelStateDesc: Down
298
+ CallerIDNum: 18005551234
299
+ CallerIDName: John Doe
300
+ AccountCode:
301
+ Exten: s
302
+ Context: from-carrier
303
+ Uniqueid: 1711408200.42
304
+ ```
305
+
306
+ ### Dial
307
+
308
+ Fired when one channel dials another:
309
+
310
+ ```
311
+ Event: Dial
312
+ SubEvent: Begin
313
+ Channel: SIP/carrier-00000042
314
+ Destination: SIP/agent_100-00000043
315
+ CallerIDNum: 18005551234
316
+ CallerIDName: John Doe
317
+ DialString: agent_100
318
+ ```
319
+
320
+ ### Bridge
321
+
322
+ Fired when two channels are connected (call answered):
323
+
324
+ ```
325
+ Event: Bridge
326
+ Privilege: call,all
327
+ Bridgestate: Link
328
+ Bridgetype: core
329
+ Channel1: SIP/carrier-00000042
330
+ Channel2: SIP/agent_100-00000043
331
+ Uniqueid1: 1711408200.42
332
+ Uniqueid2: 1711408200.43
333
+ CallerID1: 18005551234
334
+ CallerID2: 100
335
+ ```
336
+
337
+ ### Hangup
338
+
339
+ Fired when a channel is destroyed:
340
+
341
+ ```
342
+ Event: Hangup
343
+ Channel: SIP/carrier-00000042
344
+ Uniqueid: 1711408200.42
345
+ CallerIDNum: 18005551234
346
+ Cause: 16
347
+ Cause-txt: Normal Clearing
348
+ ```
349
+
350
+ ### AgentCalled / AgentConnect / AgentComplete
351
+
352
+ Queue-specific events that track agent availability and call handling. Essential for building agent performance dashboards.
353
+
354
+ ---
355
+
356
+ ## Building a Real AMI Client
357
+
358
+ Telnet is fine for testing. For production, you need a real client with connection management, event parsing, and error handling.
359
+
360
+ ### Python Example
361
+
362
+ ```python
363
+ import socket
364
+ import time
365
+
366
+
367
+ class AMIClient:
368
+ def __init__(self, host='127.0.0.1', port=5038):
369
+ self.host = host
370
+ self.port = port
371
+ self.sock = None
372
+ self.buffer = ''
373
+
374
+ def connect(self):
375
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
376
+ self.sock.settimeout(30)
377
+ self.sock.connect((self.host, self.port))
378
+ # Read the banner
379
+ banner = self._read_response()
380
+ return banner
381
+
382
+ def login(self, username, secret):
383
+ self._send_action({
384
+ 'Action': 'Login',
385
+ 'Username': username,
386
+ 'Secret': secret,
387
+ })
388
+ response = self._read_response()
389
+ if 'Authentication accepted' not in response:
390
+ raise Exception(f'AMI login failed: {response}')
391
+ return response
392
+
393
+ def originate(self, channel, context, exten, priority=1,
394
+ callerid=None, timeout=30000, action_id=None):
395
+ action = {
396
+ 'Action': 'Originate',
397
+ 'Channel': channel,
398
+ 'Context': context,
399
+ 'Exten': exten,
400
+ 'Priority': str(priority),
401
+ 'Timeout': str(timeout),
402
+ }
403
+ if callerid:
404
+ action['CallerID'] = callerid
405
+ if action_id:
406
+ action['ActionID'] = action_id
407
+ self._send_action(action)
408
+ return self._read_response()
409
+
410
+ def command(self, cmd, action_id=None):
411
+ action = {
412
+ 'Action': 'Command',
413
+ 'Command': cmd,
414
+ }
415
+ if action_id:
416
+ action['ActionID'] = action_id
417
+ self._send_action(action)
418
+ return self._read_response()
419
+
420
+ def logoff(self):
421
+ self._send_action({'Action': 'Logoff'})
422
+
423
+ def _send_action(self, action):
424
+ msg = ''
425
+ for key, value in action.items():
426
+ msg += f'{key}: {value}\r\n'
427
+ msg += '\r\n'
428
+ self.sock.sendall(msg.encode('utf-8'))
429
+
430
+ def _read_response(self):
431
+ while '\r\n\r\n' not in self.buffer:
432
+ chunk = self.sock.recv(4096).decode('utf-8')
433
+ if not chunk:
434
+ raise ConnectionError('AMI connection closed')
435
+ self.buffer += chunk
436
+ response, self.buffer = self.buffer.split('\r\n\r\n', 1)
437
+ return response
438
+
439
+ def close(self):
440
+ if self.sock:
441
+ self.logoff()
442
+ self.sock.close()
443
+
444
+
445
+ # Usage
446
+ ami = AMIClient('127.0.0.1', 5038)
447
+ ami.connect()
448
+ ami.login('vicistack', 'your_password')
449
+
450
+ # Check SIP peers
451
+ result = ami.command('sip show peers')
452
+ print(result)
453
+
454
+ # Originate a call
455
+ ami.originate(
456
+ channel='SIP/carrier/18005551234',
457
+ context='from-internal',
458
+ exten='100',
459
+ callerid='"Sales" <18005559999>',
460
+ action_id='test-call-001'
461
+ )
462
+
463
+ ami.close()
464
+ ```
465
+
466
+ ### Node.js Example
467
+
468
+ ```javascript
469
+ const net = require('net');
470
+
471
+ class AMIClient {
472
+ constructor(host = '127.0.0.1', port = 5038) {
473
+ this.host = host;
474
+ this.port = port;
475
+ this.socket = null;
476
+ this.buffer = '';
477
+ this.callbacks = {};
478
+ this.eventHandlers = {};
479
+ }
480
+
481
+ connect() {
482
+ return new Promise((resolve, reject) => {
483
+ this.socket = net.createConnection(this.port, this.host);
484
+ this.socket.setEncoding('utf8');
485
+
486
+ this.socket.once('data', (data) => {
487
+ // First data is the banner
488
+ resolve(data.trim());
489
+ });
490
+
491
+ this.socket.on('data', (data) => {
492
+ this.buffer += data;
493
+ this._processBuffer();
494
+ });
495
+
496
+ this.socket.on('error', reject);
497
+ });
498
+ }
499
+
500
+ login(username, secret) {
501
+ return this.sendAction({
502
+ Action: 'Login',
503
+ Username: username,
504
+ Secret: secret,
505
+ });
506
+ }
507
+
508
+ originate(channel, context, exten, options = {}) {
509
+ return this.sendAction({
510
+ Action: 'Originate',
511
+ Channel: channel,
512
+ Context: context,
513
+ Exten: exten,
514
+ Priority: options.priority || '1',
515
+ Timeout: options.timeout || '30000',
516
+ CallerID: options.callerId || '',
517
+ ActionID: options.actionId || `orig-${Date.now()}`,
518
+ });
519
+ }
520
+
521
+ sendAction(action) {
522
+ return new Promise((resolve) => {
523
+ const actionId = action.ActionID || `act-${Date.now()}`;
524
+ action.ActionID = actionId;
525
+ this.callbacks[actionId] = resolve;
526
+
527
+ let msg = '';
528
+ for (const [key, value] of Object.entries(action)) {
529
+ if (value) msg += `${key}: ${value}\r\n`;
530
+ }
531
+ msg += '\r\n';
532
+ this.socket.write(msg);
533
+ });
534
+ }
535
+
536
+ on(eventName, handler) {
537
+ if (!this.eventHandlers[eventName]) {
538
+ this.eventHandlers[eventName] = [];
539
+ }
540
+ this.eventHandlers[eventName].push(handler);
541
+ }
542
+
543
+ _processBuffer() {
544
+ const messages = this.buffer.split('\r\n\r\n');
545
+ this.buffer = messages.pop(); // Keep incomplete message
546
+
547
+ for (const msg of messages) {
548
+ if (!msg.trim()) continue;
549
+ const parsed = {};
550
+ for (const line of msg.split('\r\n')) {
551
+ const idx = line.indexOf(': ');
552
+ if (idx > 0) {
553
+ parsed[line.substring(0, idx)] = line.substring(idx + 2);
554
+ }
555
+ }
556
+
557
+ // Route to callback or event handler
558
+ if (parsed.ActionID && this.callbacks[parsed.ActionID]) {
559
+ this.callbacks[parsed.ActionID](parsed);
560
+ delete this.callbacks[parsed.ActionID];
561
+ }
562
+ if (parsed.Event) {
563
+ const handlers = this.eventHandlers[parsed.Event] || [];
564
+ for (const h of handlers) h(parsed);
565
+ }
566
+ }
567
+ }
568
+
569
+ close() {
570
+ if (this.socket) {
571
+ this.socket.write('Action: Logoff\r\n\r\n');
572
+ this.socket.end();
573
+ }
574
+ }
575
+ }
576
+
577
+ // Usage
578
+ async function main() {
579
+ const ami = new AMIClient('127.0.0.1', 5038);
580
+ await ami.connect();
581
+ const loginResult = await ami.login('vicistack', 'your_password');
582
+ console.log('Login:', loginResult);
583
+
584
+ // Listen for call events
585
+ ami.on('Newchannel', (event) => {
586
+ console.log('New call:', event.CallerIDNum, event.Channel);
587
+ });
588
+
589
+ ami.on('Hangup', (event) => {
590
+ console.log('Call ended:', event.Channel, event['Cause-txt']);
591
+ });
592
+
593
+ // Keep running to receive events
594
+ // ami.close() when done
595
+ }
596
+
597
+ main().catch(console.error);
598
+ ```
599
+
600
+ ---
601
+
602
+ ## AMI and VICIdial: How They Work Together
603
+
604
+ VICIdial's relationship with AMI is deep. Here's what's happening behind the scenes:
605
+
606
+ **Auto-dial engine (VDauto_dial.pl):** Connects to AMI and sends Originate actions for each lead pulled from the hopper. It monitors Newchannel, Bridge, and Hangup events to track call state.
607
+
608
+ **Agent interface (vicidial.php):** Uses AMI to transfer calls, hang up channels, initiate manual dials, and monitor agent channel state.
609
+
610
+ **Real-time reporting:** Queries AMI via the Status action and CoreShowChannels to build the real-time agent display.
611
+
612
+ **Call recording:** Uses AMI's Monitor/MixMonitor actions to start and stop recordings.
613
+
614
+ If you're building custom integrations with VICIdial, you don't usually talk to AMI directly. VICIdial has its own [Non-Agent API](/blog/vicidial-api-integration/) that wraps AMI actions. But for direct Asterisk integrations, AMI is the way.
615
+
616
+ ### VICIdial's AMI User
617
+
618
+ On a ViciBox install, VICIdial creates its own AMI user in manager.conf. Don't modify or share this user. Create a separate AMI user for your integrations:
619
+
620
+ ```ini
621
+ ; Add below the existing VICIdial user
622
+ [myintegration]
623
+ secret = separate_strong_password
624
+ deny = 0.0.0.0/0.0.0.0
625
+ permit = 127.0.0.1/255.255.255.255
626
+ read = call,agent,cdr
627
+ write = originate
628
+ ```
629
+
630
+ ---
631
+
632
+ ## Production Hardening
633
+
634
+ ### Connection Pooling
635
+
636
+ AMI connections are persistent TCP sockets. Opening a new connection for each request wastes time on TCP handshake + authentication. Maintain a pool of authenticated connections.
637
+
638
+ ### Automatic Reconnection
639
+
640
+ Asterisk restarts, network blips, and TCP timeouts will drop your AMI connection. Your client must detect disconnection and automatically reconnect + re-authenticate.
641
+
642
+ ```python
643
+ # Simplified reconnection loop
644
+ def get_ami_connection():
645
+ while True:
646
+ try:
647
+ ami = AMIClient()
648
+ ami.connect()
649
+ ami.login('user', 'secret')
650
+ return ami
651
+ except Exception as e:
652
+ print(f'AMI connection failed: {e}, retrying in 5s')
653
+ time.sleep(5)
654
+ ```
655
+
656
+ ### Event Filtering
657
+
658
+ A busy Asterisk system generates thousands of events per minute. If your integration only cares about Hangup events, filter server-side:
659
+
660
+ ```
661
+ Action: Events
662
+ EventMask: call
663
+
664
+ ```
665
+
666
+ Or more granular filtering with Asterisk 16+:
667
+
668
+ ```
669
+ Action: Filter
670
+ Operation: Add
671
+ Filter: Event: Hangup
672
+
673
+ ```
674
+
675
+ This tells Asterisk to only send you Hangup events, reducing bandwidth and processing overhead.
676
+
677
+ ### Rate Limiting Your Actions
678
+
679
+ Don't blast AMI with hundreds of Originate actions per second. Asterisk processes AMI actions sequentially per connection. Flooding it will queue up actions and increase latency for all of them.
680
+
681
+ For high-volume origination (like a predictive dialer), use multiple AMI connections with load distribution. VICIdial does this — it maintains multiple connections and distributes originate requests across them.
682
+
683
+ ### Security Considerations
684
+
685
+ 1. **Never expose port 5038 to the internet.** SSH tunnels or VPN for remote access.
686
+ 2. **Use strong passwords.** AMI has no lockout after failed attempts.
687
+ 3. **Restrict permissions.** Don't give `command` or `system` write access unless absolutely necessary.
688
+ 4. **Log AMI connections.** Enable `displayconnects = yes` in manager.conf and monitor `/var/log/asterisk/messages` for login events.
689
+ 5. **Consider TLS.** Asterisk 16+ supports AMI over TLS. Configure it if your AMI client connects over a network.
690
+
691
+ ```ini
692
+ ; manager.conf — TLS support (Asterisk 16+)
693
+ [general]
694
+ enabled = yes
695
+ port = 5038
696
+ tlsenable = yes
697
+ tlsbindaddr = 0.0.0.0:5039
698
+ tlscertfile = /etc/asterisk/keys/asterisk.pem
699
+ tlsprivatekey = /etc/asterisk/keys/asterisk.key
700
+ ```
701
+
702
+ ---
703
+
704
+ ## Common AMI Pitfalls
705
+
706
+ **ActionID is your friend.** Always include an ActionID in your actions. AMI responses don't always come back in order, especially with async actions like Originate. The ActionID is the only way to correlate a response with the action that triggered it.
707
+
708
+ **Originate is async by default.** When you send an Originate action, the Response: Success just means Asterisk accepted the request. It doesn't mean the call connected. You need to listen for Newchannel, Dial, Bridge, and Hangup events to track the call's lifecycle.
709
+
710
+ ```
711
+ Action: Originate
712
+ Channel: SIP/carrier/18005551234
713
+ Context: from-internal
714
+ Exten: 100
715
+ Priority: 1
716
+ Async: true
717
+ ActionID: call-001
718
+
719
+ ```
720
+
721
+ **Buffer carefully.** AMI messages are terminated by `\r\n\r\n`. But messages can arrive fragmented across multiple TCP reads. Your parser must buffer data and only process complete messages.
722
+
723
+ **Handle multi-line responses.** Some responses (like Command output) span multiple lines. They end with `--END COMMAND--` before the `\r\n\r\n` terminator. Your parser needs to handle this.
724
+
725
+ **Event floods during peak hours.** On a system with 100+ concurrent calls, AMI can generate 50+ events per second. If your client can't keep up processing events, the TCP buffer fills and you lose data. Use event filtering to only subscribe to events you actually need.
726
+
727
+ ---
728
+
729
+ ## Useful AMI One-Liners
730
+
731
+ For quick operations from the command line without building a full client:
732
+
733
+ ```bash
734
+ # Quick call origination via netcat
735
+ echo -e "Action: Login\r\nUsername: user\r\nSecret: pass\r\n\r\nAction: Originate\r\nChannel: SIP/carrier/18005551234\r\nContext: default\r\nExten: 100\r\nPriority: 1\r\n\r\nAction: Logoff\r\n\r\n" | nc localhost 5038
736
+
737
+ # Check how many active channels
738
+ echo -e "Action: Login\r\nUsername: user\r\nSecret: pass\r\n\r\nAction: Command\r\nCommand: core show channels count\r\n\r\nAction: Logoff\r\n\r\n" | nc localhost 5038
739
+
740
+ # Reload SIP configuration remotely
741
+ echo -e "Action: Login\r\nUsername: user\r\nSecret: pass\r\n\r\nAction: Command\r\nCommand: sip reload\r\n\r\nAction: Logoff\r\n\r\n" | nc localhost 5038
742
+ ```
743
+
744
+ ---
745
+
746
+ ## AMI for Real-Time Dashboards
747
+
748
+ The killer use case for AMI in a call center is [real-time monitoring](/blog/vicidial-realtime-agent-dashboard/). Here's the event flow for building a dashboard:
749
+
750
+ 1. Connect to AMI, authenticate, subscribe to `call` and `agent` events
751
+ 2. On **Newchannel**: New call starting, add to active calls list
752
+ 3. On **Dial**: Call is ringing, show in "ringing" state
753
+ 4. On **Bridge**: Call answered, start timer, show in "connected" state
754
+ 5. On **Hangup**: Call ended, calculate duration, remove from active calls
755
+ 6. On **AgentConnect**: Agent took a call, update agent status
756
+ 7. On **AgentComplete**: Agent finished a call, show wrap-up time
757
+
758
+ Feed these events into a WebSocket server and push to a browser-based dashboard. That's exactly how VICIdial's Real-Time Report works, though it goes through PHP + database polling rather than direct WebSocket.
759
+
760
+ For a more modern approach, push AMI events into Redis or a message queue and have your dashboard consume from there. This decouples the AMI connection from the web layer and handles connection drops gracefully.
761
+
762
+ ---
763
+
764
+ **Building custom integrations on top of VICIdial?** ViciStack has done this for dozens of operations — CRM integrations, custom wallboards, automated quality monitoring, and click-to-call systems. We know where AMI works well and where you'll hit walls. Our engagement is $5K ($1K deposit, $4K on completion) and includes architecture review, implementation guidance, and AMI security hardening. [Let's talk](/contact/).
765
+
766
+ ---
767
+
768
+ *Related: [VICIdial API Integration](/blog/vicidial-api-integration/) | [VICIdial Grafana Dashboards](/blog/vicidial-grafana-dashboards/) | [VICIdial Asterisk Configuration](/blog/vicidial-asterisk-configuration/) | [VICIdial CRM Integration](/blog/vicidial-crm-integration/)*
769
+
770
+ ## Resources
771
+
772
+ - [Read the full article](https://vicistack.com/blog/asterisk-manager-interface-guide/) on ViciStack
773
+ - [ViciStack](https://vicistack.com) - VICIdial hosting and optimization
774
+ - [Free VICIdial Audit](https://vicistack.com/free-audit/)