API Reference

The Poetmap REST API gives you programmatic access to poets, their geographic locations, and their published works. All responses are JSON.

Introduction

The base URL for all endpoints is:

https://poermap.veillax.com/api

Authentication

Public read endpoints (GET requests on /api/poets, /api/locations, and /api/works) work without a token, but are subject to a stricter IP-based rate limit.

To get a higher limit, obtain a personal API token from your Account page, then pass it as a Bearer token in the Authorization header:

Authorization: Bearer pm_your_token_here

Example — curl

curl https://poermap.veillax.com/api/poets \
  -H "Authorization: Bearer pm_your_token_here"

Example — JavaScript (fetch)

const res = await fetch('https://poermap.veillax.com/api/poets', {
  headers: {
    'Authorization': 'Bearer pm_your_token_here'
  }
});
const poets = await res.json();

Example — Python (requests)

import requests

resp = requests.get(
    'https://poermap.veillax.com/api/poets',
    headers={'Authorization': 'Bearer pm_your_token_here'}
)
poets = resp.json()

Tokens are hashed before storage — Poetmap never holds your raw token. If you lose it, revoke it and generate a new one.

Rate limits

Limits apply per 15-minute sliding window. Responses include headers so you can track your current usage:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the window
X-RateLimit-RemainingRequests left in the current window
X-RateLimit-ResetISO-8601 timestamp when the window resets
ModeLimitKey
Authenticated (token)300 requests / 15 minper token
Anonymous (no token)60 requests / 15 minper IP address

When you exceed the limit, the API responds with HTTP 429:

{
  "error":     "Rate limit exceeded",
  "limit":     300,
  "remaining": 0,
  "reset_at":  "2025-04-16T14:30:00.000Z"
}

Errors

Errors follow a consistent shape. The error field is always a human-readable string; some errors include a hint field with remediation advice.

{ "error": "Poet not found" }
{ "error": "Missing API token", "hint": "Pass your token as: Authorization: Bearer pm_..." }
StatusMeaning
400Bad request — missing or invalid body parameters
401Missing or invalid token
403Authenticated but not authorized (banned or wrong role)
404Resource not found
429Rate limit exceeded
500Internal server error

Poets

A poet object contains basic biographical metadata.

{
  "id":        1,
  "name":      "Emily Dickinson",
  "bio":       "...",
  "image_url": "https://...",
  "wiki_url":  "https://en.wikipedia.org/wiki/Emily_Dickinson"
}
GET/api/poets
Returns all poets ordered by name.
GET/api/poets/map
Returns all poets with their locations and works joined — used to populate the map. Heavier than /api/poets; suitable for full data loads.
GET/api/poets/:id
Returns a single poet by ID, including their locations and works arrays.
POST/api/poets
Create a poet. Body: { name, bio?, image_url?, wiki_url? }. name is required.
PATCH/api/poets/:id
Partial update. Any subset of { name, bio, image_url, wiki_url }. Omitted fields are left unchanged.
DELETE/api/poets/:id
Permanently delete a poet. Returns 204 No Content.

Locations

A location is a geographic point associated with a poet — birthplace, residence, place of death, etc.

{
  "id":            12,
  "poet_id":       1,
  "location_type": "birth",
  "place_name":    "Amherst, Massachusetts",
  "lat":           42.3732,
  "lng":           -72.5199
}
GET/api/locations
Returns all locations. Filter by poet with ?poet_id=1.
GET/api/locations/:id
Returns a single location by ID.
POST/api/locations
Create a location. Body: { poet_id, location_type, place_name, lat, lng }. All fields required.
PATCH/api/locations/:id
Partial update. Any subset of { location_type, place_name, lat, lng }.
DELETE/api/locations/:id
Delete a location. Returns 204 No Content.

Works

A work is a published piece — collection, chapbook, poem — tied to a poet.

{
  "id":          7,
  "poet_id":     1,
  "title":       "Poems by Emily Dickinson",
  "year":        1890,
  "description": "...",
  "url":         "https://..."
}
GET/api/works
Returns all works ordered by year. Filter with ?poet_id=1.
POST/api/works
Create a work. Body: { poet_id, title, year?, description?, url? }.
PATCH/api/works/:id
Partial update. Any subset of { title, year, description, url }.
DELETE/api/works/:id
Delete a work. Returns 204 No Content.