Skip to main content

REST API Reference

This document provides a complete reference for Visita’s REST API endpoints, including authentication, request/response formats, and example usage.
Base URL: https://visita-intelligence.vercel.app/api

Authentication

All protected API endpoints require authentication using Bearer tokens.

Headers

Authorization: Bearer <supabase_access_token>
Content-Type: application/json

Obtaining a Token

Tokens are obtained through Supabase Auth:
import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);

// Sign in
const { data, error } = await supabase.auth.signInWithPassword({
  email: '[email protected]',
  password: 'password'
});

// Get session
const { data: { session } } = await supabase.auth.getSession();
const token = session?.access_token;

Ward Intelligence API

Get Ward Overview

GET /ward/{ward_code}
Description: Retrieve comprehensive ward intelligence including safety, weather, and community data. Path Parameters:
  • ward_code (string, required): Ward identifier (e.g., “WARD001”)
Query Parameters:
  • include_weather (boolean, optional): Include weather data (default: true)
  • include_safety (boolean, optional): Include safety statistics (default: true)
  • include_community (boolean, optional): Include community projects (default: true)
Response:
{
  "ward_code": "WARD001",
  "name": "Ward 1 - Johannesburg Central",
  "boundaries": {
    "type": "Polygon",
    "coordinates": [[...]]
  },
  "statistics": {
    "business_count": 245,
    "project_count": 12,
    "member_count": 1847,
    "safety_score": 7.2
  },
  "weather": {
    "current": {
      "temperature": 22,
      "condition": "Partly Cloudy",
      "humidity": 65,
      "wind_speed": 15
    },
    "forecast": [...]
  },
  "safety": {
    "overall_score": 7.2,
    "crime_trend": "decreasing",
    "recent_incidents": [...]
  },
  "community": {
    "active_projects": [...],
    "recent_updates": [...]
  }
}
Example Request:
curl -X GET "https://visita-intelligence.vercel.app/api/ward/WARD001?include_weather=true&include_safety=true" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json"
Status Codes:
  • 200 - Success
  • 401 - Unauthorized
  • 404 - Ward not found
  • 500 - Internal server error

Get Ward Signals

GET /ward/{ward_code}/signals
Description: Retrieve recent signals (alerts, incidents, updates) for a ward. Path Parameters:
  • ward_code (string, required): Ward identifier
Query Parameters:
  • type (string, optional): Filter by signal type (safety, community, weather, infrastructure)
  • limit (number, optional): Number of results (default: 20, max: 100)
  • offset (number, optional): Pagination offset (default: 0)
  • time_range (string, optional): Time range (1h, 24h, 7d, 30d, default: 24h)
Response:
{
  "signals": [
    {
      "id": "signal-123",
      "type": "safety",
      "title": "Power Outage Reported",
      "description": "Power outage affecting 3 blocks...",
      "severity": "medium",
      "location": {
        "lat": -26.2041,
        "lng": 28.0473
      },
      "timestamp": "2025-12-30T15:30:00Z",
      "source": "user_report",
      "corroborations": 5,
      "status": "active"
    }
  ],
  "pagination": {
    "total": 42,
    "limit": 20,
    "offset": 0,
    "has_more": true
  }
}
Example Request:
curl -X GET "https://visita-intelligence.vercel.app/api/ward/WARD001/signals?type=safety&limit=10&time_range=24h" \
  -H "Authorization: Bearer $TOKEN"

Business Directory API

Search Businesses

GET /business/search
Description: Search businesses with filtering and geospatial queries. Query Parameters:
  • q (string, optional): Search query
  • ward_code (string, optional): Filter by ward
  • category (string, optional): Filter by category
  • lat (number, optional): Latitude for proximity search
  • lng (number, optional): Longitude for proximity search
  • radius (number, optional): Search radius in meters (default: 5000)
  • limit (number, optional): Number of results (default: 20)
  • offset (number, optional): Pagination offset
Response:
{
  "businesses": [
    {
      "id": "business-123",
      "name": "Maboneng Coffee",
      "description": "Local coffee shop and community hub",
      "category": "Food & Beverage",
      "ward_code": "WARD001",
      "location": {
        "lat": -26.2041,
        "lng": 28.0473,
        "address": "123 Main St, Johannesburg"
      },
      "contact": {
        "phone": "+27123456789",
        "email": "[email protected]",
        "website": "https://mabonengcoffee.co.za"
      },
      "rating": 4.5,
      "review_count": 127,
      "opening_hours": {...},
      "service_areas": ["WARD001", "WARD002"]
    }
  ],
  "pagination": {
    "total": 245,
    "limit": 20,
    "offset": 0,
    "has_more": true
  },
  "facets": {
    "categories": [
      {"name": "Food & Beverage", "count": 45},
      {"name": "Retail", "count": 38}
    ],
    "wards": [
      {"ward_code": "WARD001", "count": 67},
      {"ward_code": "WARD002", "count": 54}
    ]
  }
}
Example Request:
curl -X GET "https://visita-intelligence.vercel.app/api/business/search?q=coffee&ward_code=WARD001&lat=-26.2041&lng=28.0473&radius=2000" \
  -H "Authorization: Bearer $TOKEN"

Get Business Details

GET /business/{id}
Description: Retrieve detailed information about a specific business. Path Parameters:
  • id (string, required): Business ID or slug
Response:
{
  "id": "business-123",
  "name": "Maboneng Coffee",
  "slug": "maboneng-coffee",
  "description": "Local coffee shop and community hub...",
  "category": "Food & Beverage",
  "subcategory": "Coffee Shop",
  "ward_code": "WARD001",
  "location": {
    "lat": -26.2041,
    "lng": 28.0473,
    "address": "123 Main St, Johannesburg, 2001",
    "ward": "WARD001"
  },
  "contact": {
    "phone": "+27123456789",
    "email": "[email protected]",
    "website": "https://mabonengcoffee.co.za"
  },
  "social": {
    "facebook": "mabonengcoffee",
    "instagram": "mabonengcoffee"
  },
  "rating": 4.5,
  "review_count": 127,
  "opening_hours": {
    "monday": "08:00-18:00",
    "tuesday": "08:00-18:00",
    "sunday": "09:00-15:00"
  },
  "photos": [
    "https://cdn.visita.co.za/photos/business-123/1.jpg"
  ],
  "products": [
    {
      "id": "product-1",
      "name": "Cappuccino",
      "price": 35,
      "currency": "ZAR"
    }
  ],
  "service_areas": [
    {
      "ward_code": "WARD001",
      "service_type": "primary"
    },
    {
      "ward_code": "WARD002",
      "service_type": "delivery"
    }
  ],
  "verified": true,
  "created_at": "2025-01-15T10:00:00Z",
  "updated_at": "2025-12-20T15:30:00Z"
}

Community & Governance API

Get Community Projects

GET /ward/{ward_code}/projects
Description: Retrieve community action projects for a ward. Path Parameters:
  • ward_code (string, required): Ward identifier
Query Parameters:
  • status (string, optional): Filter by status (active, completed, planned)
  • category (string, optional): Filter by category
  • limit (number, optional): Number of results (default: 20)
  • offset (number, optional): Pagination offset
Response:
{
  "projects": [
    {
      "id": "project-123",
      "title": "Community Garden Initiative",
      "description": "Establishing a community garden...",
      "ward_code": "WARD001",
      "category": "environment",
      "status": "active",
      "progress": 65,
      "creator": {
        "id": "user-456",
        "name": "Sarah Johnson",
        "avatar": "https://..."
      },
      "participants": 24,
      "pledges": {
        "total_amount": 12500,
        "currency": "ZAR",
        "count": 18
      },
      "milestones": [
        {
          "id": "milestone-1",
          "title": "Secure Land",
          "status": "completed",
          "completed_at": "2025-11-15T10:00:00Z"
        },
        {
          "id": "milestone-2",
          "title": "Install Irrigation",
          "status": "in_progress",
          "due_date": "2025-12-31T23:59:59Z"
        }
      ],
      "created_at": "2025-10-01T10:00:00Z",
      "updated_at": "2025-12-28T15:30:00Z"
    }
  ],
  "pagination": {
    "total": 12,
    "limit": 20,
    "offset": 0,
    "has_more": false
  }
}

Get Governance Topics

GET /ward/{ward_code}/governance/topics
Description: Retrieve governance discussion topics for a ward. Path Parameters:
  • ward_code (string, required): Ward identifier
Query Parameters:
  • status (string, optional): Filter by status (open, closed, archived)
  • limit (number, optional): Number of results (default: 20)
  • offset (number, optional): Pagination offset
Response:
{
  "topics": [
    {
      "id": "topic-123",
      "title": "Budget Allocation for 2026",
      "description": "Discussion on how to allocate...",
      "ward_code": "WARD001",
      "status": "open",
      "type": "budget",
      "creator": {
        "id": "user-456",
        "name": "Councillor Smith",
        "role": "councillor"
      },
      "statements": [
        {
          "id": "statement-1",
          "author": {...},
          "content": "I propose we prioritize infrastructure...",
          "votes": {
            "agree": 45,
            "disagree": 12,
            "abstain": 8
          },
          "timestamp": "2025-12-28T10:00:00Z"
        }
      ],
      "consensus_score": 78.5,
      "participant_count": 65,
      "created_at": "2025-12-01T10:00:00Z",
      "closes_at": "2026-01-15T23:59:59Z"
    }
  ],
  "pagination": {
    "total": 8,
    "limit": 20,
    "offset": 0,
    "has_more": false
  }
}

AI & Intelligence API

Ask Ward Question (RAG)

POST /ward/{ward_code}/ask
Description: Ask a question about ward intelligence using the RAG pipeline. Path Parameters:
  • ward_code (string, required): Ward identifier
Request Body:
{
  "question": "What are the current safety concerns?",
  "max_sources": 5,
  "tier": "mid"
}
Request Body Parameters:
  • question (string, required): The question to ask
  • max_sources (number, optional): Maximum number of source documents (default: 5, max: 10)
  • tier (string, optional): AI model tier (cheap, mid, quality, default: mid)
Response:
{
  "answer": "Based on recent reports, Ward 1 has seen a 15% decrease in crime over the past quarter. However, there are ongoing concerns about power outages affecting the northern section, with 3 incidents reported in the last week. The community has organized neighborhood watch programs that have been effective in reducing petty crime.",
  "sources": [
    {
      "id": "doc-123",
      "title": "Q4 2025 Safety Report",
      "snippet": "Crime statistics show 15% decrease...",
      "url": "/reports/safety-q4-2025",
      "timestamp": "2025-12-20T10:00:00Z"
    },
    {
      "id": "doc-124",
      "title": "Power Outage Log - Week 52",
      "snippet": "3 outages reported in northern section...",
      "url": "/logs/power-outages-2025-w52",
      "timestamp": "2025-12-28T15:30:00Z"
    }
  ],
  "metadata": {
    "model_used": "claude-3-haiku-20240307",
    "tokens_used": 542,
    "documents_retrieved": 2,
    "generated_at": "2025-12-30T16:00:00Z"
  }
}
Example Request:
curl -X POST "https://visita-intelligence.vercel.app/api/ward/WARD001/ask" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "question": "What are the current safety concerns?",
    "max_sources": 5,
    "tier": "mid"
  }'

Map Tiles API

Get Map Tile

GET /tiles/{z}/{x}/{y}
Description: Retrieve map tiles for ward visualization. Path Parameters:
  • z (number, required): Zoom level
  • x (number, required): Tile X coordinate
  • y (number, required): Tile Y coordinate
Query Parameters:
  • layer (string, optional): Layer type (wards, businesses, signals, default: wards)
  • style (string, optional): Map style (light, dark, satellite, default: light)
Response: Returns a PNG or WebP image tile. Example Request:
curl -X GET "https://visita-intelligence.vercel.app/api/tiles/14/12345/67890?layer=wards&style=light" \
  -H "Authorization: Bearer $TOKEN"

Error Responses

All API endpoints return consistent error responses:
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid ward_code format",
    "details": {
      "field": "ward_code",
      "value": "INVALID",
      "constraint": "Must match pattern ^WARD\\d{3}$"
    }
  }
}
Common Error Codes:
CodeHTTP StatusDescription
VALIDATION_ERROR400Request validation failed
UNAUTHORIZED401Authentication required or invalid
FORBIDDEN403Insufficient permissions
NOT_FOUND404Resource not found
RATE_LIMITED429Rate limit exceeded
INTERNAL_ERROR500Internal server error

Rate Limiting

API requests are rate-limited to ensure fair usage:
  • Authenticated requests: 1000 requests per hour per user
  • Unauthenticated requests: 100 requests per hour per IP
Headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 987
X-RateLimit-Reset: 1640995200

Pagination

List endpoints support cursor-based pagination: Query Parameters:
  • limit (number): Number of results per page (default: 20, max: 100)
  • offset (number): Number of results to skip (default: 0)
Response:
{
  "data": [...],
  "pagination": {
    "total": 245,
    "limit": 20,
    "offset": 0,
    "has_more": true
  }
}
Example Pagination:
# First page
curl "https://visita-intelligence.vercel.app/api/business/search?limit=20&offset=0"

# Second page
curl "https://visita-intelligence.vercel.app/api/business/search?limit=20&offset=20"

# Third page
curl "https://visita-intelligence.vercel.app/api/business/search?limit=20&offset=40"

SDKs & Libraries

TypeScript/JavaScript

import { VisitaAPI } from '@visita/sdk';

const client = new VisitaAPI({
  baseURL: 'https://visita-intelligence.vercel.app/api',
  token: 'your-supabase-token'
});

// Get ward overview
const ward = await client.wards.get('WARD001');

// Search businesses
const businesses = await client.businesses.search({
  q: 'coffee',
  ward_code: 'WARD001'
});

// Ask AI question
const answer = await client.ai.ask('WARD001', {
  question: 'What are the safety concerns?'
});

Testing

Test the API using the provided sandbox:
# Get a test token
curl -X POST "https://visita-intelligence.vercel.app/api/auth/test-token" \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]"}'

# Use the token in subsequent requests
TOKEN="your-test-token"
curl -H "Authorization: Bearer $TOKEN" \
  "https://visita-intelligence.vercel.app/api/ward/WARD001"
Test tokens expire after 1 hour and can only access test data.

Support

For API support: