Interfaces

REST reference

Every endpoint of the core API. Paths are relative to this site: it forwards /api and /v1 to the core, so a browser session and a script use exactly the same URLs.

Account

Sign-up, sign-in, password and profile. Served by Better Auth.

POST/api/auth/sign-up/emailOpen

Creates an account and sends the confirmation e-mail.

Body
email, password (at least 10 characters), name
Answers
the new user and a session
Limit
5 per hour and IP address

POST/api/auth/sign-in/emailOpen

Signs in with an e-mail address.

Body
email, password
Answers
a session; the token is also in the set-auth-token header for callers without cookies
Limit
5 per minute and IP address

POST/api/auth/sign-in/usernameOpen

Signs in with a username instead of an e-mail address.

Body
username, password
Answers
a session
Limit
5 per minute and IP address

POST/api/auth/sign-outPerson

Ends the current session.

Answers
{ success: true }

POST/api/auth/update-userPerson

Changes the profile: display name, username and whether Deeds may show the name.

Body
any of name, username, defaultVisibility
Answers
the updated user

POST/api/auth/request-password-resetOpen

Sends a password reset link. Answers the same way whether or not the address exists.

Body
email, redirectTo
Answers
{ status: true }

POST/api/auth/reset-passwordOpen

Sets a new password and ends all other sessions of that account.

Body
token from the e-mail link, newPassword
Answers
{ status: true }

GET/api/auth/verify-emailOpen

Confirms an e-mail address from the link in the message.

Query
?token the token from the e-mail link (required)
Answers
a redirect back to the website

Public

Everything anyone may read: rules, money, catalog, Deeds with receipts, statistics, map.

GET/v1/public/rulesOpen

The earning rules that are active right now.

Answers
{ rules: [...] } with reward, unit, daily cap, the minimum trust level and audience; invited-only rules add invitedCount (never names)

GET/v1/public/changelogOpen

Every rule version with its reason, plus every budget deposit. Nothing is ever changed in place.

Answers
{ ruleVersions: [...], budgetDeposits: [...] }

GET/v1/public/budgetOpen

Where the money behind the Drops is: deposited, not yet issued, in wallets, reserved, donated.

Answers
amounts in milli-units as decimal strings

GET/v1/public/catalogOpen

The redemption catalog by category, with current prices and pool state.

Answers
{ categories: [...], providers: [...] }

GET/v1/public/catalog/:slugOpen

One project with its complete price history.

Path
:slug project slug, e.g. ecologi-tree
Answers
the project, its provider, pool and priceHistory

GET/v1/public/deedsOpen

All settled payouts. Contributors who chose anonymity have name: null.

Answers
{ deeds: [...] } newest first

GET/v1/public/deeds/:idOpen

One Deed with its proof: the snapshot frozen at payout time, the ledger posting and a fresh check of the receipt hash.

Path
:id payout id (UUID)
Answers
the Deed plus receiptCheck: matches, differs or missing

GET/v1/public/payouts/:id/receiptOpen

The receipt file of a payout, so anyone can hash it themselves.

Path
:id payout id (UUID)
Answers
the file as it was uploaded
Comes back as
application/pdf, image/png, image/jpeg or image/webp

GET/v1/public/impactOpen

The headline totals: donated and queued Drops, Deeds, contributors.

Answers
one object with four numbers

GET/v1/public/statsOpen

What the Deeds financed per project, with CO₂e grouped by period, impact type and how well the figure is backed. Living units are counted from the day they were paid for, so the totals grow on their own.

Answers
{ since, deeds, contributors, financed: [...], co2e: { groups, dropsWithoutFactor } }; each group carries accumulated and perYear

GET/v1/public/mapOpen

Activity per country for the world map. Never finer than a country.

Query
?days window of 1 to 90 days, 7 by default
Answers
{ days, countries: [{ country, activities, people }] }

GET/v1/public/footprintOpen

The server's estimated CO₂e and water use as reported by the hosting provider, the website's traffic, and what the Deeds dealt with in the same days. They stand side by side; nothing is offset or subtracted.

Query
?period day, week, month (default) or year: a rolling window ending on the last day the provider has figures for
Answers
{ period, since, window: { from, to, days, bucket }, server: { co2eGrams, waterLitres, daysWithData }, visits: { bytes, responses, daysWithData, since }, buckets: [{ start, days, co2eGrams, waterLitres, bytes, uptake }], deeds: [{ period, impactType, basis, units, amount, sourceUrls }] }; window and server are null before the first figures arrive, visits before the website reported traffic; uptake is the estimated daily uptake of living units, never subtracted from anything

Your account

Your own data, redemptions, devices and your right to export or delete everything.

GET/v1/mePerson

The signed-in person with their Drops and Sparks balance.

Answers
{ user, balances }

GET/v1/me/activitiesPerson

Your own reported activities and what they earned.

Answers
{ activities: [...] }

POST/v1/me/activitiesPerson

Reports an activity yourself. Trust level self_reported, so most rules will not pay for it.

Body
source, type, quantity, unit, startedAt, endedAt, optional externalId and zone
Answers
the activity and the reward decision (Status 201)

GET/v1/me/statsPerson

Your own share: what your Drops financed and the CO₂e estimate behind it, counted from the day each Deed was paid.

Answers
the same shape as /v1/public/stats, limited to your redemptions

GET/v1/me/invitationsPerson

The rules you are invited to. Rules with audience invited pay only invited people; being invited also lets you confirm your device's link code.

Answers
{ invitations: [{ ruleId, key, title, source, translations, createdAt }] }

GET/v1/me/redemptionsPerson

Your redemptions with their status.

Answers
{ redemptions: [...] }

POST/v1/me/redemptionsPerson

Turns Drops into a queued donation; the Drops are reserved at today's price.

Body
projectId plus quantity (unit projects) or drops (free amount), optional visibility
Answers
the redemption (Status 201)

POST/v1/me/redemptions/:id/cancelPerson

Takes a redemption back while it is still queued and returns the Drops.

Path
:id redemption id (UUID)
Answers
the cancelled redemption

GET/v1/me/clientsPerson

Your connected apps and devices.

Answers
{ clients: [...] } without key hashes

POST/v1/me/clientsPerson

Creates an API key for an app. The key is shown once and only its hash is stored. Only admins may do this for now (403 clients_owner_only otherwise); a key made by anyone else counts as a third-party app.

Body
name, optional kind (first_party for admins), optional signingPublicKey (Ed25519, base64)
Answers
the client and apiKey (Status 201)
Limit
10 per hour and account

DELETE/v1/me/clients/:idPerson

Revokes a device: its key stops working immediately.

Path
:id client id (UUID)
Answers
nothing (Status 204)

POST/v1/me/link/:code/approvePerson

Approves a device. It may then report activities for you, nothing else. Only admins may do this for now (403 clients_owner_only).

Path
:code the code shown on the device
Answers
nothing (Status 204)

POST/v1/me/link/:code/denyPerson

Refuses a link request.

Path
:code the code shown on the device
Answers
nothing (Status 204)

GET/v1/me/exportPerson

Your data as one JSON file (GDPR art. 15 and 20). Contains no password hash, token or key hash.

Answers
a download with profile, devices, activities, rewards, redemptions and postings
Comes back as
application/json

POST/v1/me/deletePerson

Deletes your account by anonymising it: the ledger and the Deeds stay, everything that points at you goes. Queued redemptions are donated anonymously, the rest of the Drops goes back to the budget.

Body
password
Answers
nothing (Status 204)
Limit
10 per hour and account

Apps and devices

The machine side: reporting activities with an API key.

POST/v1/client/activitiesApp or device

Reports one activity. externalId makes it idempotent, so a retry never pays twice. With an Ed25519 signature in X-Elpia-Signature the activity counts as verified, otherwise as device.

Body
source, type, quantity, unit, startedAt, endedAt, externalId, optional zone, energyWhMeasured, energyWhEstimated
Answers
the activity and the reward decision (Status 201)
Limit
600 per minute and API key

GET/v1/client/meApp or device

What this key reported and earned (last 24 hours and in total), the latest reward decision and the account's balances. Nothing about other apps.

Answers
{ client, last24h, total, lastRewardStatus, balances }, amounts in milli units
Limit
600 per minute and API key

DELETE/v1/client/meApp or device

Revokes this key, e.g. when someone logs out on the device. It stops working immediately and cannot be restored.

Answers
nothing (Status 204)
Limit
600 per minute and API key

Owner

Rules, budget, catalog, payouts and the integrity audit.

GET/v1/admin/auditOwner

Checks the books: balanced postings, no unexpected negative balances, redemptions matching payouts, receipts matching their hash.

Answers
the report; 409 instead of 200 when there are findings

GET/v1/admin/rulesOwner

All rule versions, including the disabled ones.

Answers
{ versions: [...] }

POST/v1/admin/rulesOwner

Creates a rule with its first version.

Body
key and a version object with reward, unit, caps, minimum trust and a public reason
Answers
the rule and its version (Status 201)

POST/v1/admin/rules/:id/versionsOwner

Adds a new version of a rule. The old one stays readable; nothing is ever rewritten.

Path
:id rule id (UUID)
Body
the new version with a public reason
Answers
the new version (Status 201)

GET/v1/admin/rules/:id/invitationsOwner

Who is invited to a rule that only pays invited people.

Path
:id rule id (UUID)
Answers
{ invitations: [{ userId, username, email, createdAt }] }

POST/v1/admin/rules/:id/invitationsOwner

Invites a person to a rule. Inviting someone twice changes nothing; an unknown person gives 404 user_not_found.

Path
:id rule id (UUID)
Body
user: username or e-mail address
Answers
the invited person (Status 201)

DELETE/v1/admin/rules/:id/invitations/:userIdOwner

Removes an invitation. New activities stop earning; rewards already booked stay.

Path
:id rule id (UUID)
Path
:userId the invited person's id
Answers
nothing (Status 204)

POST/v1/admin/budget/depositsOwner

Pays money into the budget. Drops can only be issued from it.

Body
amountCents, optional memo
Answers
the ledger transaction (Status 201)

GET/v1/admin/catalogOwner

The catalog including inactive providers and projects.

Answers
like the public catalog, but complete

POST/v1/admin/categoriesOwner

Creates a category.

Body
slug, name, optional description, translations, sortOrder
Answers
the category (Status 201)

PATCH/v1/admin/categories/:idOwner

Changes a category. Only the fields that are sent change.

Path
:id category id (UUID)
Body
any subset of the fields
Answers
the category

POST/v1/admin/providersOwner

Creates a provider with its minimum payment and fee note.

Body
slug, name, website, optional minPayout, feeNote, translations
Answers
the provider (Status 201)

PATCH/v1/admin/providers/:idOwner

Changes a provider.

Path
:id provider id (UUID)
Body
any subset of the fields
Answers
the provider

POST/v1/admin/projectsOwner

Creates a project. impactType is required, so no card is ever ambiguous about what the money does.

Body
slug, providerId, categoryId, title, kind, impactType, optional unitName, co2e, translations
Answers
the project (Status 201)

PATCH/v1/admin/projects/:idOwner

Changes a project. kind is fixed because redemptions depend on it.

Path
:id project id (UUID)
Body
any subset of the fields; co2e: null removes the CO₂e figure
Answers
the project

POST/v1/admin/projects/:id/pricesOwner

Adds a price. Prices are append-only and queued redemptions keep the price they were made at.

Path
:id project id (UUID)
Body
price, currency, fxToEur, public reason
Answers
the new price version (Status 201)

GET/v1/admin/queueOwner

The pools waiting for a payout and whether they have reached the provider's minimum.

Answers
{ pools: [...] }

GET/v1/admin/budget/forecastOwner

What paying out today would cost, how much the owner would have to add, and how long the budget lasts at the pace of the last 30 days.

Answers
balances, pools and the runway

POST/v1/admin/payoutsOwner

Records a real payment with its receipt and turns the settled redemptions into a Deed. Payouts cannot be changed afterwards.

Body
projectId, redemptionIds, amountPaid, currency, paidAt, optional comment, and the receipt file
Sent as
multipart/form-data, at most 12 MB
Answers
the payout with its snapshot and receipt hash (Status 201)

POST/v1/admin/payouts/:id/reviewsOwner

Records what the provider delivered after the payment. Append-only: a correction never hides the earlier entry.

Path
:id payout id (UUID)
Body
status (confirmed, needs_clarification, corrected), note, evidenceUrl (required for confirmed)
Answers
the review entry (Status 201)

Operations

What a container orchestrator asks before it sends traffic.

GET/healthOpen

Checks the process and its database. Answers 503 when the database is unreachable, so a broken instance is taken out of rotation.

Answers
{ status, database }

POST/v1/ops/trafficInternal

The website reports how many compressed bytes and responses it sent per UTC day since its last report; the API adds them up. Only totals: no address, cookie or page, so no visit can be told apart.

Body
{ days: [{ day, bytes, responses }] }; days older than 30 days or in the future are skipped
Answers
{ stored }, the number of days added