Connecting a portal
A connection is one login to one utility portal. We store the login encrypted, sign in on a schedule or when you ask, list every account the login can see, and retrieve bills only for the accounts you activate. Accounts are covered on their own page: Accounts.
- Register the connection with
POST /v1/connections. It starts aspending_verification. - The first run verifies the login and lists its accounts. It downloads nothing: its
retrieval.completedhas"dry_run": trueand every account undernew_accounts. It follows the schedule, or you can start it at once withPOST /v1/connections/{connection_id}/runs. - Activate the accounts you want, or send their numbers beforehand with
PUT /v1/expected-accountsand they activate themselves when found. Accounts you do not activate staynever_collected, and their bills are never delivered. - We retrieve each active account around its billing cycle. Every run ends in a
retrieval.completedorretrieval.failedevent, and each new bill arrives as astatement.available.
Endpoints
The connection API shares the base URL https://api.luminetric.com. Every call carries Authorization: Bearer <your connection API token>, a token issued with your account separately from the upload token. Bodies are JSON, at most 64 KB.
| Method and path | What it does |
|---|---|
GET /v1/providers |
The portals you can register, each with its credential_fields (username and password today) |
POST /v1/connections |
Registers a login: 201 for a new connection, 200 with the existing one if you registered this login before |
GET /v1/connections |
Every connection on your account, with its status, as a connections array. Each connection's accounts come from its /accounts endpoint |
GET /v1/connections/{connection_id} |
The connection and its status |
PUT /v1/connections/{connection_id}/credentials |
Replaces the password; the next run re-verifies the login |
POST /v1/connections/{connection_id}/runs |
Runs the connection now (see Triggering a run) |
DELETE /v1/connections/{connection_id} |
Deletes the connection and destroys its stored login: 204 |
GET /v1/connections/{connection_id}/accounts |
The connection and every account behind it |
POST /v1/accounts/{account_id}/activate |
Starts retrieving an account |
POST /v1/accounts/{account_id}/deactivate |
Stops retrieving it. Statements already sent are unaffected |
PUT /v1/expected-accounts |
Account numbers, for one provider, to activate automatically when a run finds them |
Registering a connection
provider is a name from GET /v1/providers. client_reference is your own label, up to 255 characters, returned on the connection.
{
"provider": "Example Electric Cooperative",
"credentials": { "username": "[email protected]", "password": "..." },
"client_reference": "site-114"
}
The answer is the connection. API responses leave out any field that has no value yet, such as last_success_at before the first run.
{
"connection_id": "con_8d62c46f-d186-427c-9610-2711def78c57",
"provider": "Example Electric Cooperative",
"client_reference": "site-114",
"status": "pending_verification",
"customer_action_required": false,
"created_at": "2026-10-13T19:54:44Z",
"mfa": { "delivery": "email", "address": "[email protected]" },
"created": true
}
Registering the same login again (same portal, same username, compared ignoring case) returns the existing connection with 200 and stores the password you sent. It is a way to update a password when you do not have the connection_id to hand. To change the username, register a new connection: PUT .../credentials refuses a different username.
Connection status
customer_action_required on the connection is true exactly when its status waits on you. last_failure_reason names the last failed run's reason code (see retrieval.failed).
status |
Meaning | What to do |
|---|---|---|
pending_verification |
Registered, or credentials replaced, and not yet verified by a run | Nothing; the next run verifies it |
active |
Signed in on the last run that reached the portal | Nothing |
credentials_failed |
The portal rejected the username or password | Send the correct password with PUT .../credentials |
mfa_failed |
The portal asked for a verification code that did not reach us | Check that the code goes to mfa.address, then send the credentials again |
client_assistance_required |
The portal needs the account holder to act, for example to accept new terms | Do that on the portal, then send the credentials again |
disabled |
We have stopped running this connection | Contact us |
While a connection waits on you, we do not run it. Sending the credentials again with PUT .../credentials, even unchanged, returns it to pending_verification and the next run verifies it.
Triggering a run
POST /v1/connections/{connection_id}/runs runs a connection now, outside the schedule. The body is optional. Send nothing, or {"mode": "collect"}, for a regular run. Send {"mode": "discover"} for a discovery run, which lists every account behind the login and downloads nothing; use it to find accounts added since the connection's first run.
{
"request_id": "run_6e6b4590-8c6a-4b3e-bbaf-7b05ab2c588f",
"connection_id": "con_8d62c46f-d186-427c-9610-2711def78c57",
"mode": "collect",
"status": "queued",
"created": true
}
202with"created": true: a run was queued. Itsrequest_idisrun_plus a UUID for a regular run anddsc_plus a UUID for a discovery run you asked for, and the run'sretrieval.completedorretrieval.failedcarries it. It starts as soon as capacity allows.200with"created": false: a run is already pending, and this is that run, whatever mode you asked for.modesays which it is, andstatusisqueued, orin_progressonce it has started.400: the connection waits on you (see Connection status) or is disabled, ormodeis notcollectordiscover. The error says what to do.429: you triggered a run of this connection, of either mode, less than 60 minutes ago. The error says how many minutes to wait. Scheduled runs do not count toward this.
A regular run is the same as a scheduled one: it looks for bills from the last 62 days, and a bill already delivered is not delivered again. On a connection that has never run, any run is the discovery run of step 2: mode says discover, and the request_id keeps the prefix of the mode you asked for.
The schedule
Once your account is live, we run each connection without being asked:
- at once after it is registered, after its credentials are replaced, and after you activate an account (within half an hour);
- daily from 3 days before an active account's next expected bill until that bill arrives or is 20 days late;
- weekly otherwise, to find accounts that appeared or disappeared.
Apart from those first cases, a scheduled run happens at most once a day. No run starts alongside another run of the same connection, and never while the connection waits on you.
Verification codes
Where a portal sends one-time codes by email, have them reach the address in the connection's mfa.address, either by setting the login's email to it or with a forwarding rule. The address is yours alone. Codes by text message or authenticator app are not currently supported, but can be based on demand.
Security
- Credentials are encrypted per connection with a key held in a key-management service, never stored beside the data. They are decrypted only at the moment we sign in, and never written to a log or returned by the API.
- Deleting a connection destroys its stored credentials.
- You confirm, in your agreement with us, that you hold each account holder's consent to store their login and retrieve their bills.
Responses and errors
Errors have the upload endpoint's shape: {"status": "error", "error": "..."}.
| Status | Meaning |
|---|---|
200 / 201 / 202 |
Done. 201: a connection was created. 202: a run was queued |
204 |
Deleted |
400 |
Refused as sent. error says why: a malformed body, a portal not enabled for you, a different username on a credentials update, a connection waiting on you, an unknown run mode |
401 |
No valid bearer token |
404 |
No such connection, account or endpoint. Another client's ids answer the same way |
405 |
That method is not allowed on that path |
413 |
The body is over 64 KB |
429 |
A run was triggered too recently |
500 / 503 |
Our fault or temporarily unavailable; retrying is safe |