Requirements

Nothing extra to install — Laravel's built-in HTTP client (Guzzle under the hood) is all you need.

  • Laravel 9, 10, or 11
  • illuminate/http — ships with Laravel by default
  • A NexiSim account with an API key (see "Where to buy" below)

Environment variables

Add these two lines to your .env file.

.env
NEXISIM_API_KEY=your_api_key_here
NEXISIM_BASE_URL=https://www.nexisim.co.ke/api/v2

Config file

Create this file so the key and base URL are available anywhere via config('nexisim...').

config/nexisim.php
<?php

return [
    'api_key'  => env('NEXISIM_API_KEY'),
    'base_url' => env('NEXISIM_BASE_URL', 'https://www.nexisim.co.ke/api/v2'),
];

Routes

Add this group to your existing routes file. It covers the dashboard page, the tester page, and the JSON endpoints the tester calls.

routes/web.php
use App\Http\Controllers\NexisimController;

Route::prefix('nexisim')->name('nexisim.')->group(function () {
    Route::get('/',        [NexisimController::class, 'index'])->name('index');
    Route::get('/tester',  [NexisimController::class, 'tester'])->name('tester');

    Route::get('/countries/{service}', [NexisimController::class, 'countries'])->name('countries');
    Route::post('/buy',                [NexisimController::class, 'buy'])->name('buy');
    Route::get('/check-sms/{id}',      [NexisimController::class, 'checkSms'])->name('check-sms');
    Route::get('/orders',              [NexisimController::class, 'orders'])->name('orders');
});
This gives you: /nexisim (dashboard), /nexisim/tester (test page), plus the JSON routes the tester calls under the hood.

Controller

One controller handles every endpoint. Each method returns a Blade view normally, or JSON when the request asks for it (that's what powers the tester page without duplicating logic).

app/Http/Controllers/NexisimController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;

class NexisimController extends Controller
{
    protected function client()
    {
        return Http::withToken(config('nexisim.api_key'))
            ->baseUrl(config('nexisim.base_url'));
    }

    public function index(Request $request)
    {
        $services = $this->client()->get('/services')->json();

        if ($request->wantsJson()) {
            return response()->json($services);
        }

        return view('nexisim.index', [
            'services' => $services,
        ]);
    }

    public function countries(string $service)
    {
        return response()->json(
            $this->client()->get("/countries/{$service}")->json()
        );
    }

    public function buy(Request $request)
    {
        $data = $request->validate([
            'service_id' => 'required',
            'country_id' => 'required',
        ]);

        $result = $this->client()->post('/buy', $data)->json();

        if ($request->wantsJson()) {
            return response()->json($result);
        }

        return view('nexisim.buy', [
            'result' => $result,
        ]);
    }

    public function checkSms(string $id)
    {
        return response()->json(
            $this->client()->get("/check-sms/{$id}")->json()
        );
    }

    public function orders(Request $request)
    {
        $orders = $this->client()->get('/orders')->json();

        if ($request->wantsJson()) {
            return response()->json($orders);
        }

        return view('nexisim.orders', [
            'orders' => $orders,
        ]);
    }

    public function tester()
    {
        return view('nexisim.tester');
    }
}

Dashboard view

Lists every service and lets someone buy a number for it right from the page.

resources/views/nexisim/index.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>NexiSim — Services</title>
    <style>
        body { font-family: sans-serif; background:#0b0e14; color:#e4e8f1; padding:40px; }
        .card { background:#141a24; border:1px solid #232b3a; border-radius:12px; padding:18px; margin-bottom:12px; }
        .card h3 { margin:0 0 8px; }
        form { display:flex; gap:8px; margin-top:10px; }
        input { flex:1; padding:8px; border-radius:6px; border:1px solid #232b3a; background:#0b0e14; color:#fff; }
        button { padding:8px 16px; border:none; border-radius:6px; background:#22c55e; color:#031a0b; font-weight:700; cursor:pointer; }
    </style>
</head>
<body>
    <h1>Available services</h1>

    @foreach ($services as $service)
        <div class="card">
            <h3>{{ $service['name'] }}</h3>
            <span>Service ID: {{ $service['id'] }}</span>

            <form action="{{ route('nexisim.buy') }}" method="POST">
                @csrf
                <input type="hidden" name="service_id" value="{{ $service['id'] }}">
                <input type="text" name="country_id" placeholder="Country ID" required>
                <button type="submit">Buy number</button>
            </form>
        </div>
    @endforeach

    <p><a href="{{ route('nexisim.orders') }}" style="color:#22c55e;">View orders →</a></p>
    <p><a href="{{ route('nexisim.tester') }}" style="color:#22c55e;">Open the API tester →</a></p>
</body>
</html>

Buy result view

Shows the number you just bought and polls for the OTP with a button — no extra JS framework required.

resources/views/nexisim/buy.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>NexiSim — Number purchased</title>
    <style>
        body { font-family: sans-serif; background:#0b0e14; color:#e4e8f1; padding:40px; }
        .card { background:#141a24; border:1px solid #232b3a; border-radius:12px; padding:20px; max-width:420px; }
        button { padding:10px 18px; border:none; border-radius:6px; background:#22c55e; color:#031a0b; font-weight:700; cursor:pointer; margin-top:14px; }
        pre { background:#050810; padding:12px; border-radius:8px; overflow-x:auto; margin-top:12px; }
    </style>
</head>
<body>
    <div class="card">
        <h1>Number purchased</h1>

        @if (($result['status'] ?? null) === 'success')
            <p>Phone number: <strong>{{ $result['phone_number'] }}</strong></p>
            <p>Activation ID: <strong>{{ $result['activation_id'] }}</strong></p>
            <p>Cost: <strong>{{ $result['cost'] }}</strong></p>

            <button id="checkBtn" data-id="{{ $result['activation_id'] }}">Check for SMS</button>
            <pre id="smsResult">Waiting…</pre>
        @else
            <p style="color:#f87171;">{{ $result['error'] ?? 'Something went wrong.' }}</p>
        @endif

        <p><a href="{{ route('nexisim.index') }}" style="color:#22c55e;">← Back to services</a></p>
    </div>

    <script>
        const btn = document.getElementById('checkBtn');
        if (btn) {
            btn.addEventListener('click', async () => {
                const id = btn.dataset.id;
                const res = await fetch(`/nexisim/check-sms/${id}`, {
                    headers: { 'Accept': 'application/json' }
                });
                const data = await res.json();
                document.getElementById('smsResult').textContent = JSON.stringify(data, null, 2);
            });
        }
    </script>
</body>
</html>

Orders view

A simple table of past orders.

resources/views/nexisim/orders.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>NexiSim — Orders</title>
    <style>
        body { font-family: sans-serif; background:#0b0e14; color:#e4e8f1; padding:40px; }
        table { width:100%; border-collapse:collapse; background:#141a24; border-radius:12px; overflow:hidden; }
        th, td { padding:12px 16px; text-align:left; border-bottom:1px solid #232b3a; }
        th { color:#8494b4; font-size:12px; text-transform:uppercase; }
    </style>
</head>
<body>
    <h1>Your orders</h1>
    <table>
        <thead>
            <tr>
                <th>Service</th>
                <th>Country</th>
                <th>Status</th>
                <th>Phone number</th>
            </tr>
        </thead>
        <tbody>
            @forelse ($orders as $order)
                <tr>
                    <td>{{ $order['service'] }}</td>
                    <td>{{ $order['country'] }}</td>
                    <td>{{ $order['status'] }}</td>
                    <td>{{ $order['phone_number'] }}</td>
                </tr>
            @empty
                <tr><td colspan="4">No orders yet.</td></tr>
            @endforelse
        </tbody>
    </table>

    <p><a href="{{ route('nexisim.index') }}" style="color:#22c55e;">← Back to services</a></p>
</body>
</html>

Tester view

A self-contained test page for your own team — no API key typed into the browser, since it calls your Laravel routes, which hold the key server-side.

resources/views/nexisim/tester.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>NexiSim — Internal tester</title>
    <style>
        body { font-family: sans-serif; background:#0b0e14; color:#e4e8f1; padding:40px; max-width:640px; margin:0 auto; }
        .card { background:#141a24; border:1px solid #232b3a; border-radius:12px; padding:20px; margin-bottom:16px; }
        .card h3 { margin-bottom:10px; }
        input { width:100%; padding:9px 12px; border-radius:6px; border:1px solid #232b3a; background:#0b0e14; color:#fff; margin-bottom:10px; font-family:monospace; }
        button { padding:9px 18px; border:none; border-radius:6px; background:#22c55e; color:#031a0b; font-weight:700; cursor:pointer; }
        pre { background:#050810; padding:12px; border-radius:8px; overflow-x:auto; margin-top:12px; font-size:12.5px; white-space:pre-wrap; word-break:break-word; }
    </style>
</head>
<body>
    <h1>NexiSim internal tester</h1>
    <p style="color:#8494b4;margin-bottom:24px;">Calls your own Laravel routes, which hold the API key. Safe to share with teammates.</p>

    <div class="card">
        <h3>Services</h3>
        <button onclick="run('GET', '/nexisim', null, 'out-services')">Fetch services</button>
        <pre id="out-services">—</pre>
    </div>

    <div class="card">
        <h3>Countries</h3>
        <input id="in-service" placeholder="Service ID">
        <button onclick="run('GET', '/nexisim/countries/' + val('in-service'), null, 'out-countries')">Fetch countries</button>
        <pre id="out-countries">—</pre>
    </div>

    <div class="card">
        <h3>Buy number</h3>
        <input id="in-buy-service" placeholder="Service ID">
        <input id="in-buy-country" placeholder="Country ID">
        <button onclick="run('POST', '/nexisim/buy', { service_id: val('in-buy-service'), country_id: val('in-buy-country') }, 'out-buy')">Buy number</button>
        <pre id="out-buy">—</pre>
    </div>

    <div class="card">
        <h3>Check SMS</h3>
        <input id="in-activation" placeholder="Activation ID">
        <button onclick="run('GET', '/nexisim/check-sms/' + val('in-activation'), null, 'out-sms')">Check SMS</button>
        <pre id="out-sms">—</pre>
    </div>

    <div class="card">
        <h3>Orders</h3>
        <button onclick="run('GET', '/nexisim/orders', null, 'out-orders')">Fetch orders</button>
        <pre id="out-orders">—</pre>
    </div>

    <script>
        function val(id) { return document.getElementById(id).value.trim(); }

        async function run(method, url, body, outId) {
            const out = document.getElementById(outId);
            out.textContent = 'Running…';

            const opts = {
                method,
                headers: {
                    'Accept': 'application/json',
                    'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
                }
            };

            if (body) {
                opts.headers['Content-Type'] = 'application/json';
                opts.body = JSON.stringify(body);
            }

            try {
                const res = await fetch(url, opts);
                const data = await res.json();
                out.textContent = JSON.stringify(data, null, 2);
            } catch (err) {
                out.textContent = 'Error: ' + err.message;
            }
        }
    </script>
</body>
</html>
Visit /nexisim/tester once the routes are in place — that's this page, live in your app.

Where to buy / top up

Numbers are purchased against your NexiSim account balance — top up and grab your API key from the same place.

NexiSim Dashboard

Sign in, top up your balance, and copy your live API key — this is also where the numbers your app buys are actually paid for.

Go to nexisim.co.ke

Install checklist

Do these in order and you're live.

  • 1 Add the two lines to .env
  • 2 Create config/nexisim.php
  • 3 Create app/Http/Controllers/NexisimController.php
  • 4 Create the four files in resources/views/nexisim/
  • 5 Paste the route group into routes/web.php
  • 6 Run php artisan config:clear so the new env vars load
  • 7 Visit /nexisim/tester and try a request
The "Buy number" test spends real balance from your NexiSim account — same as the live API tester page.