Задача достаточно прикладная, способов решения множество - просто поделюсь своим решением, которое, как мне кажется, получилось достаточно лаконичным и гибким.

API урлы будут выглядеть однообразно и классически, примерно так - /api/v1/file/info, версия, ресурс, сервис. ZF1 роутинг:

$router->addRoute('api',
   new Zend_Controller_Router_Route_Regex('api/v([0-9]+)/(.+)/(.+)'array(
      'controller' => 'api',
      'action' => 'handle',
   )array(
      => 'ver',
      => 'resource',
      => 'method',
   )'api/v%d/%s/%s')
);

API вызовы будет обрабатывать специализированный легковесный API контроллер, в обязанности которого будет входить диспетчеризация вызовов и контроль формата, про магию контроллера ниже.

<?php
class ApiController extends Zend_Controller_Action
{
    public function init()
    {
        $this->_helper->viewRenderer->setNoRender(true);
        $this->_helper->contextSwitch()
            ->initContext('json');
    }

    /**
     * @param ReflectionMethod $method
     * @return array
     */
    protected function collectParams(ReflectionMethod $method)
    {
        $params = [];
        foreach ($method->getParameters() as $param)
        {
            $param_class $param->getClass();
            $param_name $param->getName();
            if ($param_class && (($param_class->getName() === 'Zend_Controller_Action') || $param_class->isSubclassOf('\Zend_Controller_Action'))) {
                $params[] = $this;
            else {

                if ($param->isDefaultValueAvailable()) {
                    $params[] = $this->getParam($param_name$param->getDefaultValue());
                elseif ($this->hasParam($param_name)) {
                    $params[] = $this->getParam($param_name);
                else {
                    throw new DomainException("Missed required argument '$param_name'");
                }
            }
        }

        return $params;
    }

    public function handleAction()
    {
        try {
            $version $this->getParam('ver');
            $resource $this->getParam('resource');

            $class_basename '';
            foreach(preg_split('/[\-|\_|\s|\W]+/'$resourceas $part) {
                $class_basename .= ucfirst(strtolower($part));
            }
            
            $method = strtolower($this->getParam('method'));

            $class 'API\\V'.$version.'\\'.$class_basename;

            if (@class_exists($class)) {
                $reflection new ReflectionClass($class);

                if ($reflection->hasMethod($method)) {
                    $method_reflection $reflection->getMethod($method);

                    $service = ($constructor $reflection->getConstructor()) ? $reflection->newInstanceArgs($this->collectParams($constructor)) : $reflection->newInstance();

                    $return $method_reflection->invokeArgs($service$this->collectParams($method_reflection));

                    $this->_helper->json([
                        'response' => $return,
                        'status' => 200,
                        'details' => null,
                    ]);
                else {
                    throw new DomainException('Unsupported method \''.$method.'\' for \''.$resource.'\' resource');
                }
            } else {
                throw new DomainException('Unsupported \''.$resource.'\' resource - '.$class_basename);
            }
        } catch (\Exception $e) {
            $this->_helper->json([
                'response' => null,
                'status' => ($code $e->getCode()) ? $code 500,
                'details' => $e->getMessage(),
            ]);
        }
    }
}

Данный контроллер обладаем неким собственным зарядом магии, который и "делает" всю самую ценную работу. Контроллер на основании переданных параметров версии и ресурса строит имя класса, для обработки вызова. Вызов  /api/v1/file/.. трансформируется в класс API\\V1\\File. Далее через рефлекшены контроллер находит у полученного класса метод (по последнему сегменту URL), опять-же с помощью рефлекшенов проверяет какие переданные GET/POST параметры он может подставить в конструктор и метод вызова этого класса, проверяет что параметров передано достаточно, конструирует объект, вызывает найденный метод и заворачивает результат в JSON.

Практически, весь api вызов /api/v1/file/info?token=..&id=.. завернут в такую само-документирующуюся структуру:

<?php
namespace API\V1;

class File {
    use TokenHelperFileHelper;

    protected $user;

    public function __construct($token)
    {
        $this->user $this->accessToken($token);
    }

    
    public function info($id)
    {
        //.......
        return $this->mapFile($file);
    }
}

Внутри очень удобно использовать трейты в качестве набора хелперов, версионность легко обеспечивается ООП наследованием, все лаконично, гибко, достаточно безопасно и никакого овер-инжиниринга при этом.