Reference
External Integration API
Embed a Cronolog timeline in your own site and let authenticated users create or edit events in a popup window — without leaving your page.
How it works
The integration has three moving parts:
- 1. API key Your server holds a secret API key tied to one Cronolog timeline. It uses this key to generate short-lived, one-time magic links on demand.
- 2. Magic link Each magic link signs the Cronolog user in automatically and opens a specific form — either "add an event" or "edit this event". The link expires after 15 minutes and can only be used once.
- 3. Popup window The form opens in a small popup window. When the user saves or deletes the event, the popup posts a message back to your page and closes itself. Your page can refresh the relevant content without a full reload.
Your page Your server Cronolog
──────── ─────────── ────────
User clicks "Edit"
──── POST /your-api ──────►
POST /api/auth/magic_link
(Bearer: YOUR_API_KEY)
◄──── { url: magic_link } ──
◄── magic link URL ───────
window.open(magic_link) ──────────────────────────────► /auth/magic?t=TOKEN
sign in user
redirect to edit form
user edits & saves
window receives postMessage ◄───────────────────────────── popup closes
refresh event on page
Prerequisites
- A Cronolog account with at least one timeline.
- An API key for that timeline — create one at Settings → API Keys.
- A server that can make outbound HTTPS requests — the API key must never be exposed to a browser.
Step 1 — Create an API key
Go to Settings → API Keys and create a key. You will be asked for:
example.com, localhost). Magic links will only accept a return_to URL whose host is in this list. Enter one hostname per line — no scheme or path.
The raw token is shown once at creation time. Store it securely (environment variable, secrets manager). It cannot be recovered — only rotated.
Step 2 — Request a magic link
When a user on your site wants to add or edit an event, your server makes a signed request to Cronolog.
Endpoint
POST https://cronolog.ca/api/auth/magic_link
Request headers
Authorization: Bearer YOUR_API_KEY
Content-Type: application/x-www-form-urlencoded
Request body parameters
return_to
required
The URL on your site that opened the popup. The popup posts a message to this origin after saving. Must be on the key's allowed return host.
event_id
optional
The Cronolog event ID to edit. Omit this parameter to open the "add a new event" form instead.
Success response 200 OK
{
"url": "https://cronolog.ca/auth/magic?t=TOKEN"
}
Open this URL in a popup window. It is single-use and expires in 15 minutes.
Error responses
| Status | Body | Cause |
|---|---|---|
| 401 | {"error":"Unauthorized"} | Missing, invalid, or revoked API key |
| 402 | {"error":"Subscription required…"} | Key owner's Cronolog subscription has lapsed |
| 422 | {"error":"return_to is required"} | return_to param missing |
| 422 | {"error":"return_to host does not match…"} | return_to is on a different host than the key allows |
| 404 | {"error":"Event not found"} | event_id does not exist |
| 403 | {"error":"Event does not belong to this timeline"} | event_id belongs to a different timeline |
| 429 | {"error":"Too many requests…"} | More than 30 requests per minute per key |
Example — curl
# New event
curl -X POST https://cronolog.ca/api/auth/magic_link \
-H "Authorization: Bearer clk_YOUR_KEY_HERE" \
--data-urlencode "return_to=https://example.com/timeline"
# Edit an existing event
curl -X POST https://cronolog.ca/api/auth/magic_link \
-H "Authorization: Bearer clk_YOUR_KEY_HERE" \
--data-urlencode "return_to=https://example.com/timeline" \
--data-urlencode "event_id=42"
Step 3 — Open the popup
Pass the magic link URL to window.open().
The popup signs the user in and opens the event form.
When the user saves, deletes, or closes the form, the popup posts a message to your page and closes itself.
Client-side JavaScript
// 1. Fetch a magic link from your own server (never expose the API key here).
async function openEditPopup(eventId) {
const res = await fetch('/your-server/magic-link', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ eventId })
})
const { url } = await res.json()
// 2. Open the popup.
const popup = window.open(
url,
'cronolog-edit',
'width=780,height=700,resizable=yes,scrollbars=yes'
)
// 3. Listen for the result.
window.addEventListener('message', function handler(event) {
if (event.origin !== 'https://cronolog.ca') return
const { type, eventId } = event.data
if (type === 'cronolog:saved') { /* event created or updated */ }
if (type === 'cronolog:deleted') { /* event was deleted */ }
window.removeEventListener('message', handler)
popup?.close()
})
}
// "Add event" — omit eventId
function openNewEventPopup() {
return openEditPopup(null)
}
postMessage payload
When the popup closes after a successful save or delete, your page receives a
window.message event with this shape:
{
type: "cronolog:saved" // or "cronolog:deleted"
eventId: 42 // Cronolog event ID (0 for a newly created event)
}
The message origin is always https://cronolog.ca — always verify this before acting on the payload.
No message is sent if the user closes the popup without saving.
Do not open the popup with noopener — that severs window.opener and the message will never arrive.
For a newly created event, eventId is the Cronolog ID of the new record. You can store this to construct an edit link for later.
Surfacing event IDs
To build an "edit" button for each event on your page, you need the Cronolog event ID for each one.
The timeline JSON feed — available at
https://cronolog.ca/t/YOUR_TOKEN.json
— includes an id field on every event.
{
"timeline": { ... },
"events": [
{
"id": 42,
"title": "Moved to Vancouver",
"date": "2019",
...
}
]
}
Fetch this feed when rendering your page and pass each event's id to the magic link request
when the user clicks its edit button. See the format reference for the full JSON schema.
Key management
All key management is done in Settings → API Keys.
Security notes
- The API key must only be used server-side. It is a long-lived credential with write access to your timeline.
- Magic links are one-time and expire after 15 minutes. There is no harm in generating many of them — each is independent.
- The return_to host is validated against the key's allowed return host. A magic link cannot be used to redirect back to an arbitrary site.
- Always check event.origin === 'https://cronolog.ca' before acting on a postMessage. Do not trust messages from other origins.
- The popup is opened on the cronolog.ca domain. Cronolog handles the authentication and form — your site never handles credentials.
- Rate limiting is applied per API key: 30 requests per minute. This is more than sufficient for interactive use.
Complete example
A minimal server-side handler and the client-side JavaScript needed to wire up an edit button for each event on a page.
Server — generate a magic link
# Pseudo-code — adapt to your server framework
POST /your-server/magic-link
body: { eventId: 42 } # or null for new event
→ POST https://cronolog.ca/api/auth/magic_link
Authorization: Bearer $CRONOLOG_API_KEY
return_to: https://example.com/timeline-page
event_id: 42 # omit if creating new
← { "url": "https://cronolog.ca/auth/magic?t=TOKEN" }
→ return { url } to the browser
Client — open popup and handle result
const CRONOLOG_ORIGIN = 'https://cronolog.ca'
document.querySelectorAll('[data-edit-event]').forEach(btn => {
btn.addEventListener('click', async () => {
const eventId = btn.dataset.editEvent || null
// 1. Ask your server for a magic link.
const res = await fetch('/your-server/magic-link', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ eventId })
})
if (!res.ok) { console.error('Could not get magic link'); return }
const { url } = await res.json()
// 2. Open the popup.
window.open(url, 'cronolog-edit', 'width=780,height=700,resizable=yes,scrollbars=yes')
})
})
// 3. Listen for save/delete confirmation from the popup.
window.addEventListener('message', event => {
if (event.origin !== CRONOLOG_ORIGIN) return
const { type, eventId } = event.data
if (type === 'cronolog:saved') {
console.log('Event saved, Cronolog ID:', eventId)
// e.g. refetch your timeline JSON and re-render
}
if (type === 'cronolog:deleted') {
console.log('Event deleted, Cronolog ID:', eventId)
// e.g. remove the event from the DOM
}
})
HTML — edit buttons
<!-- Edit an existing event -->
<button data-edit-event="42">Edit event</button>
<!-- Add a new event (no ID needed) -->
<button data-edit-event="">Add event</button>