XF 2.2 How to create custom api endpoints?

FoxSecrets

Active member
How can I create custom API POST endpoint to my data?
I didn't find anything related to it in documentation.
 
Look under XF Admin -> Development -> Routes

There’s a tab for API (similar to Admin and Public routes).

Basically you define a route similar to other routes and then you have an API controller for that route.
 
Yes, the API system is just another app (like the public or admin apps). Much of the same principles apply, only it renders API results instead of views and such. You can take a look at one of the core API controllers to get a feel for the differences (src/XF/Api/Controller/*.php). For your entities, you'll need to add setupApiResultData methods and modify the structure to add 'api' => true to the columns and relations you want to include in the results.
 
Look under XF Admin -> Development -> Routes

There’s a tab for API (similar to Admin and Public routes).

Basically you define a route similar to other routes and then you have an API controller for that route.
Yes, I've seen that. But for example, how to setup a post request in case you have to send a body payload? How to receive it?

Yes, the API system is just another app (like the public or admin apps). Much of the same principles apply, only it renders API results instead of views and such. You can take a look at one of the core API controllers to get a feel for the differences (src/XF/Api/Controller/*.php). For your entities, you'll need to add setupApiResultData methods and modify the structure to add 'api' => true to the columns and relations you want to include in the results.
Right. It seems a little complex to me. Any detailed documentation or even a real example of it?
The one available is very superficial: https://xenforo.com/docs/dev/rest-api/
 
The best “documentation” is just going to be looking at how the default API routes work. There are methods in there to assert that a request is a post for example. I’m walking around San Francisco like a tourist nerd right now, so I don’t have access to a computer to look up more detailed info, sorry.
 
Last edited:
Right. It seems a little complex to me. Any detailed documentation or even a real example of it?
Not really, other than the code itself. It's not too different from a regular controller, only the actions include the HTTP verbs:

PHP:
<?php

namespace Your\Api\Controller;

use XF\Api\Controller\AbstractController;
use XF\Api\Mvc\Reply\ApiResult;
use XF\Mvc\Entity\Entity;
use XF\Mvc\ParameterBag;

class Things extends AbstractController
{
    protected function preDispatchController($action, ParameterBag $params): void
    {
        // assuming you've created the scope 'thing'
        $this->assertApiScopeByRequestMethod('thing');
    }

    public function actionPost(): ApiResult
    {
        // your creation logic -- assuming $thing is the created entity

        return $this->apiSuccess([
            'thing' => $thing->toApiResult(Entity::VERBOSITY_VERBOSE)
        ]);
    }
}

You can see \XF\Api\Controller\Posts::actionPost for a real-world example. It doesn't differ too much from \XF\Pub\Controller\Thread::actionAddReply. If you have specific questions feel free to ask.

I’m walking around San Francisco like a tourist nerd right now, so I don’t have access to a computer to look up more detailed info, sorry.
I'm missing Dolores Park right about now... 🙂
 
modify the structure to add 'api' => true to the columns and relations you want to include in the results
Is that necessary?

Since I'm adding functionality to an existing add-on, I added a function setupApiResultData {}
in My\Addon\Extended\Addon\Entity;
and configured the results I'd like such as:
$result->title= $this->title;

Is there a better way to do so?
 
I need help @Jeremy P .
So I've set scope and api url. So first question, where it goes the api key? I created a simple request and set XF-Api-Key on client side header but it returns "do_not_have_permission". I also tried to get users as example with permission to user scope (http://localhost:8000/api/products/users/?page=1) and got "endpoint_not_found".

I have no idea how to implement, just need a crud example.
Basically what I need is the user that will post a product, then if exists just update, otherwise create.
How can I read the post params? And how to respond with a json message?

Code:
public function actionPost(ParameterBag $params): ApiResult
    {
        $this->assertRequiredApiInput(['sku', 'product', 'qty']);
        $productSku = $this->filter('sku', 'uint');

        if ($this->request->exists('sku') && $productSku)
        {
            $product = $this->assertProductExists($productSku);
        }
        else
        {
            $product = $this->em()->create('FOX\ProductManager:Product');
        }

        return $this->apiSuccess([
            'product' => $product->toApiResult(Entity::VERBOSITY_VERBOSE)
        ]);
    }
 
Last edited:
Is that necessary?
Not if you're setting it manually, but adding 'api' => true to the column or relation would accomplish the same thing. You could also do $result->includeColumn('title') in the method.

So I've set scope and api url. So first question, where it goes the api key? I created a simple request and set XF-Api-Key on client side header but it returns "do_not_have_permission".
If the API key was passed incorrectly, you would get a different error (API key provided in request was not found). Does a basic request succeed (ie. GET /api/index/)?

I also tried to get users as example with permission to user scope (http://localhost:8000/api/products/users/?page=1) and got "endpoint_not_found".
Is products/ in the route intentional? The regular end-point for users is /api/users/. If you've created a custom products endpoint, that would map to the actionGetUsers method of your corresponding controller.

How can I read the post params?
The same way as usual, $this->filter('some_param', 'some_type').

And how to respond with a json message?
PHP:
return $this->apiSuccess([
    'message' => \XF::phrase('some_message')
]);
 
If the API key was passed incorrectly, you would get a different error (API key provided in request was not found). Does a basic request succeed (ie. GET /api/index/)?
Yes, it does (below), so the key is correct. Why can't I get users? /api/users/?page=1

Code:
{
    "version_id": 2020870,
    "site_title": "XenForo",
    "base_url": "http://localhost:8000",
    "api_url": "http://localhost:8000/api/",
    "key": {
        "type": "guest",
        "user_id": null,
        "allow_all_scopes": true,
        "scopes": []
    }
}
 
Your API key is authenticated as a guest user. The usual options and permissions apply. Is the member list option enabled and do guests have permission to view it?
 
The option was hidden and I only knew about it because of tiny text at the bottom of the page! Omg! :eek:
Now I can see api get working! thanks @Brogan and @Jeremy P
Next step is to make my custom method work! Let's see....
 
FWIW permissions can be bypassed with a super-user key by setting api_bypass_permissions to false in the request, and API requests tied to an account with administrative user permissions can access the users endpoint regardless of the option/permissions as well.

Also the ACP search will surface options which are usually hidden.
 
Trying to make my custom get from my table "products", but still no success, it's returning 404 "requested_page_not_found".
What does it mean? Wrong url?

Code:
public function actionGet(ParameterBag $params)
    {
        $this->assertApiScope('product:read');

        $product = $this->assertViewableProduct($params->product_id);

        $result = [
            'product' => $product->toApiResult(Entity::VERBOSITY_VERBOSE)
        ];
        return $this->apiResult($result);
    }
 
If you \XF::dump($params) do you get the expected input? What about for $product? What does your assertViewableProduct method contain?
 
Top Bottom