# en-tri Integration API

This document is written for AI agents and partner developers. Treat it as the source of truth for how external sites should use en-tri.

## Product Boundary

en-tri is the booking layer for small sports events.

The organiser is the hero. en-tri helps them create a session, sell spots, publish it to many channels, and keep one roster.

Source-of-truth rule:

```text
If a field affects whether someone can book, pay, attend, or take a spot, en-tri owns it.
If a field affects discovery, SEO, branding, sport-specific presentation, or marketplace ranking, the channel owns it.
```

Examples:

```text
en-tri owns: date, time, price, capacity, spots remaining, payment state, booking status, attendee contact, waitlist.
Channel owns: landing page copy, images, sport-specific filters, venue guides, recommendations, court assignment, tournament operations.
```

Long-term pattern:

```text
Your site sells the event. en-tri sells the spot.
```

## Integration Levels

Use the simplest integration that meets the UX need.

1. Link: channel sends users to `https://en-tri.com/o/{organiserSlug}/{eventSlug}`.
2. Embed: channel displays `https://en-tri.com/embed/{channelSlug}/{organiserSlug}/{eventSlug}` in a frame or hosted view.
3. API: channel renders native UI and uses en-tri APIs for availability, payment, booking confirmation, and source tracking.

For owned properties and high-volume partner sites, prefer API-first native UI. Use embeds only as a fast fallback.

## Current Public API

Base URL:

```text
https://en-tri.com
```

All responses are JSON unless noted. Times use local organiser/event time. Dates are `YYYY-MM-DD`. Money uses minor units, e.g. cents.

### List Channel Events

```http
GET /api/channels/{channelSlug}/events
GET /api/channels/{channelSlug}/events?organiser={organiserSlug}
GET /api/channels/{channelSlug}/events/{organiserSlug}/{eventSlug}/bookings
```

Purpose: list upcoming published events enabled for a distribution channel.

Current channel slugs:

```text
foot7
our-excuse
padelclique
```

Response:

```json
{
  "channel": { "slug": "foot7", "name": "Foot7", "origin": "https://foot7.org" },
  "organisers": [
    { "name": "Futbol Amistoso", "slug": "futbol-amistoso", "currency": "EUR" }
  ],
  "organiser": { "name": "Futbol Amistoso", "slug": "futbol-amistoso", "currency": "EUR" },
  "events": [
    {
      "organiserSlug": "futbol-amistoso",
      "organiserName": "Futbol Amistoso",
      "eventSlug": "friday-football-22-05-2026",
      "title": "Friday Football",
      "description": "Friendly 7-a-side game",
      "date": "2026-05-22",
      "startTime": "19:00",
      "endTime": "20:00",
      "priceCents": 800,
      "currency": "EUR",
      "spotsTotal": 14,
      "spotsRemaining": 5,
      "placeName": "Example Sports Centre",
      "placeAddress": "Example Street, Madrid",
      "placeId": "google-place-id",
      "lat": 40.4168,
      "lng": -3.7038,
      "canonicalUrl": "https://en-tri.com/o/futbol-amistoso/friday-football-22-05-2026",
      "embedUrl": "https://en-tri.com/embed/foot7/futbol-amistoso/friday-football-22-05-2026"
    }
  ]
}
```

404 response for unknown channel:

```json
{ "channel": null, "organisers": [], "events": [] }
```

### List Channel Event Bookings

```http
GET /api/channels/{channelSlug}/events/{organiserSlug}/{eventSlug}/bookings
```

Purpose: show a public roster on a native channel event page. This returns display names and booking status only. It does not expose email, phone, notes, payment ids, or organiser-only fields.

Response:

```json
{
  "spotsTotal": 4,
  "bookings": [
    { "position": 1, "name": "Jane Player", "status": "confirmed", "guests": 0 },
    { "position": 2, "name": "Sam Player", "status": "waitlisted", "guests": 0 }
  ]
}
```

### Legacy Foot7 Event Feed

```http
GET /api/foot7/events
GET /api/foot7/events?organiser={organiserSlug}
```

Purpose: backwards-compatible Foot7 feed. New integrations should use `/api/channels/foot7/events`.

### Create Channel Booking

```http
POST /api/channels/{channelSlug}/bookings
Content-Type: application/json
Idempotency-Key: optional-partner-booking-id
```

Purpose: create a confirmed or waitlisted booking from a native partner site when payment is free, offline, or handled outside en-tri.

This is the preferred endpoint for native integrations such as Padel Clique. It lets the channel render its own fast UI while en-tri owns the roster, capacity, waitlist, organiser notifications, and source tracking.

Request:

```json
{
  "organiserSlug": "coach-name",
  "eventSlug": "group-padel-class-26-05-2026",
  "name": "Jane Player",
  "email": "jane@example.com",
  "phone": "+34600111222",
  "guests": 0,
  "note": "Left-handed player",
  "waitlist": false,
  "idempotencyKey": "padelclique-booking-123",
  "signupOrigin": "https://www.padelclique.com",
  "signupReferrer": "https://www.padelclique.com/events/group-class"
}
```

Rules:

```text
name is required.
email or phone is required.
guests defaults to 0.
If spots are available and waitlist is false, status is confirmed.
If the event is full or waitlist is true, status is waitlisted.
For repeat requests with the same event, channel, and idempotency key, en-tri returns the original booking.
```

Success:

```json
{
  "ok": true,
  "booking": {
    "id": "booking_uuid",
    "status": "confirmed",
    "name": "Jane Player",
    "email": "jane@example.com",
    "phone": "+34600111222",
    "guests": 0
  },
  "event": {
    "organiserSlug": "coach-name",
    "eventSlug": "group-padel-class-26-05-2026",
    "title": "Group Padel Class",
    "spotsRemaining": 2
  }
}
```

Errors:

```text
400 missing organiserSlug/eventSlug/name or missing email+phone
404 channel or event not found
```

### Create Payment Intent

```http
POST /api/payment-intent/{organiserSlug}/{eventSlug}
Content-Type: application/json
```

Purpose: create a Stripe Payment Intent for a paid event. This reserves no spot until payment is confirmed.

Request:

```json
{
  "name": "Jane Player",
  "phone": "+34600111222",
  "signupSource": "api",
  "signupChannel": "generic-channel",
  "signupOrigin": "https://generic-channel.example",
  "signupReferrer": "https://generic-channel.example/events/group-class"
}
```

`signupSource` values:

```text
en-tri
embed
api
manual
```

Partner API/native integrations should send:

```json
{ "signupSource": "api", "signupChannel": "generic-channel" }
```

Response:

```json
{
  "clientSecret": "pi_...",
  "paymentIntentId": "pi_...",
  "publishableKey": "pk_...",
  "stripeAccount": "acct_..."
}
```

Errors:

```text
400 name/phone missing, event is free
404 event not found
409 sold out
503 payments unavailable
```

### Confirm Payment

```http
POST /api/payment-confirm/{organiserSlug}/{eventSlug}
Content-Type: application/json
```

Purpose: verify a Stripe Payment Intent, mark the booking confirmed, decrement available spots, and trigger notifications.

Request:

```json
{ "paymentIntentId": "pi_..." }
```

Success:

```json
{ "ok": true }
```

Payment not finished:

```json
{ "ok": false, "status": "requires_payment_method" }
```

### Booking Follow-up

```http
POST /api/booking-followup/{organiserSlug}/{eventSlug}
Content-Type: application/json
```

Purpose: attach optional email/note after payment, send confirmation email if email was newly provided, and update Stripe receipt email when possible.

Request:

```json
{
  "paymentIntentId": "pi_...",
  "email": "jane@example.com",
  "note": "Left-handed player"
}
```

Response:

```json
{ "ok": true }
```

## Webhooks To Organisers

Organisers can set a webhook URL in dashboard settings. en-tri currently sends this payload after confirmed bookings:

```json
{
  "event": "booking.confirmed",
  "event_title": "Group Padel Class",
  "event_date": "2026-05-26",
  "event_time": "19:00",
  "event_slug": "group-padel-class-26-05-2026",
  "organiser_slug": "coach-name",
  "attendee_name": "Jane Player",
  "attendee_email": "jane@example.com",
  "attendee_phone": "+34600111222",
  "guests": 0
}
```

Webhook URLs must use HTTPS.

## Recommended Channel Architecture

Create one en-tri event for every bookable event on a partner or organiser site.

Your site may keep its own event/page record for SEO, discovery, and sport-specific presentation, but it should store `enTriEventId` or `{organiserSlug, eventSlug}` and treat en-tri as authoritative for booking fields.

Recommended data split:

```text
Channel event:
  slug, page title, SEO copy, images, venue guide, sport categories, discovery ranking, en-tri event reference

en-tri event:
  title used at checkout, date, time, location, price, capacity, minimum-to-run, booking questions, spots, roster, payment state
```

Recommended runtime flow:

```text
1. Your site lists events from its own fast cache or /api/channels/generic-channel/events.
2. Your site renders native event pages.
3. Your site creates payment via en-tri.
4. Stripe confirms payment in native site UI.
5. Your site calls en-tri payment-confirm.
6. en-tri updates roster/spots and sends organiser notifications.
7. Your site refreshes cached availability.
```

## API Design Targets

Future API work should preserve these properties:

```text
Native-feeling: partners can render their own UI.
One source of truth: en-tri owns commercial booking state.
One-time setup: organisers connect a channel once; future events appear automatically.
Idempotent writes: partner booking APIs must accept a partner idempotency key.
Traceable attribution: every booking records source, channel, origin, and referrer.
Stable docs: all public fields are documented before partners depend on them.
```

## Gaps To Close Next

These are intentional next API steps, not current guarantees:

```text
POST /api/events                 create event for authenticated organiser/API client
PATCH /api/events/{id}           update bookable fields
GET /api/organisers/{slug}/events public organiser feed for website widgets
GET /api/events/{id}/availability explicit availability endpoint
Webhook signatures               HMAC header for organiser webhooks
Public widget script             one-time website install that lists future events automatically
API keys                         scoped organiser/channel credentials
OpenAPI coverage                 expand from current public endpoints to full partner API
```
