API Integration

What this is for

Loopr exposes a GraphQL API so anything that can make an HTTP request can push items into your loop, complete them, snooze them, or read what’s in there. You don’t need to sync a new external service through a Stream — if you can write a webhook, you can write to the loop.

Typical uses:

  • A Zapier zap that turns a new row in Airtable into a loop item.
  • A shell alias on your laptop that creates an item from the command line.
  • A CI job that opens an urgent item when a build fails.
  • A custom scraper that replaces a Stream Loopr doesn’t support natively.

The two pieces

  1. An API key — a secret you send on every request so Loopr knows it’s you. Create them in Preferences → API Keys.
  2. The GraphQL endpointPOST https://loopr.life/gql. One URL, one schema, every operation.

There’s also a live playground at /gql/playground for poking around the schema and running queries against your own account.

Creating an API key

In preferences, name the key (e.g. macbook, ci, zapier) and pick an expiration (1 day up to 1 year, or Never). You’ll see the plaintext key exactly once when it’s created. Copy it somewhere safe — Loopr only stores a hash, so there is no way to retrieve it afterwards. If you lose it, revoke it and make a new one.

Keys look like loopr_<random>_<checksum>. The last four characters are shown in the UI so you can tell keys apart without revealing them. Revoke any key from the same list with one click.

Scope: a key acts as you. It can do anything your logged-in session can do — no finer-grained permissions. Treat it like a password; never commit one to a repo.

Making a request

Send the key in the Authorization: Bearer header:

curl -X POST https://loopr.life/gql \
-H "Authorization: Bearer loopr_…" \
-H "Content-Type: application/json" \
-d '{"query":"{ listQueuedItems(limit: 3) { results { id title urgency } } }"}'

Browse the full schema at /gql/playground — schema introspection works there, autocomplete included. All queries and mutations accept the same Authorization header.

Common operations

Create an item

Push something into your loop. customSourceName is a free-form label (e.g. zapier, my-script) that shows up as a little uppercase badge on the item so you remember where it came from.

mutation {
createItem(input: {
title: "Review Q2 budget"
customSourceName: "zapier"
urgency: 3
}) {
result { id title urgency }
errors { message }
}
}

List queued items

{
listQueuedItems(limit: 20) {
results {
id
title
urgency
customSourceName
snoozedUntil
}
count
}
}

Equivalent queries exist for snoozed items (listSnoozedItems) and top-of-mind items (listTopOfMindItems).

Complete, snooze, requeue

mutation {
completeItem(id: "...") { result { id state } errors { message } }
}
mutation {
snoozeItem(id: "...", until: "2026-05-01T09:00:00Z") {
result { id snoozedUntil } errors { message }
}
}

unsnoozeItem, requeueItem, setItemUrgency, updateItem, and destroyItem all follow the same shape.

Streams

{ listStreams { results { id title type paused } } }

pauseStream, resumeStream, and deleteStream are available as mutations.

The customSourceName field

The built-in source field (github, gmail, native, …) is fixed by the Stream type that made the item. customSourceName is the escape hatch for API-created items: a string you choose that shows up as a badge on the item so you can tell at a glance which integration it came from. Pick short, lowercase names (zapier, home-assistant, ci-bot). It’s optional — omit it and the item just shows up with no extra badge.

Non-goals

The API does not currently support:

  • OAuth or per-scope permissions. An API key is equivalent to a session.
  • Webhooks out of Loopr (subscribing to item events from an external service). If you need that, let us know.
  • Rate limit headers. Be reasonable; we’ll reach out if a key is noisy.

Gotchas

  • Lost keys are lost. Regenerate and update your callers.
  • “Expires: Never” keys are convenient for long-lived integrations but worth treating with extra care — if the laptop with a never-expiring key goes missing, revoke from Preferences.
  • The GraphQL schema evolves. Fields we add are additive; we’ll announce breaking changes in the changelog. Pinning a client? Query only the fields you need and handle unknown ones gracefully.