PHP api design - anything done badly?
What you are really trying to accomplish here is taking http requests and dispatching them to the correct class. This is much better handled by one of many mvc frameworks in existence. That said, I see several problems with what you have here.


Improper check for POST requests. $_POST is always available
No validation of input. Do you want to allow unrestricted access to classes and methods?
Gateway class (which seems more like a dispatcher) is concerned with too much. Not only is it dispatching the request, it is also formatting responses.
No use of http header to properly instruct the client. Put the status codes (200, 404, etc) in the response header, not in the payload.
You are potentially exposing sensitive information by passing back the Exception object's message. When things go wrong, YOU need to know what and why, but the client does not.


Here is a rough improvement:

GatewayExeption.php

&lt;?php
//Use your own exception to distinguish from others
class GatewayException extends Exception {}


Gateway.php

&lt;?php
class Gateway
{
    /**
     * @var array
     */
    private $apis;

    /**
     * Pass an array of pre-configured api class and actions
     * 
     * array(
     *   'Foo' =&gt; array('bar', 'baz')
     * )
     * 
     * @param array $apis
     */
    public function __construct($apis)
    {
        $this-&gt;apis = $apis;
    }

    /**
     * Invoke a pre-configured api
     * 
     * @param string $apiName
     * @param string $action
     * @param null|array $args
     * @throws GatewayException
     */
    public function invokeApi($apiName, $action, $args = array())
    {
        if (! array_key_exists($apiName, $this-&gt;apis)) {
            throw new GatewayException('Invalid api name');
        }

        if (! in_array($action, $this-&gt;apis[$apiName])) {
            throw new GatewayException('Invalid action');
        }

        try {
            $obj = new $apiName;
            $result = call_user_func(array($obj, $action), $args);
            return $result;
        } catch (Exception $e) {
            throw new GatewayException('api error', null, $e);
        }
    }
}


Sites.php

&lt;?php
class Sites
{
    public function getSites($params)
    {
        return array(
            'gotyou' =&gt; 'message'
        );
    }
}


index.php

&lt;?php
require 'GatewayException.php'
require 'Gateway.php'

if ( $_SERVER['REQUEST_METHOD'] !== 'POST') {
    header('HTTP/1.0 405 Method Not Allowed');
    exit;
}

//Provide an explicit list of available apis
$validApis = array(
    'Sites' =&gt; array('getSites')
);
$gateway = new Gateway($validApis);

//initialize request parameters with defaults
$api = array_key_exists('api', $_POST) ? $_POST['api'] : '';
$action = array_key_exists('action', $_POST) ? $_POST['action'] : '';
$params = array_key_exists('params', $_POST) ? $_POST['params'] : null;

//all responses at this point will be json
header('Content-Type: application/json');

try {
    $result = $gateway-&gt;invokeApi($api, $action, $params);
    echo json_encode($result);
} catch (GatewayException $e) {
    header('HTTP/1.0 400 Bad Request');
    echo json_encode( array(
        'status'=&gt; $e-&gt;getMessage()
    ));
} catch (Exception $e) {
    header('HTTP/1.0 500 Internal Server Error');
    //you should log $e-&gt;getMessage()
    //you probably do not want to reveal the Exception's message
    echo json_encode( array(
        'status'=&gt; 'A system error occurred' 
    ));
}