LogicSRC standards surface
++ A setup token you paste. A bridge issues it, any app claims it once, and no app + registers, keeps a key or hosts a redirect. +
++ An app that wants to act through a person's accounts has one road today, OAuth, and + that road assumes the app can register with every provider, keep a client secret and + survive a browser round trip. A browser extension can do none of those safely. A shell + script cannot. So those apps ask for a password, or a personal API key pasted into a + settings field, and what they get is the whole account, forever, listed nowhere.{" "} + SimpleFIN solved this for bank + data with something smaller than OAuth. OpenConnection is that door, written down for + anything a bridge holds. +
+
+ Status: 0.1. The first bridge is mynaposter.com,
+ serving the social profile; the first app claiming it is{" "}
+ DefPromo. SimpleFIN is the{" "}
+ finance profile, unchanged.
+
| + {what} + | +{how} | +
+ One request, no credential. The bearer that comes back is the whole credential from + then on, and the bridge can revoke it. +
+{CLAIM}
+
+ SimpleFIN puts Basic credentials in the access URL itself, which a browser's{" "}
+ fetch refuses outright, so a bridge serving browsers answers{" "}
+ bearer. A second claim of the same token is{" "}
+ 403 claimed, and the bridge tells the person, because a token
+ claimed twice was seen by someone it was not meant for.
+
+ Optional for an app, since the claim URL is inside the token. It is how an app links a
+ person to setup and reads what a bridge can do before asking.
+
{DESCRIPTOR}
+ Scopes use the same resource:action vocabulary as OpenAccess.
+| endpoint | +scope | +answers | +
|---|---|---|
+ {endpoint}
+ |
+
+ {scope}
+ |
+ {answers} | +
| + {what} + | +{why} | +
", "message": "…"}`.
+
+## Requests
+
+Every request after the claim goes to a path under `access_url` and carries the token:
+
+```
+GET https://mynaposter.com/openconnection/v1/accounts
+Authorization: Bearer oc_9k2…
+```
+
+Two endpoints exist in every profile:
+
+- **`GET /info`** returns `{ "versions", "profiles", "scopes", "principal", "app", "issued", "expires" }`: what this token is, for whom, and what it may do.
+- **`GET /accounts`** returns `{ "accounts": [ … ], "errlist": [ … ] }`. An account is what the bridge is connected to on the person's behalf: a bank account, a social handle, a calendar. Each is `{ "id", "kind", "name", "org": { "name", "url" }, "url", "updated" }`, and a profile adds keys. `errlist` carries `{ "code", "msg", "account_id" }` for a connection the bridge could not read, so a stale account is reported rather than silently dropped.
+
+And one the app uses to leave:
+
+- **`DELETE /`** revokes the token the request carries. The app forgets it.
+
+The rules, and every one degrades:
+
+1. **The token is the credential.** No request needs anything else, and no request works without it. A missing or unknown token is `401` with `{"error": "unauthorized"}`.
+2. **Revocation is `401 revoked`.** A person revokes an app at the bridge, and the app's next request is refused with `{"error": "revoked"}`. The app forgets the token and links the person to `setup` for a new one. It never retries a revoked token.
+3. **Scopes are enforced by the bridge.** A request outside the token's scopes is `403` with `{"error": "scope", "scope": "posts:create"}`, naming the scope that was missing, so the app can ask the person for exactly that.
+4. **The person's credentials never cross.** A bridge hands an app the result of acting, never the password, cookie or OAuth token behind an account. An app that needs one of those is asking for the wrong thing.
+5. **The bridge keeps the list.** For each person it shows every app holding a token, by the name the app gave at claim, with when it claimed and when it last called, and a revoke control beside each. That list is the reason this is safer than a pasted API key.
+6. **Rate limits are the bridge's.** `429` with `Retry-After`, and the app waits that long.
+7. **Absent is unstated.** A missing key in any response means the bridge did not say, never that the value is empty or false.
+8. **Unknown keys are kept.** A bridge says more than a profile names, and an app passes it through under the bridge's own key.
+
+## The `social` profile
+
+A bridge that holds a person's social accounts and can write in their voice serves these under the access URL. The first bridge serving it is [mynaposter.com](https://mynaposter.com); the first app claiming it is [DefPromo](https://defpromo.com), a browser extension that used to ask for the person's own OpenAI key and a scraper key instead.
+
+An account in this profile is a network the person connected: `{ "id": "bluesky:chovy.bsky.social", "kind": "social", "network": "bluesky", "handle": "chovy.bsky.social", "name": "Chovy", "org": { "name": "Bluesky", "url": "https://bsky.app" }, "url": "https://bsky.app/profile/chovy.bsky.social", "updated": "…" }`.
+
+- **`POST /analyze`** with `{ "url" }`, scope `analyze:create`. The bridge reads the page the way a directory does: the site's [OpenProfile.md](/openprofile) and `llms.txt` first, its HTML only when it must, and answers `{ "name", "description", "audience", "features": [ … ], "tone", "read_from": ["openprofile" | "llms" | "html"] }`. A project starts from what the site says about itself.
+- **`POST /write`** with `{ "kind": "post" | "comment", "network", "count", "project": { "name", "description", "audience", "features", "tone", "url" }, "context": { "title", "content", "url" }, "include_link", "title" }`, scope `write:create`. `context` is the post being replied to when `kind` is `comment`. `network` lets the bridge apply that network's length and house rules. Answers `{ "variations": [ "…" ], "title", "usage": { "input", "output", "cost" } }`. `title` is present when asked for and the network wants one.
+- **`POST /suggest`** with `{ "project" }`, scope `suggest:create`. Answers `{ "subreddits": [ … ], "hashtags": [ … ], "keywords": [ … ], "forums": [ { "name", "url" } ] }`. Where a bridge can read a directory such as [nichedb.dev](https://nichedb.dev), the forums are real places, not guesses.
+- **`POST /activity`** with `{ "network", "kind", "url", "text", "project", "at" }`, scope `activity:write`, and **`GET /activity`**. The app made a post with the person's own hands, in their own browser, and tells the bridge so the person's history, recap and analytics see it. The bridge answers `{ "id" }`.
+- **`POST /posts`** with `{ "network", "text", "title", "url", "when" }`, scope `posts:create`, only when the descriptor says `"posts": true`. The bridge's own poster queues it and answers `{ "id", "status": "queued" }`. A bridge that never holds a social credential never serves this, and says so by leaving `posts` out of its descriptor.
+
+## The `finance` profile
+
+SimpleFIN 1.0 and 2.0, unchanged: `GET /accounts` with `start-date`, `end-date`, `pending`, `account`, `balances-only` and `version`, answering an account set with balances and transactions. A SimpleFIN bridge is an OpenConnection bridge whose descriptor says `"profiles": ["finance"]` and whose claim answers `"auth": "basic"`. Nothing in this document asks it to change.
+
+## Discovery
+
+An app finds a bridge two ways:
+
+1. The person pastes a setup token. The claim URL is inside it. This is the normal way and needs no descriptor.
+2. `/.well-known/openconnection.json` on the bridge's origin, when the app wants to link the person to `setup` or read what the bridge can do before asking.
+
+A descriptor is **verified** when it was fetched from `/.well-known/` on the origin the access URL is on. A claim URL on some other origin than its descriptor is a claim about the bridge by whoever hosts it.
+
+## With OpenAccess
+
+[OpenAccess](/openaccess) is the door for an app that can register: it keeps a key, serves a descriptor, and receives a grant the person can carry between apps. OpenConnection is the door for an app that cannot. They share the scope vocabulary, and a bridge that is also an OpenAccess app may make the claimed token an OpenAccess JWT, so a resource that already verifies those needs no second path. The app claiming it need not know or care.
+
+## What is deliberately absent
+
+**No client registration.** The app is named by what it says at claim, and the person judges that name on the bridge's list. A bridge that wants registered apps runs OpenAccess beside this.
+
+**No redirect, no browser round trip.** The person moves the token by hand. That is the feature.
+
+**No refresh token.** A token lives until it expires or is revoked. When it does, the person pastes a new setup token, which takes as long as the first one did.
+
+**No credentials passed through.** The app acts through the bridge or not at all.
+
+**No posting unless declared.** A bridge says `"posts": true` or it does not post.
+
+## Running one
+
+A bridge needs a table of setup tokens (secret, person, scopes, expiry, claimed at, claimed by), a table of access tokens (person, app name, scopes, issued, last used, revoked at), a page at `setup`, a list page with revoke, and the profile's endpoints. The first one took an afternoon.
+
+## Related standards
+
+- [OpenAccess](/openaccess): the registered door, same scopes.
+- [OpenProfile.md](/openprofile): the `operator` behind a bridge and the `principal` an app acts for.
+- [OpenMCP](/openmcp): a bridge that is also an MCP relay lists itself there; an OpenConnection token works as its bearer.
+- [SimpleFIN](https://www.simplefin.org/protocol.html): the `finance` profile, and the protocol this generalises.
+
+## Version history
+
+| Version | Date | Change |
+|---|---|---|
+| 0.1 | 2026-09-13 | First publication: the descriptor, the setup token, the claim, eight rules, the `social` and `finance` profiles, discovery, with OpenAccess. |
+
+## License
+
+The specification text is CC BY 4.0. Serve it, copy it, extend it.