DEV Community

Cover image for Nigerian Government Fees API: AI-Powered Public Service Data
AbdulmalikAlayande
AbdulmalikAlayande

Posted on

Nigerian Government Fees API: AI-Powered Public Service Data

Xano AI-Powered Backend Challenge: Public API Submission

This is a submission for the Xano AI-Powered Backend Challenge: Production-Ready Public API

What I Built

I built the Nigerian Government and Public Utility Fees API, a production-ready REST API that provides verified, structured, and traceable information about Nigerian government fees and public utility costs and centralizes the information from multiple Nigerian agencies into one accessible endpoint.

In Nigeria, citizens often struggle to find accurate information about the official cost of services such as:

  • National Identification Number (NIN) services
  • WAEC / NECO / JAMB examination fees
  • Passport and identity-related processes
  • Electricity tariffs across different DisCos
  • Other government-regulated public fees

The problem

For most Nigerians, finding accurate government fee information is frustrating. Information is usually scattered across PDFs, outdated websites, or unofficial sources. This API centralizes that data into a clean, developer-friendly backend that can be reliably consumed by applications, chatbots, dashboards, and AI agents.

The Solution

A single API that provides:

  • 90+ verified government fees across 6 categories (Identity, Immigration, Education, Electricity, Transport, Business)
  • 6 official agencies: NIMC, NIS, NECO, JAMB, NERC, UNILAG
  • Complete documentation with code examples in JavaScript, Python, and cURL
  • Rich relationship data: Every fee includes category, agency, and official source information
  • Search & filtering: Find exactly what you need instantly

The API is built with a strong focus on:

  • official data sources
  • traceability
  • maintainability
  • real-world production usage

Live API URL: https://xmlb-8xh6-ww1h.n7e.xano.io/api:public

API Documentation

The API follows a simple and predictable REST structure.

Core Resources

  • Categories (e.g. Identity & Management, Education, Electricity)
  • Subcategories (e.g. NIN, NECO, JAMB, Passport, DisCos)
  • Fees (individual payable items)
  • Agencies (government bodies)
  • Sources (official documents and websites)

Base URL

https://xmlb-8xh6-ww1h.n7e.xano.io/api:public
Enter fullscreen mode Exit fullscreen mode

Authentication

API key via query parameter: ?api_key=YOUR_KEY
Test API Key: nga_b6cf98a60bda43ba8cf54af9dbd87260

Available Endpoints

1. POST /api_key/generate

Generates the API key to be used for accessing the secured endpoints.

Example

curl -X 'POST''https://xmlb-8xh6-ww1h.n7e.xano.io/api:public/api_key/generate' -H 'Content-Type: application/json' --data '{"user_email":"your-email@example.com"}'
Enter fullscreen mode Exit fullscreen mode

2. GET /fees

Retrieve paginated list of fees with optional filtering.
Parameters:

category (optional) - Filter by category name/slug
state (optional) - Filter by state
search (optional) - Search in name and description
page (optional, default: 1) - Page number
per_page (optional, default: 20, max: 100) - Results per page

Example:

curl 'https://xmlb-8xh6-ww1h.n7e.xano.io/api:public/fees?category=identity&per_page=5'
Enter fullscreen mode Exit fullscreen mode

3. GET /fees/{id}

Get single fee by ID with all relationships.

Example:

curl 'https://xmlb-8xh6-ww1h.n7e.xano.io/api:public/fees/1?api_key=nga_b6cf98a60bda43ba8cf54af9dbd87260'
Enter fullscreen mode Exit fullscreen mode

4. GET /fees/search

Search fees by name and description (min 2 characters).

Example:

curl 'https://xmlb-8xh6-ww1h.n7e.xano.io/api:public/fees/search?q=passport&api_key=nga_b6cf98a60bda43ba8cf54af9dbd87260'
Enter fullscreen mode Exit fullscreen mode

5. GET /categories

Get all categories with fee counts.

Example:

curl 'https://xmlb-8xh6-ww1h.n7e.xano.io/api:public/categories?api_key=nga_b6cf98a60bda43ba8cf54af9dbd87260'
Enter fullscreen mode Exit fullscreen mode

6. GET /metadata

Get API statistics and version information.

Example:

curl 'https://xmlb-8xh6-ww1h.n7e.xano.io/api:public/metadata?api_key=nga_b6cf98a60bda43ba8cf54af9dbd87260'
Enter fullscreen mode Exit fullscreen mode

Rate Limits

Currently no rate limits enforced. Request tracking is implemented for future rate limiting.

Response Format

All responses are JSON. Errors follow standardized format:

{
  "code": "ERROR_CODE_TYPE",
  "message": "Human-readable error message"
}
Enter fullscreen mode Exit fullscreen mode

Full documentation: Available in my GitHub repository with complete request/response examples, error handling, and integration guides.


Demo

Example 1: Generate API Key

Request:

curl -X 'POST''https://xmlb-8xh6-ww1h.n7e.xano.io/api:public/api_key/generate' -H 'Content-Type: application/json' --data '{"user_email":"your-email@example.com"}'
Enter fullscreen mode Exit fullscreen mode

Response:

{
    "success":true,
    "api_key":"nga_********************************",
    "message":"API key generated successfully. Please save this key as it provides access to the API."
}
Enter fullscreen mode Exit fullscreen mode

Example 2: Get All Categories

Request:

curl -X 'GET' \
  'https://xmlb-8xh6-ww1h.n7e.xano.io/api:public/categories?api_key=nga_b6cf98a60bda43ba8cf54af9dbd87260' \
  -H 'accept: application/json'
Enter fullscreen mode Exit fullscreen mode

Response:

[
  {
    "id": 1,
    "display_name": "Identity & Management",
    "description": "Fees related to national identity systems such as NIN",
    "fee_count": 19
  },
  {
    "id": 2,
    "display_name": "Immigration",
    "description": "Passport and visa related fees",
    "fee_count": 10
  },
  {
    "id": 3,
    "display_name": "Education",
    "description": "NECO, JAMB and related examination fees",
    "fee_count": 45
  }
]
Enter fullscreen mode Exit fullscreen mode

Example 3: Search for NIN Fees

Request:

curl -X 'GET' \
  'https://xmlb-8xh6-ww1h.n7e.xano.io/api:public/fees/search?q=NIN&api_key=nga_b6cf98a60bda43ba8cf54af9dbd87260' \
  -H 'accept: application/json'
Enter fullscreen mode Exit fullscreen mode

Response:

[
  {
    "id": 1,
    "name": "NIN Enrolment (First Time)",
    "amount": 0,
    "currency": "NGN",
    "service_type": "Standard",
    "description": "Initial NIN enrollment is free",
    "category_name": "Identity & Management",
    "agency_name": "National Identity Management Commission",
    "subcategory_name": "NIN",
    "source_name": "NIMC"
  }
]
Enter fullscreen mode Exit fullscreen mode

Example 4: Get Fee Details

Request:

curl -X 'GET' \
  'https://xmlb-8xh6-ww1h.n7e.xano.io/api:public/fees/20?api_key=nga_b6cf98a60bda43ba8cf54af9dbd87260' \
  -H 'accept: application/json'
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "id": 20,
  "name": "Standard Passport 32 Pages (5-Year Validity)",
  "amount": 100000,
  "currency": "NGN",
  "service_type": "Standard",
  "description": "New or Renewal",
  "subcategory": {
    "id": 3,
    "name": "Passport",
    "category": {
      "id": 2,
      "name": "Immigration",
      "description": "Passport and visa related fees"
    }
  },
  "source": {
    "id": 2,
    "name": "Nigerian Immigration Service (NIS)",
    "agency": {
      "id": 2,
      "name": "Nigeria Immigration Service",
      "website": "https://immigration.gov.ng"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Code Integration Examples

JavaScript:

const BASE_URL = 'https://xmlb-8xh6-ww1h.n7e.xano.io/api:public';
const API_KEY = 'nga_b6cf98a60bda43ba8cf54af9dbd87260';

async function searchFees(query) {
  const response = await fetch(
    `${BASE_URL}/fees/search?q=${query}&api_key=${API_KEY}`
  );
  const data = await response.json();
  console.log(`Found ${data.length} fees matching "${query}"`);
  return data;
}

searchFees('passport');
Enter fullscreen mode Exit fullscreen mode

Python:

import requests

BASE_URL = 'https://xmlb-8xh6-ww1h.n7e.xano.io/api:public'
API_KEY = 'nga_b6cf98a60bda43ba8cf54af9dbd87260'

def search_fees(query):
    response = requests.get(
        f'{BASE_URL}/fees/search',
        params={'q': query, 'api_key': API_KEY}
    )
    data = response.json()
    print(f'Found {len(data)} fees matching "{query}"')
    return data

search_fees('passport')
Enter fullscreen mode Exit fullscreen mode

Try it yourself with your API key, see the example provided above!


The AI Prompt I Used

I used Cursor with XanoScript to generate all 5 core endpoints. Here's my main prompt:

Generate 5 Xano API endpoints for a Nigerian government fees API:

1. GET /fees
   - Query fees table with optional filters: category, state, search
   - Include relationships: subcategory, category, agency, source
   - Add pagination: limit (default 50), offset (default 0)
   - Return JSON with data array and metadata

2. GET /fees/{id}
   - Get single fee by ID with all relationships
   - Return 404 if not found
   - Include subcategory, category, source, and agency data

3. GET /categories
   - List all categories
   - Include count of fees in each category
   - Return with display_name and description

4. GET /fees/search
   - Accept 'q' query parameter (required, minimum 2 characters)
   - Search in fee name and description using case-insensitive match
   - Limit to 20 results
   - Include all relationships

5. GET /metadata
   - Return API statistics (total fees, categories, agencies)
   - Include last database update timestamp
   - Return API version number
Enter fullscreen mode Exit fullscreen mode

AI delivered in ~30 minutes:

  • Complete XanoScript code for all 5 endpoints
  • Database query structures with joins
  • Basic input validation
  • Pagination logic
  • Error handling with preconditions

Time saved: ~6 hours of boilerplate coding


How I Refined the AI-Generated Code

AI gave me a great starting point, but I had to fix critical bugs and add production features. Here are the key refinements:

Issue #1: Invalid eval Syntax - CRITICAL BUG


AI Generated (GET /fees/{id}):

eval = {
  category   : $db.{}
  subcategory: $db.{}
  agency     : $db.{}
  source     : $db.{}
}
Enter fullscreen mode Exit fullscreen mode

The Problem: $db.{} is invalid XanoScript syntax. This caused relationships to fail silently—no error in development, but incomplete data in production.

My Fix:

// Removed invalid eval block
// Used db.get to fetch relationships separately
var $subcategory { 
  value = db.get('subcategories', $fee.subcategory_id) 
}
var $category { 
  value = db.get('categories', $subcategory.category_id) 
}
var $source { 
  value = db.get('sources', $fee.source_id) 
}
var $agency { 
  value = db.get('agencies', $source.agency_id) 
}

// Then nested them properly using var.update
$fee|var.update: { 
  subcategory: {
    ...$subcategory,
    category: $category
  },
  source: { 
    ...$source, 
    agency: $agency 
  }
}
Enter fullscreen mode Exit fullscreen mode

Impact: Relationships now properly included in every response. API went from returning incomplete data to being production-ready.


Issue #2: N+1 Query Performance Problem

AI Generated (GET /categories):

foreach ($categories as $category) {
  // Query fees for EACH category individually - terrible for performance!
  var $fee_count { 
    value = db.query('fees', { 
      where: { subcategory.category_id: $category.id }
    }).count
  }
}
Enter fullscreen mode Exit fullscreen mode

The Problem: If you have 6 categories, this makes 6 separate database queries. Would fail under load.

My Optimization:

// Single query with proper aggregation
var $fee_counts {
  value = db.query('fees', {
    join: { 
      subcategories: { category_id: 'categories.id' } 
    },
    group_by: 'categories.id',
    select: {
      category_id: 'categories.id',
      fee_count: 'COUNT(fees.id)'
    }
  })
}
Enter fullscreen mode Exit fullscreen mode

Impact: Reduced database queries from 6 to 1. Response time improved by ~60%.


Issue #3: Empty Results Bug

The Problem: When calling /fees with no filters, API returned {"items":[],"itemsTotal":0} despite having 87 fees in the database.

Root Cause: The where clause was too restrictive. When optional filters were null, the logical AND conditions were filtering out all records.

My Fix:

// Added explicit null/empty checks for each optional filter
where = (
  ($search_filter == null || $search_filter == '' || 
   $db.fees.name includes? $search_filter || 
   $db.fees.description includes? $search_filter) && 
  ($category_filter == null || $category_filter == '' || 
   $db.category.slug ==? $category_filter || 
   $db.category.name ==? $category_filter) && 
  ($state_filter == null || $state_filter == '' || 
   $db.fees.meta.state ==? $state_filter)
)
Enter fullscreen mode Exit fullscreen mode

Impact: API now correctly returns all fees when no filters are applied, and properly filters when they are.


Issue #4: Incorrect Sort Syntax

AI Generated:

sort = { "$db.fees.updated_at": "desc" }
Enter fullscreen mode Exit fullscreen mode

The Problem: XanoScript doesn't use $db. prefix in sort blocks. Caused silent sorting failures.

My Fix:

sort = { fees.updated_at: "desc" }
Enter fullscreen mode Exit fullscreen mode

Impact: Last updated fees now correctly sorted by timestamp.


What I Added Beyond AI

1. API Key Authentication System

function auth_api_key() {
  // Extract and validate API key
  var $api_key { value = get($http_query, "api_key") }

  if (!$api_key) {
    throw {
      code: "ERROR_CODE_ACCESS_DENIED",
      message: "Missing API Key"
    }
  }

  // Query and validate
  var $key_record { 
    value = db.get('api_keys', { 
      key: $api_key, 
      is_active: true 
    }) 
  }

  if (!$key_record) {
    throw {
      code: "ERROR_CODE_ACCESS_DENIED",
      message: "Invalid or inactive API Key"
    }
  }

  // Update usage stats
  db.update('api_keys', $key_record.id, {
    request_count: $key_record.request_count + 1,
    last_request_at: now()
  })

  return $key_record
}
Enter fullscreen mode Exit fullscreen mode

2. Standardized Error Responses

{
  "code": "ERROR_CODE_ACCESS_DENIED",
  "message": "Invalid or inactive API Key.",
  "status": 401
}
Enter fullscreen mode Exit fullscreen mode

3. Comprehensive Documentation

  • Full API reference with request/response examples
  • Quick Start guide (5 minutes to first API call)
  • Code examples in JavaScript, Python, and cURL
  • Data sources reference with official URLs

The Numbers: AI vs Human Contribution

Metric AI Human
Time 30 minutes 4-5 hours
Code Generated ~500 lines ~300 lines
Critical Bugs Found 0 5
Production Features Basic CRUD Auth, optimization, error handling
Documentation None Complete

Total Development Time: ~5 hours (would have been 10-12 hours without AI)


My Experience with Xano

What I Found Most Helpful

1. Visual Database Designer
Building the relational database schema with proper foreign keys was intuitive. The visual interface made it easy to see relationships between tables (fees → subcategories → categories → agencies).

2. Built-in API Testing
The "Run & Debug" feature in the API builder was invaluable. I could test endpoints immediately without deploying or using external tools. This caught bugs early.

3. XanoScript Flexibility
Having both visual blocks and code gave me the best of both worlds. For simple queries, I used the visual builder. For complex logic (like the authentication function), I wrote XanoScript directly.

4. One-Click Deployment
No server setup, no deployment configuration, no DevOps complexity. Just build and it's live. This saved hours of infrastructure work.

5. PostgreSQL Under the Hood
Knowing I had a real relational database with proper ACID guarantees gave me confidence. This isn't a toy database—it's production-grade.


Challenges I Faced

1. XanoScript Learning Curve
Coming from Java/JavaScript/Python, XanoScript syntax had quirks:

  • $variable vs variable naming conventions
  • When to use eval blocks (spoiler: less than I thought)
  • Sort/filter syntax differences from standard SQL

Solution: Trial and error, plus reading Xano docs. The AI-generated code gave me examples to learn from, even when it had bugs.

2. Debugging AI-Generated Code
AI confidently generated invalid syntax ($db.{}) that looked plausible. Without testing, I would have shipped broken code.

Lesson Learned: Always test AI-generated code immediately. AI is great at structure but can miss framework-specific nuances.

3. Documentation Gaps
Some XanoScript features weren't well-documented online. I had to experiment to figure out the correct syntax for complex joins and aggregations.

Solution: Used the Xano community forums and tested different approaches until I found what worked.

4. N+1 Query Gotcha
It was easy to accidentally create performance problems with nested loops. The visual builder doesn't warn you about N+1 queries.

Solution: Always think about database query patterns. Single complex query > multiple simple queries.


What Made This Project Successful

The Winning Combination:

  • Xano handled infrastructure and deployment
  • AI generated boilerplate and structure quickly
  • Human expertise fixed bugs, optimized performance, added production features
  • Real data from official government sources made it valuable

None of these alone would have worked. But together? I built a production-ready API in ~5 hours that would have taken 2-3 days manually.


Would I Use Xano Again?

Absolutely. For rapid API development, especially for:

  • Hackathons - Speed to deployment is unbeatable
  • MVPs - Get to market fast, validate ideas
  • Internal tools - Build CRUD APIs in hours, not days
  • Prototypes - Prove concepts before investing in custom infrastructure

When I might not use it:

  • Extremely high-scale applications requiring custom infrastructure
  • Projects needing specific database features outside PostgreSQL
  • Complex real-time features requiring WebSockets at scale

But for 90% of backend API projects? Xano + AI is a game-changer.


Impact & Future Plans

Who Can Use This API?

🏦 Fintech Companies:

  • Budget calculators including government fees
  • Payment platforms for government services
  • Financial planning apps

📱 Mobile Apps:

  • Citizen services super-apps
  • Student planning tools
  • Government service aggregators

🤖 Chatbots:

  • WhatsApp bots for fee inquiries
  • USSD services for feature phones
  • AI government assistants

What's Next

Immediate (v1.1):

  • Implement rate limiting (100 req/hour)
  • Add webhook support for fee updates
  • Create admin dashboard for data management

Future (v2.0):

  • Expand to all 36 Nigerian states
  • Historical fee tracking (price changes)
  • Multi-language support
  • Mobile SDK

Long-term Vision:
Transform into a comprehensive government services platform with payment processing, application tracking, and document verification.


Data Sources

All fees sourced from official government documents:

  • NIMC: SERVICE_LEVEL_AGREEMENT_2025_.pdf
  • NIS: Official Immigration Service SLA
  • NECO: Official exam fees from neco.gov.ng
  • JAMB: Payment services from jamb.gov.ng
  • NERC/EKEDC: Official tariff plans from ekedp.com

Every fee includes verification_url linking to the official source for transparency.


Try It Now!

Test API Key: nga_b6cf98a60bda43ba8cf54af9dbd87260

Quick Test:

curl 'https://xmlb-8xh6-ww1h.n7e.xano.io/api:public/fees/search?q=passport&api_key=nga_b6cf98a60bda43ba8cf54af9dbd87260'
Enter fullscreen mode Exit fullscreen mode

Full Documentation: GitHub Repository (includes Quick Start guide, complete API reference, and code examples)


Conclusion

This project proves that AI-assisted development works best as a partnership: AI provides speed and structure, humans provide critical thinking and domain expertise.

The Nigerian Government Fees API solves a real problem affecting millions. By centralizing scattered information into one well-documented API, we're removing barriers to accessing essential government services.

More importantly, this is just the beginning. With a solid foundation, this can grow into a platform that truly improves how Nigerian citizens interact with public services.


Questions? Want to contribute? Drop a comment below! 🚀

Top comments (0)