API Integrations - Mad Monkey Project

Project: "Where Has Benny Been?" - API Documentation

Jump to: n8n Webhooks Shlink API Listmonk API Geo IP APIs External APIs

n8n Webhook Endpoints

Base URL: https://hooks.mad-monkey-creations.com

GET /scan

Description: Log QR code scan and redirect to landing page

Request Parameters

Parameter Type Required Description
qr_id string Yes Card ID (e.g., "T42", "N123")

Headers Captured

Example Request

curl -L "https://hooks.mad-monkey-creations.com/scan?qr_id=T42"

Response

HTTP 302 Redirect to: https://mad-monkey-creations.com/benny?qr_id=T42

Workflow Actions

  1. Extract qr_id from query parameter
  2. Capture IP address and User-Agent
  3. Anonymize IP address (remove last octet)
  4. Call geolocation API (ipapi.co or ipinfo)
  5. Insert record into PostgreSQL scans table
  6. Update cards.last_scan_ts and increment total_scans
  7. Return 302 redirect with Location header

Error Handling

Error HTTP Code Response
Missing qr_id 400 {"error": "Missing qr_id parameter"}
Invalid qr_id 404 {"error": "Card not found"}
Database error 500 {"error": "Internal server error"}

POST /entry

Description: Process contest entry form submission

Request Body (JSON)

{
    "qr_id": "T42",
    "email": "user@example.com",
    "name": "John Doe",
    "user_city": "Chicago, IL",
    "consent": true,
    "captcha_token": "optional-captcha-token"
}

Validation Rules

Field Type Required Validation
qr_id string Yes Must exist in cards table
email string Yes Valid email format
name string No Max 100 characters
user_city string Yes Max 100 characters
consent boolean Yes Must be true

Rate Limiting

Example Request

curl -X POST https://hooks.mad-monkey-creations.com/entry \
  -H "Content-Type: application/json" \
  -d '{
    "qr_id": "T42",
    "email": "user@example.com",
    "name": "John Doe",
    "user_city": "Chicago, IL",
    "consent": true
  }'

Success Response

{
    "success": true,
    "message": "Entry submitted! Check your email to confirm.",
    "entry_id": 12345
}

Workflow Actions

  1. Validate all required fields
  2. Check rate limits (email, IP)
  3. Check if email already entered this month
  4. Insert entry to PostgreSQL entries table (verified=false)
  5. Calculate month_bucket (YYYY-MM format)
  6. Call Listmonk API to create/update subscriber
  7. Add tags: qr_campaign, benny, entry_month_YYYY-MM
  8. Listmonk automatically sends double opt-in email
  9. Return success response

Error Responses

Error HTTP Code Response
Invalid email format 400 {"error": "Invalid email address"}
Missing consent 400 {"error": "Consent required"}
Rate limit exceeded 429 {"error": "Too many entries, please try again later"}
Duplicate entry (same month) 409 {"error": "You've already entered this month"}

POST /listmonk/confirm

Description: Handle Listmonk webhook on subscriber confirmation

Request Body (from Listmonk)

{
    "event": "subscriber.confirmed",
    "subscriber": {
        "id": 123,
        "email": "user@example.com",
        "name": "John Doe",
        "status": "enabled",
        "lists": [1]
    }
}
Authentication: Webhook signature validation (if supported by Listmonk) or IP whitelist (10.0.0.250)

Workflow Actions

  1. Validate webhook signature or source IP
  2. Extract subscriber email from payload
  3. Update PostgreSQL entries.verified=true for email
  4. Set verified_at timestamp
  5. Update users table with confirmed status
  6. Trigger welcome email (immediate)
  7. Schedule story email (Day 1 delay - 24 hours)
  8. Schedule reward email (Day 3 delay - 72 hours)

Success Response

{
    "success": true,
    "message": "Subscriber confirmed"
}

Base URL: http://10.0.0.250:8081 (internal) or https://admin.mmlnk.us/rest (external, IP-restricted)

API Version: v3

Authentication: API Key via X-Api-Key header

GET /rest/v3/short-urls

Description: List all short URLs

Example Request

curl -X GET "http://10.0.0.250:8081/rest/v3/short-urls" \
  -H "X-Api-Key: your-api-key-here"

Query Parameters

Parameter Type Description
page integer Page number (default: 1)
itemsPerPage integer Items per page (default: 10)
searchTerm string Filter by short code or long URL
tags[] array Filter by tags

Example Response

{
  "shortUrls": {
    "data": [
      {
        "shortCode": "T42",
        "shortUrl": "https://mmlnk.us/T42",
        "longUrl": "https://hooks.mad-monkey-creations.com/scan?qr_id=T42",
        "dateCreated": "2024-12-01T10:30:00Z",
        "visitsCount": 42,
        "tags": ["benny", "traveler"],
        "domain": "mmlnk.us"
      }
    ],
    "pagination": {
      "currentPage": 1,
      "pagesCount": 10,
      "itemsPerPage": 10,
      "itemsInCurrentPage": 10,
      "totalItems": 95
    }
  }
}

POST /rest/v3/short-urls

Description: Create a new short URL

Request Body

{
    "longUrl": "https://hooks.mad-monkey-creations.com/scan?qr_id=T42",
    "customSlug": "T42",
    "domain": "mmlnk.us",
    "tags": ["benny", "traveler"],
    "title": "Benny Traveler Card 42",
    "findIfExists": true
}

Example Request

curl -X POST "http://10.0.0.250:8081/rest/v3/short-urls" \
  -H "X-Api-Key: your-api-key-here" \
  -H "Content-Type: application/json" \
  -d '{
    "longUrl": "https://hooks.mad-monkey-creations.com/scan?qr_id=T42",
    "customSlug": "T42",
    "domain": "mmlnk.us",
    "tags": ["benny", "traveler"]
  }'

Success Response

{
  "shortCode": "T42",
  "shortUrl": "https://mmlnk.us/T42",
  "longUrl": "https://hooks.mad-monkey-creations.com/scan?qr_id=T42",
  "dateCreated": "2024-12-01T10:30:00Z",
  "tags": ["benny", "traveler"],
  "domain": "mmlnk.us"
}

GET /rest/v3/short-urls/{shortCode}/visits

Description: Get visit statistics for a short URL

Example Request

curl -X GET "http://10.0.0.250:8081/rest/v3/short-urls/T42/visits" \
  -H "X-Api-Key: your-api-key-here"

Example Response

{
  "visits": {
    "data": [
      {
        "date": "2024-12-01T15:23:10Z",
        "referer": "",
        "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)",
        "visitLocation": {
          "cityName": "Chicago",
          "countryName": "United States",
          "regionName": "Illinois"
        }
      }
    ],
    "pagination": {
      "currentPage": 1,
      "pagesCount": 3,
      "totalItems": 42
    }
  }
}

Listmonk API

Base URL: http://10.0.0.250:9000/api

API Documentation: https://listmonk.app/docs/apis/apis

Authentication: HTTP Basic Auth (username:password encoded in Authorization header)

POST /api/subscribers

Description: Create or update a subscriber

Request Body

{
    "email": "user@example.com",
    "name": "John Doe",
    "status": "enabled",
    "lists": [1],
    "attribs": {
        "city": "Chicago",
        "qr_id": "T42",
        "entry_month": "2024-12"
    },
    "preconfirm_subscriptions": false
}

Example Request

curl -X POST "http://10.0.0.250:9000/api/subscribers" \
  -u "admin:your-password" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "name": "John Doe",
    "status": "enabled",
    "lists": [1],
    "preconfirm_subscriptions": false
  }'

Success Response

{
  "data": {
    "id": 123,
    "created_at": "2024-12-01T10:30:00Z",
    "updated_at": "2024-12-01T10:30:00Z",
    "email": "user@example.com",
    "name": "John Doe",
    "status": "enabled",
    "lists": [
      {
        "id": 1,
        "name": "Mad Monkey - Benny Campaign",
        "subscription_status": "unconfirmed"
      }
    ]
  }
}
Note: When preconfirm_subscriptions is false, Listmonk automatically sends a double opt-in email. Set to true to skip confirmation (not recommended for compliance).

GET /api/subscribers

Description: List subscribers with filters

Query Parameters

Parameter Type Description
query string SQL WHERE clause (e.g., "subscribers.email LIKE '%@gmail.com'")
list_id integer Filter by list ID
page integer Page number
per_page integer Items per page (max 100)

Example Request

curl -X GET "http://10.0.0.250:9000/api/subscribers?list_id=1&query=subscribers.attribs->>'entry_month'='2024-12'" \
  -u "admin:your-password"

POST /api/campaigns/{id}/status

Description: Send a campaign (transactional email)

Request Body

{
    "status": "running"
}

Example Request

curl -X PUT "http://10.0.0.250:9000/api/campaigns/5/status" \
  -u "admin:your-password" \
  -H "Content-Type: application/json" \
  -d '{"status": "running"}'

POST /api/tx

Description: Send transactional email (for drip campaigns)

Request Body

{
    "subscriber_email": "user@example.com",
    "template_id": 2,
    "data": {
        "name": "John",
        "story": "Benny was spotted in Chicago, then traveled to Milwaukee...",
        "discount_code": "BANANAS10"
    }
}

Example Request

curl -X POST "http://10.0.0.250:9000/api/tx" \
  -u "admin:your-password" \
  -H "Content-Type: application/json" \
  -d '{
    "subscriber_email": "user@example.com",
    "template_id": 2,
    "data": {
        "name": "John",
        "discount_code": "BANANAS10"
    }
  }'

Geolocation APIs

ipapi.co (Recommended)

Base URL: https://ipapi.co

Rate Limit: 1,000 requests/day (free tier), 30,000/month (paid)

GET /{ip}/json

Example Request

curl "https://ipapi.co/203.0.113.42/json/"

Example Response

{
  "ip": "203.0.113.42",
  "city": "Chicago",
  "region": "Illinois",
  "region_code": "IL",
  "country": "US",
  "country_name": "United States",
  "postal": "60601",
  "latitude": 41.8781,
  "longitude": -87.6298,
  "timezone": "America/Chicago",
  "org": "Comcast Cable Communications LLC"
}
n8n Integration: Use HTTP Request node with URL: https://ipapi.co/{{ $json.ip }}/json/

ipinfo.io (Alternative)

Base URL: https://ipinfo.io

Rate Limit: 50,000 requests/month (free tier)

GET /{ip}/json

Example Request

curl "https://ipinfo.io/203.0.113.42/json?token=YOUR_TOKEN"

Example Response

{
  "ip": "203.0.113.42",
  "city": "Chicago",
  "region": "Illinois",
  "country": "US",
  "loc": "41.8781,-87.6298",
  "postal": "60601",
  "timezone": "America/Chicago",
  "org": "AS7922 Comcast Cable Communications LLC"
}

External APIs

Printify API (Prize Fulfillment)

Base URL: https://api.printify.com/v1

Documentation: https://developers.printify.com

Authentication: Bearer token via Authorization: Bearer YOUR_TOKEN

POST /shops/{shop_id}/orders.json

Request Body (Simplified)

{
  "external_id": "prize-2024-12",
  "label": "Mad Monkey Winner - December 2024",
  "line_items": [
    {
      "product_id": "your-product-id",
      "variant_id": 12345,
      "quantity": 1
    }
  ],
  "shipping_method": 1,
  "address_to": {
    "first_name": "John",
    "last_name": "Doe",
    "email": "user@example.com",
    "address1": "123 Main St",
    "city": "Chicago",
    "region": "IL",
    "zip": "60601",
    "country": "US"
  }
}

Example Request

curl -X POST "https://api.printify.com/v1/shops/YOUR_SHOP_ID/orders.json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d @order.json

Etsy API (Product Links)

Base URL: https://openapi.etsy.com/v3

Documentation: https://developers.etsy.com

Authentication: OAuth 2.0 or API Key via x-api-key header

GET /application/shops/{shop_id}/listings/active

Example Request

curl "https://openapi.etsy.com/v3/application/shops/YOUR_SHOP_ID/listings/active" \
  -H "x-api-key: YOUR_API_KEY"
Usage: Fetch active product listings to include in reward emails or landing page