Skip to main content
GET
/
api
/
participants
Search Participants
curl --request GET \
  --url https://api.example.com/api/participants
{
  "participants": [
    {}
  ]
}

Endpoint

GET /api/participants?name={name}&company={company}

Description

Searches for participants by partial name match within a specific school. This endpoint allows users to find children who have participated in the Tamborrada Infantil across different years. Important: For privacy and accuracy, the search requires a minimum of first name + two surnames and an exact school name.

Authentication

No authentication required. This is a public endpoint.

Query Parameters

name
string
required
The participant’s full name to search for. Must include at least:
  • First name
  • Two surnames (Spanish/Basque naming convention)
The name is normalized (diacritics removed) and supports partial matching.Example: “Juan Garcia Lopez”
company
string
required
The exact school/company name. Must match exactly one of the values from /api/companies.Example: “Amara Berri”

Response

participants
array
required
Array of participant records, sorted by year (descending). Each record contains:
  • name (string): Full name of the participant (normalized)
  • school (string): School/company name
  • year (number): Year of participation

Success Response (200 OK)

{
  "participants": [
    {
      "name": "Juan Garcia Lopez",
      "school": "Amara Berri",
      "year": 2024
    },
    {
      "name": "Juan Garcia Lopez",
      "school": "Amara Berri",
      "year": 2023
    },
    {
      "name": "Juan Garcia Lopez",
      "school": "Amara Berri",
      "year": 2022
    }
  ]
}

Error Responses

Missing Parameters (400 Bad Request)

{
  "error": "Parametros 'name' y 'company' son obligatorios"
}

Insufficient Name Parts (400 Bad Request)

{
  "error": "Por favor, proporciona al menos un nombre y dos apellidos"
}
Returned when the name has fewer than 3 parts (first name + 2 surnames).

No Participants Found (500 Internal Server Error)

{
  "error": "Error: No participants found"
}
Returned when no matching participants are found for the given name and school.

Server Error (500 Internal Server Error)

{
  "error": "Error al obtener el estado del sistema",
  "details": "<technical details>"
}

Examples

cURL

# Search for a participant
curl "https://tamborradata.com/api/participants?name=Juan%20Garcia%20Lopez&company=Amara%20Berri"

# Note: URL encode spaces as %20

JavaScript (Fetch)

async function searchParticipant(name, company) {
  const params = new URLSearchParams({ name, company });
  const response = await fetch(
    `https://tamborradata.com/api/participants?${params}`
  );
  
  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.error);
  }
  
  const data = await response.json();
  return data.participants;
}

// Usage
try {
  const results = await searchParticipant(
    'Juan Garcia Lopez',
    'Amara Berri'
  );
  
  console.log(`Found ${results.length} participation(s)`);
  results.forEach(p => {
    console.log(`${p.year}: ${p.name} at ${p.school}`);
  });
} catch (error) {
  console.error('Search failed:', error.message);
}

JavaScript (Axios)

const axios = require('axios');

try {
  const { data } = await axios.get(
    'https://tamborradata.com/api/participants',
    {
      params: {
        name: 'Maria Rodriguez Sanchez',
        company: 'Santo Tomas Lizeoa'
      }
    }
  );
  
  console.log('Participants:', data.participants);
} catch (error) {
  if (error.response?.status === 400) {
    console.error('Invalid search parameters');
  } else if (error.response?.status === 500) {
    console.log('No participants found');
  } else {
    console.error('Error:', error.response?.data?.error);
  }
}

Python

import requests
from urllib.parse import urlencode

def search_participant(name, company):
    params = {'name': name, 'company': company}
    response = requests.get(
        'https://tamborradata.com/api/participants',
        params=params
    )
    
    if response.status_code == 200:
        data = response.json()
        return data['participants']
    else:
        error = response.json()
        raise Exception(error['error'])

# Usage
try:
    results = search_participant(
        'Ana Lopez Gonzalez',
        'La Salle Ikastetxea'
    )
    
    print(f"Found {len(results)} participation(s)")
    for p in results:
        print(f"{p['year']}: {p['name']} at {p['school']}")
except Exception as e:
    print(f"Error: {e}")

TypeScript

type Participant = {
  name: string;
  school: string;
  year: number;
};

type ParticipantsResponse = {
  participants: Participant[];
};

type ErrorResponse = {
  error: string;
};

async function searchParticipant(
  name: string,
  company: string
): Promise<Participant[]> {
  // Validate name has at least 3 parts
  const nameParts = name.trim().split(/\s+/);
  if (nameParts.length < 3) {
    throw new Error('Name must include first name and two surnames');
  }
  
  const params = new URLSearchParams({ name, company });
  const response = await fetch(
    `https://tamborradata.com/api/participants?${params}`
  );
  
  if (!response.ok) {
    const error: ErrorResponse = await response.json();
    throw new Error(error.error);
  }
  
  const data: ParticipantsResponse = await response.json();
  return data.participants;
}

// Usage
try {
  const participants = await searchParticipant(
    'Carlos Perez Ruiz',
    'Ekintza Ikastola'
  );
  
  if (participants.length > 0) {
    console.log('Participation history:');
    participants.forEach(p => {
      console.log(`  ${p.year}: ${p.school}`);
    });
  } else {
    console.log('No participations found');
  }
} catch (error) {
  console.error('Search error:', error);
}

Implementation Details

Source Code Reference

The endpoint is implemented in:
  • Route: app/(backend)/api/participants/route.ts:6
  • Service: app/(backend)/api/participants/services/participants.service.ts
  • Repository: app/(backend)/api/participants/repositories/participants.repo.ts:6
  • Validation: app/(backend)/api/participants/dtos/participants.schema.ts:5

Data Flow

  1. Extract name and company query parameters
  2. Validate both parameters are provided
  3. Normalize name (remove diacritics, trim whitespace)
  4. Validate name has at least 3 parts (first + 2 surnames)
  5. Query participants table with ILIKE for partial match
  6. Filter by exact school match
  7. Order results by year (descending)
  8. Return array of participants

Name Normalization

Names are normalized using:
const cleanName = name
  .normalize('NFD')
  .replace(/[\u0300-\u036f]/g, '')  // Remove diacritics
  .replace(/\s+/g, ' ')              // Normalize whitespace
  .trim();
This allows matching names with or without accents:
  • “José” matches “Jose”
  • “María” matches “Maria”
  • “Ñ” matches “N”

Database Query

The endpoint executes:
SELECT name, school, year
FROM participants
WHERE name ILIKE $1
  AND school = $2
ORDER BY year DESC;
Where $1 is %{cleanName}% (partial match) and $2 is the exact school name.

Use Cases

1. Participant Search Form

async function handleSearchForm(event) {
  event.preventDefault();
  
  const formData = new FormData(event.target);
  const name = formData.get('name');
  const company = formData.get('company');
  
  try {
    const participants = await searchParticipant(name, company);
    displayResults(participants);
  } catch (error) {
    displayError(error.message);
  }
}

function displayResults(participants) {
  const container = document.getElementById('results');
  container.innerHTML = participants
    .map(p => `<li>${p.year}: ${p.name} - ${p.school}</li>`)
    .join('');
}

2. Check Participation History

async function getParticipationYears(name, company) {
  try {
    const participants = await searchParticipant(name, company);
    return participants.map(p => p.year);
  } catch (error) {
    return [];
  }
}

const years = await getParticipationYears(
  'Juan Garcia Lopez',
  'Amara Berri'
);
console.log('Participated in:', years.join(', '));

3. Verify Participation

async function didParticipateInYear(name, company, year) {
  try {
    const participants = await searchParticipant(name, company);
    return participants.some(p => p.year === year);
  } catch (error) {
    return false;
  }
}

const participated = await didParticipateInYear(
  'Maria Rodriguez Sanchez',
  'La Salle Ikastetxea',
  2024
);
console.log('Participated in 2024:', participated);

Notes

Names in the database are stored normalized (without diacritics). The API handles normalization automatically.
The company parameter must match exactly (case-sensitive). Use /api/companies to get valid school names.
Partial name matching is supported. Searching “Juan Garcia” will match “Juan Garcia Lopez”, “Juan Garcia Ruiz”, etc.

Spanish Naming Convention

Spanish and Basque names typically follow the pattern:
[First Name] [Father's Surname] [Mother's Surname]
Examples:
  • Juan Garcia Lopez
  • Maria Rodriguez Sanchez
  • Ane Etxeberria Agirre
The API requires all three parts to ensure accurate identification.

Privacy Considerations

This endpoint returns participant data from published historical records. The data:
  • Comes from public Tamborrada participation lists
  • Is historical (not real-time)
  • Requires full name + school to query (not easily enumerable)
  • Is intended for legitimate research and statistical purposes
This API returns data from mock/synthetic records in this demo. Production data follows strict privacy guidelines.

Performance

  • Response Time: 100-200ms
  • Database Query: ILIKE query with index on name and school
  • Caching: Not recommended (data is user-specific)
  • Payload Size: Varies (typically < 5 KB)

Error Handling

async function searchParticipantSafely(name, company) {
  try {
    // Client-side validation
    if (!name || !company) {
      throw new Error('Name and company are required');
    }
    
    const nameParts = name.trim().split(/\s+/);
    if (nameParts.length < 3) {
      throw new Error(
        'Please provide first name and two surnames'
      );
    }
    
    const params = new URLSearchParams({ name, company });
    const response = await fetch(
      `https://tamborradata.com/api/participants?${params}`
    );
    
    if (!response.ok) {
      const error = await response.json();
      
      if (response.status === 500 && 
          error.error.includes('No participants found')) {
        // Not an error, just no results
        return [];
      }
      
      throw new Error(error.error);
    }
    
    const data = await response.json();
    return data.participants || [];
  } catch (error) {
    console.error('Participant search error:', error);
    throw error;
  }
}

Name Search Tips

Handling Special Characters

// API handles normalization, but you can pre-normalize:
function normalizeInput(str) {
  return str
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '')
    .trim();
}

const userInput = 'José María García';
const normalized = normalizeInput(userInput);
// Result: "Jose Maria Garcia"

Partial Name Matching

// These searches will work:
await searchParticipant('Juan Garcia Lopez', 'Amara Berri');
await searchParticipant('Garcia Lopez', 'Amara Berri');  // Partial
await searchParticipant('Juan', 'Amara Berri');  // Too short, but will match

// Remember: Still need 3 parts for validation

Build docs developers (and LLMs) love