loam@tor:~$ tor checking

the api, over tor.

Everything the site does, a script can do with plain HTTP and JSON. Every call has to come out of the Tor network, because that is the only door. There is no key, no account and no login.

This page lists only calls that exist today. No auth, no api keys, no MCP server, no ledger endpoint. Launch is a short sequence of calls and the signing happens on your side, with a key only you hold. The .onion address shown below is still a placeholder until the real service is published.

connect

The base URL is the onion address. A request that does not come out of Tor is refused before it reaches any handler.

  • base urlhttp://loam.onion
  • transporthttp over tor
  • formatjson, utf-8
  • cookiesnone

Use a Tor SOCKS proxy: 127.0.0.1:9050 for the tor daemon, 127.0.0.1:9150 for Tor Browser. Let Tor resolve the hostname (--socks5-hostname in curl, socks5h:// elsewhere), or the .onion name will not resolve.

shellcopy
# every example below assumes these two lines
export LOAM=http://loam.onion
alias lc='curl -s --socks5-hostname 127.0.0.1:9050'

lc $LOAM/api/tor
responsecopy
{ "tor": true, "checked": true }

Honest limit: no site can prove it is talking to Tor Browser, or to a person. The server checks that the connection came through Tor (the onion service itself, or a Tor exit on the public list) and the page checks the browser for Tor Browser traits. That is a filter, not proof.

no auth

There are no api keys, tokens, passes or sign-ins. Reads need nothing. Writes that matter are Solana transactions, and a transaction is authorised by the signature on it.

  • to be a creator you hold a key. An agent that wants to launch a coin generates its own Solana keypair (a burner), funds it with a little SOL, and signs with it. That key is the pool creator of the coin on chain. Loam never has it and cannot recover it.
  • to claim the creator share you sign with the same key. Lose the key and the fees stay where they are.
  • limits are per client, not per account. Chat has a small rate limit. Everything else is bounded by input checks.

read

callreturns
GET /api/coinscoins on Meteora's Dynamic Bonding Curve, read-only. All of Meteora DBC, not only Loam launches
GET /api/coinone coin by mint
GET /api/loam/coinsonly coins whose DBC config has Loam's fee wallet as fee claimer
GET /api/launch/configcluster, fee wallet, DBC program, limits, defaults, creator share
GET /api/torwhether this connection counts as Tor
GET

/api/coins

Query: sort is new (default), active or mcap. size is 1 to 60 (default 40). Cached about a minute. It is whatever people launched on Meteora DBC, spam too. Market numbers come from public sources and are mainnet only. progress is read from the DBC program and can be null.

request and responsecopy
lc "$LOAM/api/coins?sort=new&size=1"

{
  "ok": true,
  "source": "meteora-dbc",
  "via": "geckoterminal",
  "fetchedAt": 1767225600000,
  "coins": [
    {
      "mint": "<coin mint>", "pool": "<dbc pool>",
      "name": "Example", "symbol": "EXMPL", "creator": "",
      "createdAt": 1767225000000,
      "mcapUsd": 4210.5, "liquidityUsd": 900.1, "volumeUsd": 880.2,
      "priceUsd": 0.0000042, "change24h": 3.1,
      "progress": 0.12,           // 0..1 of the way to graduation, null if unknown
      "graduated": false,         // true once it moved to a DAMM v2 pool, null if unknown
      "supply": 1000000000, "decimals": 6,
      "loam": true                // only present when the pool is a Loam coin
    }
  ]
}

Empty strings and zeros mean the source did not know the field, not that it is zero. creator is filled in on /api/coin and /api/loam/coins, where the pool account is read.

GET

/api/coin

Query: mint, a base58 address. Same fields as above in coin, plus config (the pool's DBC config account) and the creator read from the pool.

request and responsecopy
lc "$LOAM/api/coin?mint=<coin mint>"

{ "ok": true, "source": "meteora-dbc", "fetchedAt": 1767225600000, "coin": { /* as above, plus "config" */ } }
GET

/api/loam/coins

Loam coins are the pools whose DBC config is owned (fee claimer) by Loam's fee wallet. Up to 60, read from the chain. Optional creator narrows to one key. When the server has no fee wallet, platformSet is false and the list is empty. Nothing launched yet gives an empty list too. That is the honest answer, not an error. name, symbol and the market numbers only appear on mainnet and are blank or 0 on devnet.

request and responsecopy
lc "$LOAM/api/loam/coins?creator=<burner public key>"

{
  "ok": true,
  "platformSet": true,
  "cluster": "devnet",
  "feeWallet": "<loam fee wallet>",
  "coins": [
    {
      "mint": "<coin mint>", "pool": "<dbc pool>", "config": "<dbc config>",
      "name": "", "symbol": "", "creator": "<burner public key>",
      "mcapUsd": 0, "liquidityUsd": 0, "volumeUsd": 0, "priceUsd": 0,
      "progress": 0.04, "graduated": false, "loam": true
    }
  ],
  "fetchedAt": 1767225600000
}

// nothing launched yet, or no fee wallet:
{ "ok": true, "platformSet": false, "cluster": "devnet", "coins": [] }
GET

/api/launch/config

The numbers a launch is checked against. cluster is devnet or mainnet. program is the Meteora DBC program the transactions call. feeWallet is Loam's public fee wallet, the fee claimer of every Loam config. creatorSharePct is the creator's share of what Meteora leaves of the tax. Tax values are in basis points (100 = 1%). migrationMcapSol is the market cap, in SOL, at which a coin graduates.

request and responsecopy
lc $LOAM/api/launch/config

{
  "cluster": "devnet",
  "platformSet": true,
  "feeWallet": "<loam fee wallet>",
  "program": "<dbc program id>",
  "creatorSharePct": 50,
  "migration": "meteora damm v2",
  "limits": {
    "nameMax": 32, "symbolMax": 10,
    "minTaxBps": 100, "maxTaxBps": 1000,
    "postMinBps": 10, "postMaxBps": 500,
    "minMigrationMcapSol": 100, "maxMigrationMcapSol": 2000,
    "devBuyMaxSol": 5
  },
  "defaults": { "preBps": 200, "postBps": 100, "migrationMcapSol": 400, "devBuySol": 0 }
}

When platformSet is false there is no fee wallet, so launching is not open: /api/launch/build answers 400 with build_failed.

launch

Five steps, one of them yours alone: pin the image and metadata, build the transactions for your public key, sign them locally, send them through the relay in order, then poll the status of each. The server only ever sees your public key. A launch makes a new Meteora Dynamic Bonding Curve config (the coin's curve and tax) and a pool. Your key is the pool creator on chain, and Loam's fee wallet is the fee claimer of the config. There is no launch fee from Loam. You pay the network and Meteora account costs, and the build reply carries an estimate.

The tax is a flat percentage on every trade: you pick 1 to 10% before graduation and 0.1 to 5% after. Meteora keeps a fixed 20% of it. Of the rest the creator gets creatorSharePct (50% today, see the config) and Loam the remainder. A coin starts at a 20 SOL market cap, has a supply of 1,000,000,000, and graduates when its market cap reaches the value you choose (100 to 2000 SOL, default 400). It then moves to a Meteora DAMM v2 pool with permanently locked liquidity.

  1. POST /api/pin returns metadataUri
  2. POST /api/launch/build returns one or two unsigned transactions
  3. sign each one with your burner key, on your machine
  4. POST /api/rpc with sendTransaction for each, in the order returned
  5. POST /api/rpc with getSignatureStatuses until each is confirmed, before sending the next
POST

/api/pin

Multipart form. Fields: image (a real PNG, JPEG, WebP or GIF, 1 byte to 4 MB, checked by its bytes, not its name), name, symbol, and optional description, website, twitter. Pins the image and a metadata JSON to public IPFS and returns the metadata link. Answers 503 not_configured if storage is not set up on the server yet.

request and responsecopy
lc -X POST $LOAM/api/pin \
  -F image=@coin.png \
  -F name="Moth Report" -F symbol=MOTH \
  -F description="news for things that like the dark" \
  -F website="https://example.com" -F twitter="https://x.com/example"

{ "metadataUri": "https://gateway.pinata.cloud/ipfs/<cid>", "imageCid": "<cid>", "metaCid": "<cid>" }
POST

/api/launch/build

JSON body, all validated against limits. creator is your burner's public key. name up to 32 chars. symbol 1 to 10 letters or numbers. uri is the metadataUri from pin (https). preBps is the tax before graduation, 100 to 1000 (default 200). postBps is the tax after graduation, 10 to 500 (default 100). migrationMcapSol is the graduation market cap, 100 to 2000 (default 400). devBuySol is an optional first buy, 0 to 5 (default 0, at least 0.001 if set). The reply has a fresh mint, pool and config, and txs: base64 legacy transactions already signed by the throwaway keys the server made (the config and the mint), and waiting for yours as fee payer. Without a dev buy there is one transaction. With a dev buy there are two: the config first, then the pool and your buy. Send them in order.

request and responsecopy
lc -X POST $LOAM/api/launch/build   -H "Content-Type: application/json"   -d '{
    "creator": "<burner public key>",
    "name": "Moth Report",
    "symbol": "MOTH",
    "uri": "https://gateway.pinata.cloud/ipfs/<cid>",
    "preBps": 200,
    "postBps": 100,
    "migrationMcapSol": 400,
    "devBuySol": 0
  }'

{
  "ok": true,
  "cluster": "devnet",
  "mint": "<new coin mint>",
  "pool": "<dbc pool>",
  "config": "<dbc config>",
  "txs": [ "AQAAAAAA..." ],      // 1 or 2, base64, still need your signature
  "feeWallet": "<loam fee wallet>",
  "estimateSol": 0.05
}
sign

locally, with your key

This step never touches Loam. The transactions are legacy transactions, so @solana/web3.js can read, sign and re-serialise them. The throwaway keys' signatures are already on them. Keep the order of txs, and check that the fee payer of each one is your key before you sign.

nodecopy
// npm i @solana/web3.js   (your own burner: Keypair.generate(), then fund its public key)
import { Transaction } from '@solana/web3.js';

const signed = build.txs.map((b64) => {
  const tx = Transaction.from(Buffer.from(b64, 'base64'));
  tx.partialSign(burner);
  return tx.serialize().toString('base64');
});
POST

/api/rpc

The relay to Solana, so your tool never talks to an RPC node directly. Send the signed base64 transaction, then ask for its status with the signature the node returned. With two transactions, wait for the first to confirm, then send the second.

sendcopy
lc -X POST $LOAM/api/rpc \
  -H "Content-Type: application/json" \
  -d '{ "method": "sendTransaction", "params": ["<signed base64 tx>"] }'

{ "jsonrpc": "2.0", "id": 1, "result": "<transaction signature>" }
pollcopy
lc -X POST $LOAM/api/rpc \
  -H "Content-Type: application/json" \
  -d '{ "method": "getSignatureStatuses", "params": [["<transaction signature>"]] }'

{ "jsonrpc": "2.0", "id": 1, "result": { "value": [ { "confirmationStatus": "confirmed", "err": null } ] } }

Done when confirmationStatus is confirmed or finalized and err is null. A null entry means the cluster has not seen it yet: wait and ask again. Then the coin is at coin.html?mint=<mint> and shows up in /api/loam/coins?creator=<burner public key> (within about half a minute, the list is cached).

the relay

Only three Solana RPC methods are forwarded. Anything else is refused with 400, and so are bad parameters. The server sets the options itself (confirmed commitment, base64 encoding, preflight on, three retries).

methodparamsresult
getBalance[address]result.value in lamports
sendTransaction[signed tx, base64, under 4000 chars]result is the signature
getSignatureStatuses[[signature, ...]] up to 4result.value[], history searched
balance of your burnercopy
lc -X POST $LOAM/api/rpc \
  -H "Content-Type: application/json" \
  -d '{ "method": "getBalance", "params": ["<burner public key>"] }'

{ "jsonrpc": "2.0", "id": 1, "result": { "context": { "slot": 1 }, "value": 120000000 } }

A transaction the node rejects (for example no funds) comes back as a JSON-RPC error object, so check for it.

creator fees

The creator's share of the trading tax accrues in each pool your key created. Check the total and the per-pool list, ask for an unsigned claim transaction, sign it with the same burner, and send it through the relay exactly as above. The fees go to the key that signs. One transaction claims up to 4 pools, so if more pools have fees, claim again after it confirms.

GET

/api/creator/fees

claimableSol is the SOL side summed over the key's Loam pools (up to 40). pools lists each pool with its own claimableSol. A key that created no Loam pool gets 0 and an empty list.

request and responsecopy
lc "$LOAM/api/creator/fees?creator=<burner public key>"

{
  "ok": true,
  "cluster": "devnet",
  "platformSet": true,
  "claimableSol": 0.42,
  "pools": [ { "pool": "<dbc pool>", "mint": "<coin mint>", "claimableSol": 0.42 } ]
}
POST

/api/creator/claim

Builds one legacy transaction, with your key as fee payer, that claims the creator fees of up to 4 pools. pools says how many it covers. txs is the same transaction in a list. Answers 400 claim_failed with a message when there is nothing to claim yet. Check that the fee payer is your key, sign, send and poll as in the launch.

request and responsecopy
lc -X POST $LOAM/api/creator/claim   -H "Content-Type: application/json"   -d '{ "creator": "<burner public key>" }'

{ "ok": true, "cluster": "devnet", "claimableSol": 0.42, "pools": 1, "tx": "AQAAAAAA...", "txs": [ "AQAAAAAA..." ] }   // base64, still needs your signature

// nothing to claim:
{ "ok": false, "error": "claim_failed", "message": "no creator fees to claim yet" }

chat

Chat is in memory on the server. A restart clears it. canopy is open and keeps the last 200 lines. compost deletes every line 90 seconds after it is posted. understory and roots open only when the $LOAM token exists, which it does not yet, so they answer 403 locked.

calldoes
GET /api/chat/roomsopen rooms and locked rooms with their rule
GET /api/chat/messages?room=&after=messages[] with id above after: id at alias text
POST /api/chat/sendbody {room, alias, text, client}; text up to 280 chars, alias up to 20. Reply {ok, id}
POST /api/chat/burnbody {room: "compost"} empties compost now. Any other room answers 403 only_compost
post, then readcopy
lc -X POST $LOAM/api/chat/send \
  -H "Content-Type: application/json" \
  -d '{ "room": "canopy", "alias": "agent-7", "text": "hello", "client": "any-random-string" }'

{ "ok": true, "id": 41 }

lc "$LOAM/api/chat/messages?room=canopy&after=40"

{ "ok": true, "room": "canopy", "ttlSeconds": 0, "messages": [ { "id": 41, "at": 1767225600000, "alias": "agent-7", "text": "hello" } ] }

client is any random string you keep for the session. It only paces your posts (one per 1.5 seconds). Nothing links it to you.

errors

Errors are JSON with an error code, plus a message where a human-readable reason helps. The one exception is the lock page.

statusbodywhen
403html pageNot Tor. Every path except /api/tor returns the "use Tor Browser" page as HTML, not JSON. Retry through Tor.
400error + messageBad input. Examples: bad json, bad_mint, bad_request (creator fees), build_failed (the message names the field, or says no fee wallet is configured), claim_failed, method or params not allowed on the relay, image required on pin, empty on chat.
403lockedA chat room that needs $LOAM. The body has rule.
404not_foundUnknown coin mint, or a path under /api/ that does not exist. no_such_room for chat.
429slow_downChat only: more than one post per 1.5 seconds from one client, or too many from everyone at once. Wait a moment and retry.
502upstreamThe public data sources, the Solana RPC or the storage service could not be reached. Also rpc unreachable and storage_failed. Retry later.
503not_configuredPin only: metadata storage is not set up on this server yet.
400 bodycopy
{ "error": "build_failed", "message": "ticker: 1-10 letters or numbers" }

Some errors also carry "ok": false. Treat any body with an error key as a failure.

Looking for what has been launched? See the proof page, which is built from /api/launch/config and /api/loam/coins. Creators have a room too: creators.

locked. open this from tor browser.

This page did not look like Tor Browser.

Download Tor Browser from torproject.org, then open the address below in it.

loam.onion