XF 2.2 How to create custom api endpoints?

Please check

Code:
<?php

//...
    $structure->columns = [
      'product_id'   => ['type' => self::UINT, 'autoIncrement' => true],
      'sku'          => ['type' => self::STR, 'default' => ''],
      'product'      => ['type' => self::STR, 'maxLength' => 50, 'default' => ''],
      'qty'          => ['type' => self::UINT, 'default' => ''],
      'api'          => ['type' => self::BOOL, 'default' => true],
    ];
    $structure->relations = [
      'User' => [
        'entity' => 'XF:User',
        'type' => self::TO_ONE,
        'conditions' => 'user_id',
        'primary' => true,
        'api' => true
      ],
    ];
//...
}
 
Last edited:
You only have 'api' => true set for the user relation. You'd need to set it on each column you want to include.

PHP:
$structure->columns = [
    // ...
    'sku' => [
        'type' => self::STR,
        'default' => '',
        'api' => true,
    ],
    // ...
];
 
Oh so I misunderstood, I've put as a new column in 'products' table 'api' => ['type' => self::BOOL, 'default' => true],. Fixed now.
And how can I attach a new field such a success message (not as a column) ?
 
Last edited:
Got it.
Code:
       return $this->apiSuccess([
            'product' => $product->toApiResult(Entity::VERBOSITY_VERBOSE),
            'message' => \XF::phrase('product_successfully_added')
        ]);
 
Still related to this topic, how to correctly configure the client side?
When I submit the form, it says "action not found", but it exists and runs because it shows the return in console.
How can the response be mixed with "action not found"? that's no sense. Also, it returns to console, not in function. What am I doing wrong?

In my tests, I'm posting wrong value to enter in "if" condition and get api error message.

ACTION:
Code:
public function actionUpdate()
    {
        $this->assertPostOnly();

        $url = 'https://domain.com/api/products/';

        $form = [
            'sku' => $this->filter(['sku' => 'str']),
            'product_name' => $this->filter(['product_name' => 'str']),
            'qty' => $this->filter(['qty' => 'str'])
        ];

        $header = [
            'Content-Type: application/x-www-form-urlencoded',
            'XF-Api-Key: xxxxxxxxxxxxxxxxxxxxxxxxx'
        ];

        $ch = curl_init();
        curl_setopt($ch,CURLOPT_URL, $url);
        curl_setopt($ch,CURLOPT_POST, true);
        curl_setopt($ch,CURLOPT_POSTFIELDS, http_build_query($form));
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        curl_setopt($ch,CURLOPT_RETURNTRANSFER, true);
        $result = curl_exec($ch);
        curl_close($ch);

        if (!$result) {
            $message = $result['message'];
            return $this->error(\XF::phrase($message));
        }

        return $result['status'];
    }

CONSOLE:
Code:
49271:66     POST http://localhost:8000/admin.php?fox-products/update 404 (Not Found)

PHP: string(237) "{
    "errors": [
        {
            "code": "admin.fox_products_message_error_not_found",
            "message": "Unable to update the product.",
            "params": []
        }
    ]
}"
{
    "status": "error",
    "errors": [
        "The requested page could not be found. (Code: no_reply, controller: FOX\\Products:Product, action: Update)"
    ],
    "errorHtml": {
        "content": "\n\n<div class=\"blockMessage\">\n\t\n\t\tThe requested page could not be found. (Code: no_reply, controller: FOX\\Products:Product, action: Update)\n\t\n</div>",
        "title": "Oops! We ran into some problems."
    },
    "visitor": {
        "conversations_unread": "0",
        "alerts_unviewed": "0",
        "total_unread": "0"
    },
    "debug": {
        "time": 0.8444,
        "queries": 5,
        "memory": 8.45
    }
}
 
Last edited:
All controller actions must return reply objects, which is why you're getting a no_reply error code.

Also the built-in Guzzle client is preferred for making requests instead of using cURL directly:
PHP:
// ...

$client = $this->app->http()->client();
$options = [
    'headers' => $header,
    'form_params' => $form,
];

$response = $client->post($url, $options);
$result = \GuzzleHttp\json_decode($response->getBody(), true);

// ...
 
This Guzzle client is new for me. How can I know what clients are included in XF?

I'm getting error of missing api key, but it's being sent, I didn't understand :unsure:
I did not find any related info in documentation. Any idea?

[B]GuzzleHttp\Exception\ClientException[/B]: Client error: `POST https://domain.com/api/products/` resulted in a `400 Bad Request` response: { "errors": [ { "code": "no_api_key_in_request", "message": "No API key was included (truncated...) in [B]src/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php[/B] at line [B]113[/B]

Code:
$header = [
            'Content-Type: application/x-www-form-urlencoded',
            'XF-Api-Key: xxxxxxxxxxxxxxxxxxxxxxxxxx'
        ];
 
Given the developer documentation only covers the basics, looking at how things are accomplished in the core is your best bet.

I'm getting error of missing api key, but it's being sent, I didn't understand :unsure:

PHP:
$header = [
   'XF-Api-Key' => 'xxx',
];

You don't need to specify the content type header manually if setting the form_params option:
 
Oh tiny detail, I was blind. Now i'm getting 400 Bad Request on response.

Code:
GuzzleHttp\Exception\ClientException: Client error: `POST https://domain.com/api/products/` resulted in a `400 Bad Request` response: { "errors": [ { "code": "fox_product_error_message", "messag (truncated...) in src/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php at line 113

I can't read it var_dump($result['errors'][0]['message']); even var_dump($result);
Shoudn't be available?
 
PHP:
try
{
    $response = $client->post($url, $options);
}
catch (\GuzzleHttp\Exception\RequestException $e)
{
    // generally you'd do error handling and return an error reply here
    // for debugging purposes, maybe try:
    $response = $e->getResponse();
}

$result = \GuzzleHttp\json_decode($response->getBody(), true);
\XF::dumpSimple($result);
 
Top Bottom