Complete Tender Integration Platform

Access live tenders from across Africa. Display, filter, and integrate tender data seamlessly into any application.

Security Notice: Domain locking restricts API usage to registered websites only. Testing from this documentation page is automatically allowed.

Real-time Data

Access live tender data updated continuously from multiple African countries

Advanced Filtering

Filter tenders by country, category, sector, county, and closing date

Secure Document Access

Download tender documents securely through our API gateway

Secure & Reliable

Enterprise-grade API with rate limiting and 99.9% uptime guarantee

Get Started in 5 Minutes

Important: Your API key is domain-locked. It works automatically from:
  • Your registered website(s)
  • api.tendersoko.africa (for testing)
  • localhost (for development)
1

Get Your API Key

Register for a free account and get your API key from the dashboard.

Get API Key
2

Learn Secure Authentication

Use HTTP headers for API authentication instead of query parameters.

// SECURE: Use headers
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
3

Choose Integration Method

Select from our pre-built solutions or build your own integration.

4

Go Live

Deploy your integration and start displaying live tenders to your users.

Your integration is ready for production use

Quick Authentication Test

Enter your API key to test connectivity and explore the API:

Enter your API key to test the connection

API Testing Playground

API Authentication

Enter your API key to start testing

Professional Test Endpoints

Document Download Tests

These endpoints trigger file downloads directly. Use the iframe method for reliable downloads.

API Response
// Response will appear here...

Complete API Endpoints Reference

Security Notice: All API requests must include authentication via HTTP headers. Query parameters are deprecated but supported for backward compatibility.

Tender Management Endpoints

GET
/api/v1/countries

Get all available countries with their codes

GET
/api/v1/tenders

Get all active tenders with optional filtering

?country=KE&category=5&sector=12
GET
/api/v1/tenders/{id}

Get detailed information about a specific tender

/api/v1/tenders/227817

Tender descriptions are automatically formatted and sanitized

GET
/api/v1/today

Get tenders published today

GET
/api/v1/closing

Get tenders closing soon (default: 7 days)

?days=3&country=KE

Document Management Endpoints

GET
/api/v1/tenders/{id}/documents

Get all documents for a specific tender

/api/v1/tenders/227817/documents

Returns: { "status": true, "documents": [...] }

GET
/api/v1/advert_doc/{id}

Download tender advertisement as PDF (Direct Download)

/api/v1/advert_doc/227817

Auto-downloads: tender-title-advert.pdf

Note: This is a binary download, not a JSON response

GET
/api/v1/documents/{id}/download

Download specific document (Direct Download)

/api/v1/documents/1/download

Auto-downloads: document-title.extension

Note: This is a binary download, not a JSON response

Reference Data Endpoints

GET
/api/v1/categories

Get all tender categories

GET
/api/v1/sectors

Get all tender sectors

GET
/api/v1/statuses

Get all tender statuses

GET
/api/v1/counties

Get all counties (optionally filter by country_id)

?country_id=1

Company Logo Endpoint

GET
/api/v1/company_logo/{company_id}

Get company logo by company ID

/api/v1/company_logo/6

Response: Binary image file (PNG/JPG/JPEG)

Flow:

  1. Client requests logo from gateway
  2. Gateway fetches logo URL from main API
  3. Gateway streams image directly to client
// Use as image source in HTML:
<img src="https://api.tendersoko.africa/api/v1/company_logo/6" alt="Company Logo" />

// Example with fallback:
<img src="https://api.tendersoko.africa/api/v1/company_logo/6"
onerror="this.src='https://api.tendersoko.africa/images/companies/briefcase.png'"
alt="Company Logo" />

Note: This endpoint requires API authentication via headers

Clean Tender Descriptions

GET
/api/v1/tenders/{id}

Get tender with sanitized HTML descriptions

All tender descriptions are automatically cleaned and formatted for safe display

PHP Helper Function for Clean HTML

Use this PHP function to clean and safely display tender descriptions:

HTML Cleaning Function
1/**
2 * Clean and format HTML content safely
3 * Removes dangerous tags while preserving formatting
4 */
5function clean_html_content($html) {
6    if (empty($html)) {
7        return 'No description available.';
8    }
9    
10    // Decode HTML entities
11    $html = html_entity_decode($html, ENT_QUOTES | ENT_HTML5, 'UTF-8');
12    
13    // Allow safe tags but remove dangerous ones
14    $allowed_tags = '<p><br><strong><b><em><i><u><ul><ol><li><table><tr><td><th><tbody><thead><tfoot><div><span><h1><h2><h3><h4><h5><h6><a><img><sup><sub>';
15    
16    // Clean HTML while preserving basic formatting
17    $html = strip_tags($html, $allowed_tags);
18    
19    // Fix common MS Word HTML issues
20    $html = preg_replace('/class="[^"]*"/', '', $html); // Remove class attributes
21    $html = preg_replace('/style="[^"]*"/', '', $html); // Remove style attributes
22    $html = preg_replace('/\s+/', ' ', $html); // Normalize whitespace
23    
24    // Ensure links have proper attributes
25    $html = preg_replace_callback('/<a[^>]*>/', function($matches) {
26        $tag = $matches[0];
27        // Add target="_blank" and rel="noopener noreferrer"
28        if (strpos($tag, 'target=') === false) {
29            $tag = str_replace('<a ', '<a target="_blank" rel="noopener noreferrer" ', $tag);
30        }
31        if (strpos($tag, 'href=') === false) {
32            $tag = str_replace('<a ', '<a href="#" ', $tag);
33        }
34        return $tag;
35    }, $html);
36    
37    return trim($html);
38}
39
40// Usage example:
41$tender_description = $tender['description'] ?? '';
42$clean_description = clean_html_content($tender_description);
43echo "<div class='tender-description'>" . $clean_description . "</div>";
Security Note: This function protects against XSS attacks while preserving readable formatting. It automatically:
  • Decodes HTML entities properly
  • Removes dangerous tags (script, iframe, style, etc.)
  • Keeps safe formatting tags (p, br, strong, tables, lists)
  • Secures links with target="_blank" and rel="noopener noreferrer"
  • Removes inline styles and classes for cleaner output

CSS Styling for Clean Descriptions

Add these CSS styles to make tender descriptions look professional:

CSS for Tender Descriptions
1.tender-description {
2    font-size: 1rem;
3    line-height: 1.8;
4    color: #334155;
5}
6
7.tender-description p {
8    margin-bottom: 1rem;
9}
10
11.tender-description table {
12    width: 100%;
13    border-collapse: collapse;
14    margin: 1rem 0;
15}
16
17.tender-description td, .tender-description th {
18    border: 1px solid #ddd;
19    padding: 8px;
20}
21
22.tender-description tr:nth-child(even) {
23    background-color: #f9f9f9;
24}
25
26.tender-description ul, .tender-description ol {
27    margin: 1rem 0;
28    padding-left: 2rem;
29}
30
31.tender-description li {
32    margin-bottom: 0.5rem;
33}
34
35.tender-description a {
36    color: #1a5f7a;
37    text-decoration: underline;
38}
39
40.tender-description a:hover {
41    color: #e74c3c;
42}

Key Features of Clean Descriptions

XSS Protection

Removes all dangerous HTML tags and attributes to prevent cross-site scripting attacks

Format Preservation

Keeps tables, lists, paragraphs, headings, and basic formatting intact

Secure Links

Automatically adds target="_blank" and rel="noopener" to all links for security

Clean Output

Removes MS Word classes, inline styles, and unnecessary whitespace

Complete Implementation Example

Here's a complete example of displaying a tender with clean description:

Displaying Clean Tender Details
1<div class="tender-detail-section">
2  <h3>Tender Description</h3>
3  <div class="tender-description">
4    <?php
5      // $tender is from API response
6      $description = $tender['description'] ?? '';
7      echo clean_html_content($description);
8    ?>
9  </div>
10</div>
11
12<?php if (!empty($tender['eligibility_requirements'])): ?>
13<div class="tender-detail-section">
14  <h3>Eligibility Requirements</h3>
15  <div class="tender-description">
16    <?php echo clean_html_content($tender['eligibility_requirements']); ?>
17  </div>
18</div>
19<?php endif; ?>

Document Download Implementation

Document Download Implementation
1// Tendersoko API - Document Download Solution
2const API_KEY = 'YOUR_API_KEY_HERE';
3const API_BASE = 'https://api.tendersoko.africa/api/v1';
4
5/**
6 * Download tender advert - uses hidden iframe method
7 * This method avoids console errors and works reliably
8 */
9function downloadAdvert(tenderId) {
10    const iframe = document.createElement('iframe');
11    iframe.style.display = 'none';
12    iframe.src = `${API_BASE}/advert_doc/${tenderId}`;
13    document.body.appendChild(iframe);
14    
15    setTimeout(() => {
16        if (iframe.parentNode) {
17            document.body.removeChild(iframe);
18        }
19    }, 5000);
20    
21    return true;
22}
23
24/**
25 * Download document - works for all document types (PDF, DOC, etc.)
26 */
27function downloadDocument(docId) {
28    const iframe = document.createElement('iframe');
29    iframe.style.display = 'none';
30    iframe.src = `${API_BASE}/documents/${docId}/download`;
31    document.body.appendChild(iframe);
32    
33    setTimeout(() => {
34        if (iframe.parentNode) {
35            document.body.removeChild(iframe);
36        }
37    }, 5000);
38    
39    return true;
40}
41
42/**
43 * Alternative method using fetch (may show console errors but works)
44 */
45async function downloadDocumentWithFetch(docId, docTitle, docExtension) {
46    try {
47        const response = await fetch(${API_BASE}/documents/${docId}/download, {
48            headers: {
49                'Authorization': 'Bearer ' + API_KEY
50            }
51        });
52        
53        if (!response.ok) {
54            throw new Error(HTTP ${response.status});
55        }
56        
57        const blob = await response.blob();
58        const url = window.URL.createObjectURL(blob);
59        const a = document.createElement('a');
60        a.href = url;
61        
62        // Create safe filename
63        const safeTitle = docTitle
64            .replace(/[^a-zA-Z0-9\s-]/g, '')
65            .replace(/\s+/g, '-')
66            .toLowerCase()
67            .substring(0, 100);
68        
69        a.download = ${safeTitle}.${docExtension.toLowerCase()};
70        document.body.appendChild(a);
71        a.click();
72        window.URL.revokeObjectURL(url);
73        document.body.removeChild(a);
74        
75        return { success: true };
76    } catch (error) {
77        console.warn('Fetch method failed, falling back to iframe');
78        downloadDocument(docId);
79        return { success: true, method: 'fallback' };
80    }
81}
82
83// Example usage:
84// downloadAdvert(227817); // Downloads PDF advert for tender 227817
85// downloadDocument(1); // Downloads specific document

Quick Integration Examples

Example
Download Tender Advert
downloadAdvert(227817)

Downloads: tender-advert.pdf

Example
Get Company Logo
<img src="/api/v1/company_logo/6" />

Displays: USAID logo (6.jpg)

Complete Integration Guide

Security Best Practices: Always use HTTP headers for API authentication. Never expose API keys in client-side code or URLs.

Quick Start - Basic Tender Display

Copy and paste this code to display tenders on your website:

Basic HTML Integration (Secure)
1<!-- Complete HTML example with styling and JavaScript -->
2<!DOCTYPE html>
3<html lang="en">
4<head>
5    <meta charset="UTF-8">
6    <meta name="viewport" content="width=device-width, initial-scale=1.0">
7    <title>Tenders Display</title>
8    <style>
9        .tenders-container { 
10            max-width: 1200px; 
11            margin: 0 auto; 
12            padding: 20px; 
13        }
14        .tender-card {
15            background: white; 
16            border-radius: 12px; 
17            padding: 20px; 
18            margin: 15px 0;
19            border: 1px solid #e2e8f0; 
20            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
21            transition: all 0.3s;
22        }
23        .tender-card:hover {
24            transform: translateY(-2px); 
25            box-shadow: 0 8px 25px rgba(0,0,0,0.15);
26        }
27        .tender-title {
28            font-size: 1.1rem; 
29            font-weight: 600; 
30            margin-bottom: 12px; 
31            color: #334155; 
32            cursor: pointer;
33        }
34    </style>
35</head>
36<body>
37    <div class="tenders-container" id="tenders-container">
38        <div style="text-align: center; padding: 40px;">Loading tenders...</div>
39    </div>
40
41    <script>
42        const API_KEY = 'YOUR_API_KEY_HERE';
43        const API_BASE = 'https://api.tendersoko.africa/api/v1';
44
45        // Load tenders on page load
46        document.addEventListener('DOMContentLoaded', function() {
47            loadTenders();
48        });
49
50        async function loadTenders() {
51            try {
52                const response = await fetch(${API_BASE}/tenders, {
53                    headers: {
54                        'Authorization': 'Bearer ' + API_KEY
55                    }
56                });
57                const data = await response.json();
58
59                if (data.status === 'OK') {
60                    displayTenders(data.tenders);
61                } else {
62                    showError('Failed to load tenders: ' + data.message);
63                }
64            } catch (error) {
65                showError('Network error: ' + error.message);
66            }
67        }
68
69        function displayTenders(tenders) {
70            const container = document.getElementById('tenders-container');
71            // ... rendering code ...
72        }
73    </script>
74</body>
75</html>

Advanced - Filtered Tenders with Country Selection

Enhanced integration with filtering capabilities:

Filtered Tenders Integration
1// Advanced integration with filtering
2const API_KEY = 'YOUR_API_KEY_HERE';
3const API_BASE = 'https://api.tendersoko.africa/api/v1';
4
5let countries = [];
6let categories = [];
7let sectors = [];
8
9// Initialize on page load
10document.addEventListener('DOMContentLoaded', async function() {
11    await loadReferenceData();
12    await loadTenders();
13});
14
15// Load reference data for filters
16async function loadReferenceData() {
17    try {
18        // Load countries with secure headers
19        const countriesResponse = await fetch(${API_BASE}/countries, {
20            headers: {
21                'Authorization': 'Bearer ' + API_KEY
22            }
23        });
24        const countriesData = await countriesResponse.json();
25        if (countriesData.status === true) {
26            countries = countriesData.countries;
27            populateCountryFilter();
28        }
29    } catch (error) {
30        console.error('Failed to load reference data:', error);
31    }
32}
33
34// Load tenders with filters using secure headers
35async function loadTenders(filters = {}) {
36    try {
37        const params = new URLSearchParams();
38        
39        // Add filters but NOT the API key
40        if (filters.country) params.append('country', filters.country);
41        if (filters.category) params.append('category', filters.category);
42        
43        const endpoint = filters.type === 'today' ? '/today' : '/tenders';
44        
45        const response = await fetch(${API_BASE}${endpoint}?${params.toString()}, {
46            headers: {
47                'Authorization': 'Bearer ' + API_KEY
48            }
49        });
50        const data = await response.json();
51
52        if (data.status === 'OK') {
53            displayTenders(data.tenders);
54        }
55    } catch (error) {
56        showError('Network error: ' + error.message);
57    }
58}

Professional Document Management

Complete solution for handling document downloads:

Document Manager Class
1// Professional Document Manager for Tendersoko API
2class TendersokoDocumentManager {
3    constructor(apiKey) {
4        this.apiKey = apiKey;
5        this.apiBase = 'https://api.tendersoko.africa/api/v1';
6    }
7    
8    /**
9     * Download tender advert - uses iframe method
10     */
11    downloadAdvert(tenderId) {
12        const iframe = document.createElement('iframe');
13        iframe.style.display = 'none';
14        iframe.src = `${this.apiBase}/advert_doc/${tenderId}`;
15        document.body.appendChild(iframe);
16        
17        setTimeout(() => {
18            if (iframe.parentNode) {
19                document.body.removeChild(iframe);
20            }
21        }, 5000);
22        
23        return { success: true };
24    }
25}

Security Best Practices Guide

Critical Security Notes:
  • Never expose API keys in client-side JavaScript for production applications
  • Domain locking restricts API usage to registered websites only
  • Use server-side proxies for production deployments
  • Regularly rotate API keys from your dashboard
Server-Side Proxy Pattern
1// Node.js/Express example for secure server-side proxy
2const express = require('express');
3const axios = require('axios');
4const app = express();
5
6// Your secure endpoint that hides the API key
7app.get('/api/tenders', async (req, res) => {
8    try {
9        const response = await axios.get('https://api.tendersoko.africa/api/v1/tenders', {
10            headers: {
11                'Authorization': 'Bearer YOUR_SERVER_API_KEY'
12            },
13            params: req.query
14        });
15        
16        res.json(response.data);
17    } catch (error) {
18        res.status(error.response?.status || 500).json({
19            error: 'Failed to fetch tenders'
20        });
21    }
22});