{
  "info": {
    "_postman_id": "9cd65bad-d2ea-43f3-be87-c0f298ed40d0",
    "name": "Amical API Router",
    "description": "Partner API for Amical device operations.\n\n## Authentication\n\nEvery request requires three custom headers:\n- `client_id` — your API key ID (from the Amical admin panel)\n- `timestamp` — current epoch in milliseconds (must be within ±5 minutes of server time)\n- `signature` — Base64-encoded HMAC-SHA256 of the string `{timestamp}.{client_id}.{rawBody}` using your secret key\n\nNo Supabase JWT is required (`verify_jwt = false`). The pre-request script computes all three headers automatically.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "_exporter_id": "3635474"
  },
  "item": [
    {
      "name": "Device Call Agent",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"action\": \"device_call_agent\",\n  \"amical_id\": \"{{amical_id}}\",\n  \"context_block\": \"\",\n  \"first_message\": \"\",\n  \"webhook_url\": \"\",\n  \"external_id\": \"\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{base_url}}",
          "host": [
            "{{base_url}}"
          ]
        },
        "description": "Initiates a voice call to the device's agent.\n\n### Required fields\n| Field | Type | Description |\n|---|---|---|\n| `action` | string | Must be `device_call_agent` |\n| `amical_id` | string | Target device identifier |\n\n### Optional fields\n| Field | Type | Description |\n|---|---|---|\n| `context_block` | string | Extra context appended to the system prompt |\n| `first_message` | string | Overrides the agent's default first message |\n| `webhook_url` | string | URL to receive call event callbacks |\n| `external_id` | string | Your external reference ID |\n\n### Success response (200)\n```json\n{\n  \"success\": true,\n  \"data\": { \"id\": \"<incoming_call_uuid>\" },\n  \"requestId\": \"<uuid>\"\n}\n```\n\n### Error responses\n| Status | Title |\n|---|---|\n| 400 | `invalid_body` |\n| 401 | `signature_invalid`, `timestamp_expired`, `client_id_invalid` |\n| 403 | `device_access_denied`, `secret_key_not_configured` |\n| 404 | `device_not_found` |\n| 500 | `internal_error`, `persist_failed`, `incoming_call_update_failed` |\n| 502 | `agent_config_error`, `voicyn_create_agent_error`, `voicyn_call_error` |"
      },
      "response": [
        {
          "name": "200 – Success",
          "originalRequest": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"action\": \"device_call_agent\",\n  \"amical_id\": \"{{amical_id}}\",\n  \"context_block\": \"\",\n  \"first_message\": \"\",\n  \"webhook_url\": \"\",\n  \"external_id\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}",
              "host": [
                "{{base_url}}"
              ]
            },
            "description": "Initiates a voice call to the device's agent.\n\n### Required fields\n| Field | Type | Description |\n|---|---|---|\n| `action` | string | Must be `device_call_agent` |\n| `amical_id` | string | Target device identifier |\n\n### Optional fields\n| Field | Type | Description |\n|---|---|---|\n| `context_block` | string | Extra context appended to the system prompt |\n| `first_message` | string | Overrides the agent's default first message |\n| `webhook_url` | string | URL to receive call event callbacks |\n| `external_id` | string | Your external reference ID |\n\n### Success response (200)\n```json\n{\n  \"success\": true,\n  \"data\": { \"id\": \"<incoming_call_uuid>\" },\n  \"requestId\": \"<uuid>\"\n}\n```\n\n### Error responses\n| Status | Title |\n|---|---|\n| 400 | `invalid_body` |\n| 401 | `signature_invalid`, `timestamp_expired`, `client_id_invalid` |\n| 403 | `device_access_denied`, `secret_key_not_configured` |\n| 404 | `device_not_found` |\n| 500 | `internal_error`, `persist_failed`, `incoming_call_update_failed` |\n| 502 | `agent_config_error`, `voicyn_create_agent_error`, `voicyn_call_error` |"
          },
          "status": "OK",
          "code": 200,
          "_postman_previewlanguage": "Text",
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "cookie": [],
          "body": "{\n  \"success\": true,\n  \"data\": {\n    \"id\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\"\n  },\n  \"requestId\": \"f0e1d2c3-b4a5-6789-0abc-def123456789\"\n}"
        },
        {
          "name": "404 – Device Not Found",
          "originalRequest": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"action\": \"device_call_agent\",\n  \"amical_id\": \"{{amical_id}}\",\n  \"context_block\": \"\",\n  \"first_message\": \"\",\n  \"webhook_url\": \"\",\n  \"external_id\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}",
              "host": [
                "{{base_url}}"
              ]
            },
            "description": "Initiates a voice call to the device's agent.\n\n### Required fields\n| Field | Type | Description |\n|---|---|---|\n| `action` | string | Must be `device_call_agent` |\n| `amical_id` | string | Target device identifier |\n\n### Optional fields\n| Field | Type | Description |\n|---|---|---|\n| `context_block` | string | Extra context appended to the system prompt |\n| `first_message` | string | Overrides the agent's default first message |\n| `webhook_url` | string | URL to receive call event callbacks |\n| `external_id` | string | Your external reference ID |\n\n### Success response (200)\n```json\n{\n  \"success\": true,\n  \"data\": { \"id\": \"<incoming_call_uuid>\" },\n  \"requestId\": \"<uuid>\"\n}\n```\n\n### Error responses\n| Status | Title |\n|---|---|\n| 400 | `invalid_body` |\n| 401 | `signature_invalid`, `timestamp_expired`, `client_id_invalid` |\n| 403 | `device_access_denied`, `secret_key_not_configured` |\n| 404 | `device_not_found` |\n| 500 | `internal_error`, `persist_failed`, `incoming_call_update_failed` |\n| 502 | `agent_config_error`, `voicyn_create_agent_error`, `voicyn_call_error` |"
          },
          "status": "Not Found",
          "code": 404,
          "_postman_previewlanguage": "Text",
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "cookie": [],
          "body": "{\n  \"success\": false,\n  \"error\": {\n    \"title\": \"device_not_found\",\n    \"message\": \"device_not_found\",\n    \"status\": 404\n  },\n  \"requestId\": \"f0e1d2c3-b4a5-6789-0abc-def123456789\"\n}"
        },
        {
          "name": "403 – Device Access Denied",
          "originalRequest": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"action\": \"device_call_agent\",\n  \"amical_id\": \"{{amical_id}}\",\n  \"context_block\": \"\",\n  \"first_message\": \"\",\n  \"webhook_url\": \"\",\n  \"external_id\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}",
              "host": [
                "{{base_url}}"
              ]
            },
            "description": "Initiates a voice call to the device's agent.\n\n### Required fields\n| Field | Type | Description |\n|---|---|---|\n| `action` | string | Must be `device_call_agent` |\n| `amical_id` | string | Target device identifier |\n\n### Optional fields\n| Field | Type | Description |\n|---|---|---|\n| `context_block` | string | Extra context appended to the system prompt |\n| `first_message` | string | Overrides the agent's default first message |\n| `webhook_url` | string | URL to receive call event callbacks |\n| `external_id` | string | Your external reference ID |\n\n### Success response (200)\n```json\n{\n  \"success\": true,\n  \"data\": { \"id\": \"<incoming_call_uuid>\" },\n  \"requestId\": \"<uuid>\"\n}\n```\n\n### Error responses\n| Status | Title |\n|---|---|\n| 400 | `invalid_body` |\n| 401 | `signature_invalid`, `timestamp_expired`, `client_id_invalid` |\n| 403 | `device_access_denied`, `secret_key_not_configured` |\n| 404 | `device_not_found` |\n| 500 | `internal_error`, `persist_failed`, `incoming_call_update_failed` |\n| 502 | `agent_config_error`, `voicyn_create_agent_error`, `voicyn_call_error` |"
          },
          "status": "Forbidden",
          "code": 403,
          "_postman_previewlanguage": "Text",
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "cookie": [],
          "body": "{\n  \"success\": false,\n  \"error\": {\n    \"title\": \"device_access_denied\",\n    \"message\": \"device_access_denied\",\n    \"status\": 403\n  },\n  \"requestId\": \"f0e1d2c3-b4a5-6789-0abc-def123456789\"\n}"
        }
      ]
    },
    {
      "name": "Device Get Status",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"action\": \"device_get_status\",\n  \"amical_id\": \"{{amical_id}}\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{base_url}}",
          "host": [
            "{{base_url}}"
          ]
        },
        "description": "Retrieves the current status of a device from Voicyn.\n\n### Required fields\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `action` | string | Must be `device_get_status` |\n| `amical_id` | string | Target device identifier |\n\n### Success response (200)\n\n``` json\n{\n  \"success\": true,\n  \"data\": {\n    \"device\": {\n      \"amical_id\": \"ABC-123\",\n      \"status\": \"online\"\n    }\n  },\n  \"requestId\": \"<uuid>\"\n}\n\n ```\n\n### Error responses\n\n| Status | Title |\n| --- | --- |\n| 400 | `invalid_body` |\n| 401 | `signature_invalid`, `timestamp_expired`, `client_id_invalid` |\n| 403 | `device_access_denied`, `secret_key_not_configured` |\n| 404 | `device_not_found`, `device_not_provisioned` |\n| 500 | `internal_error`, `persist_failed` |\n| 502 | `voicyn_error` |"
      },
      "response": [
        {
          "name": "200 – Success",
          "originalRequest": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"action\": \"device_get_status\",\n  \"amical_id\": \"{{amical_id}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}",
              "host": [
                "{{base_url}}"
              ]
            },
            "description": "Retrieves the current status of a device from Voicyn.\n\n### Required fields\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `action` | string | Must be `device_get_status` |\n| `amical_id` | string | Target device identifier |\n\n### Success response (200)\n\n``` json\n{\n  \"success\": true,\n  \"data\": {\n    \"device\": {\n      \"amical_id\": \"ABC-123\",\n      \"status\": \"online\"\n    }\n  },\n  \"requestId\": \"<uuid>\"\n}\n\n ```\n\n### Error responses\n\n| Status | Title |\n| --- | --- |\n| 400 | `invalid_body` |\n| 401 | `signature_invalid`, `timestamp_expired`, `client_id_invalid` |\n| 403 | `device_access_denied`, `secret_key_not_configured` |\n| 404 | `device_not_found`, `device_not_provisioned` |\n| 500 | `internal_error`, `persist_failed` |\n| 502 | `voicyn_error` |"
          },
          "status": "OK",
          "code": 200,
          "_postman_previewlanguage": "Text",
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "cookie": [],
          "body": "{\n  \"success\": true,\n  \"data\": {\n    \"device\": {\n      \"amical_id\": \"ABC-123\",\n      \"status\": \"online\"\n    }\n  },\n  \"requestId\": \"f0e1d2c3-b4a5-6789-0abc-def123456789\"\n}"
        },
        {
          "name": "404 – Device Not Found",
          "originalRequest": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"action\": \"device_get_status\",\n  \"amical_id\": \"{{amical_id}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}",
              "host": [
                "{{base_url}}"
              ]
            },
            "description": "Retrieves the current status of a device from Voicyn.\n\n### Required fields\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `action` | string | Must be `device_get_status` |\n| `amical_id` | string | Target device identifier |\n\n### Success response (200)\n\n``` json\n{\n  \"success\": true,\n  \"data\": {\n    \"device\": {\n      \"amical_id\": \"ABC-123\",\n      \"status\": \"online\"\n    }\n  },\n  \"requestId\": \"<uuid>\"\n}\n\n ```\n\n### Error responses\n\n| Status | Title |\n| --- | --- |\n| 400 | `invalid_body` |\n| 401 | `signature_invalid`, `timestamp_expired`, `client_id_invalid` |\n| 403 | `device_access_denied`, `secret_key_not_configured` |\n| 404 | `device_not_found`, `device_not_provisioned` |\n| 500 | `internal_error`, `persist_failed` |\n| 502 | `voicyn_error` |"
          },
          "status": "Not Found",
          "code": 404,
          "_postman_previewlanguage": "Text",
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "cookie": [],
          "body": "{\n  \"success\": false,\n  \"error\": {\n    \"title\": \"device_not_found\",\n    \"message\": \"device_not_found\",\n    \"status\": 404\n  },\n  \"requestId\": \"f0e1d2c3-b4a5-6789-0abc-def123456789\"\n}"
        },
        {
          "name": "404 – Device Not Provisioned",
          "originalRequest": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"action\": \"device_get_status\",\n  \"amical_id\": \"{{amical_id}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}",
              "host": [
                "{{base_url}}"
              ]
            },
            "description": "Retrieves the current status of a device from Voicyn.\n\n### Required fields\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `action` | string | Must be `device_get_status` |\n| `amical_id` | string | Target device identifier |\n\n### Success response (200)\n\n``` json\n{\n  \"success\": true,\n  \"data\": {\n    \"device\": {\n      \"amical_id\": \"ABC-123\",\n      \"status\": \"online\"\n    }\n  },\n  \"requestId\": \"<uuid>\"\n}\n\n ```\n\n### Error responses\n\n| Status | Title |\n| --- | --- |\n| 400 | `invalid_body` |\n| 401 | `signature_invalid`, `timestamp_expired`, `client_id_invalid` |\n| 403 | `device_access_denied`, `secret_key_not_configured` |\n| 404 | `device_not_found`, `device_not_provisioned` |\n| 500 | `internal_error`, `persist_failed` |\n| 502 | `voicyn_error` |"
          },
          "status": "Not Found",
          "code": 404,
          "_postman_previewlanguage": "Text",
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "cookie": [],
          "body": "{\n  \"success\": false,\n  \"error\": {\n    \"title\": \"device_not_provisioned\",\n    \"message\": \"device_not_provisioned\",\n    \"status\": 404\n  },\n  \"requestId\": \"f0e1d2c3-b4a5-6789-0abc-def123456789\"\n}"
        }
      ]
    },
    {
      "name": "Unsupported Action (example)",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"action\": \"some_unknown_action\",\n  \"amical_id\": \"{{amical_id}}\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{base_url}}",
          "host": [
            "{{base_url}}"
          ]
        },
        "description": "Demonstrates the 501 response when sending an unsupported action."
      },
      "response": [
        {
          "name": "501 – Action Not Supported",
          "originalRequest": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"action\": \"some_unknown_action\",\n  \"amical_id\": \"{{amical_id}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}",
              "host": [
                "{{base_url}}"
              ]
            },
            "description": "Demonstrates the 501 response when sending an unsupported action."
          },
          "status": "Not Implemented",
          "code": 501,
          "_postman_previewlanguage": "Text",
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "cookie": [],
          "body": "{\n  \"success\": false,\n  \"error\": {\n    \"title\": \"action_not_supported\",\n    \"message\": \"action_not_supported\",\n    \"status\": 501\n  },\n  \"requestId\": \"f0e1d2c3-b4a5-6789-0abc-def123456789\"\n}"
        }
      ]
    }
  ],
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "requests": {},
        "exec": [
          "const secretKeyB64 = pm.collectionVariables.get(\"secret_key\");",
          "const clientId = pm.collectionVariables.get(\"client_id\");",
          "const timestamp = String(Date.now());",
          "",
          "const body = pm.request.body?.raw",
          "  ? pm.variables.replaceIn(pm.request.body.raw)",
          "  : \"\";",
          "",
          "const signingString = `${timestamp}.${clientId}.${body}`;",
          "",
          "function base64ToUint8Array(b64) {",
          "  const binary = atob(b64);",
          "  const bytes = new Uint8Array(binary.length);",
          "  for (let i = 0; i < binary.length; i++) {",
          "    bytes[i] = binary.charCodeAt(i);",
          "  }",
          "  return bytes;",
          "}",
          "",
          "function uint8ArrayToBase64(bytes) {",
          "  let binary = \"\";",
          "  for (const b of bytes) {",
          "    binary += String.fromCharCode(b);",
          "  }",
          "  return btoa(binary);",
          "}",
          "",
          "const key = await crypto.subtle.importKey(",
          "  \"raw\",",
          "  base64ToUint8Array(secretKeyB64),",
          "  { name: \"HMAC\", hash: \"SHA-256\" },",
          "  false,",
          "  [\"sign\"]",
          ");",
          "",
          "const signatureBuffer = await crypto.subtle.sign(",
          "  \"HMAC\",",
          "  key,",
          "  new TextEncoder().encode(signingString)",
          ");",
          "",
          "const signatureB64 = uint8ArrayToBase64(new Uint8Array(signatureBuffer));",
          "",
          "pm.request.headers.upsert({ key: \"timestamp\", value: timestamp });",
          "pm.request.headers.upsert({ key: \"client_id\", value: clientId });",
          "pm.request.headers.upsert({ key: \"signature\", value: signatureB64 });"
        ]
      }
    },
    {
      "listen": "test",
      "script": {
        "type": "text/javascript",
        "packages": {},
        "requests": {},
        "exec": [
          ""
        ]
      }
    }
  ],
  "variable": [
    {
      "key": "base_url",
      "value": "https://supa.amical-ai.com/functions/v1/api-router"
    },
    {
      "key": "client_id",
      "value": "",
      "description": "Your API key ID (amical_api_keys.id)"
    },
    {
      "key": "secret_key",
      "value": "",
      "description": "Your HMAC-SHA256 secret key (base64, shown once at creation)"
    },
    {
      "key": "amical_id",
      "value": "",
      "description": "The device's amical_id"
    }
  ]
}