Android app from a spec
Write what the app should do — up to 1,000 characters — and receive it built. Built and smoke-tested on one device profile. You get the Android App Bundle that Play requires, an apk you can install on a phone today, and the full source. An agent can write a specification and cannot ship an artifact: no build machine, no signing, no account, nobody accountable if it does not run. That is the gap this closes.
POST https://api.bottrunk.com/s/app-1000
Call it
# once, on the machine your agent runs on npx bottrunk-mcp wallet # prints the address; send it 0.3 ALGO, then USDC claude mcp add bottrunk -- npx -y bottrunk-mcp # then ask your agent for it — the tool is `bottrunk_app_1000`
# Ask. You get a 402 with the payment requirements.
curl https://api.bottrunk.com/s/app-1000 \
-H "Content-Type: application/json" \
-d '{"brief":"A tip calculator for waiters in Honduras. One screen: bill amount, tip percent slider, number of people, and the split per person in lempiras. Remembers the last tip percent. No accounts, works offline.","package_name":"com.yourcompany.tips","contact_email":"buyer@example.com"}'
# Pay and retry. Any x402 client does this for you.
curl https://api.bottrunk.com/s/app-1000 \
-H "PAYMENT-SIGNATURE: <signed USDC transfer>" \
-H "Content-Type: application/json" \
-d '{"brief":"A tip calculator for waiters in Honduras. One screen: bill amount, tip percent slider, number of people, and the split per person in lempiras. Remembers the last tip percent. No accounts, works offline.","package_name":"com.yourcompany.tips","contact_email":"buyer@example.com"}'
# pip install "x402-avm[avm,requests]==2.0.2"
import base64, os
from algosdk import encoding, mnemonic
from x402 import x402ClientSync
from x402.http import x402HTTPClientSync
from x402.http.clients.requests import x402_requests
from x402.mechanisms.avm import ALGORAND_MAINNET_CAIP2
from x402.mechanisms.avm.exact import ExactAvmScheme
class MnemonicSigner: # signs the payment with your agent's 25-word mnemonic
def __init__(self, words):
self._sk = mnemonic.to_private_key(words)
self.address = encoding.encode_address(base64.b64decode(self._sk)[32:])
def sign_transactions(self, unsigned_txns, indexes_to_sign):
return [base64.b64decode(encoding.msgpack_encode(
encoding.msgpack_decode(base64.b64encode(t).decode()).sign(self._sk)))
if i in indexes_to_sign else None
for i, t in enumerate(unsigned_txns)]
# A wallet holding 0.3 ALGO, opted in to USDC, funded with the USDC it may spend.
signer = MnemonicSigner(os.environ["ALGORAND_MNEMONIC"])
client = x402ClientSync().register(ALGORAND_MAINNET_CAIP2,
ExactAvmScheme(signer, algod_url="https://mainnet-api.algonode.cloud"))
with x402_requests(client) as session: # answers the 402, pays, retries
r = session.post("https://api.bottrunk.com/s/app-1000", json={"brief": "A tip calculator for waiters in Honduras. One screen: bill amount, tip percent slider, number of people, and the split per person in lempiras. Remembers the last tip percent. No accounts, works offline.", "package_name": "com.yourcompany.tips", "contact_email": "buyer@example.com"})
print(r.json())
receipt = x402HTTPClientSync(client).get_payment_settle_response(r.headers.get)
print(receipt.transaction) # the settlement on-chain
// npm install @x402-avm/fetch @x402-avm/avm @x402-avm/core algosdk
import algosdk from "algosdk";
import { x402Client } from "@x402-avm/core/client";
import { ExactAvmScheme } from "@x402-avm/avm/exact/client";
import { toClientAvmSigner } from "@x402-avm/avm";
import { wrapFetchWithPayment } from "@x402-avm/fetch";
// A wallet holding 0.3 ALGO, opted in to USDC, funded with the USDC it may spend.
const { sk } = algosdk.mnemonicToSecretKey(process.env.ALGORAND_MNEMONIC!);
const signer = toClientAvmSigner(Buffer.from(sk).toString("base64"));
const client = new x402Client().register("algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=",
new ExactAvmScheme(signer, { algodUrl: "https://mainnet-api.algonode.cloud" })); // default is TestNet
const fetchWithPay = wrapFetchWithPayment(fetch, client);
const r = await fetchWithPay("https://api.bottrunk.com/s/app-1000", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({"brief":"A tip calculator for waiters in Honduras. One screen: bill amount, tip percent slider, number of people, and the split per person in lempiras. Remembers the last tip percent. No accounts, works offline.","package_name":"com.yourcompany.tips","contact_email":"buyer@example.com"}),
});
console.log(await r.json());
How it behaves
The decisions this service makes on your behalf, and the ones that end in a refusal. A 4xx is never settled, so a refusal costs you nothing.
| What you get | An Android App Bundle (.aab) and an installable .apk, the full source and build instructions. Yours outright, no licence back to us. |
| Signing | The bundle is built with an upload key whose keystore is handed over with the source. With Play App Signing the final key lives on your Play account, so nothing we hold can lock you out of your own app. |
| What is not included | Play Store publication — it needs your developer account and an identity check that cannot be done for a wallet — plus backend hosting and ongoing support. Those are separate conversations. |
| The length is the scope | Everything the brief does not say is out of scope by construction. That is what keeps a fixed price honest in both directions. |
| What we will not build | Malware, an app impersonating a real company or bank, anything that harvests credentials, or a scraper whose purpose is to break another service's terms. The brief is read before any work starts, and anything in that list is refunded in full rather than built. |
| If it cannot be built from your description | The payment is returned in full. A brief under 200 characters is refused with 422 before you are charged at all. |
| Size and time | Pages over 2 MB are refused with page too large. Connect times out at 5 s, the whole fetch at 20 s, and at most 3 redirects are followed. |
| Who we look like | Requests go out as BotTrunk/0.1 (+https://bottrunk.com/docs). Sites that block unknown agents will block this one. |
| Private addresses | Hostnames that resolve to private, loopback or link-local space are refused with 422 before any request is made. Do not point this at localhost or an intranet. |
| Method | POST only. A GET on the same path returns this page. |
Who does the work
A person, not a script. The payment settles on-chain first; the job then enters our queue and is done within the stated window. You get an order id immediately and can poll it until the proof comes back — a bank receipt reference, photos, or the finished text. If we cannot deliver, the USDC is returned to the paying wallet and the refund transaction is recorded on the order.
Input
| brief | string | What the app does, who uses it, what each screen shows. 200–1,000 characters. |
| package_name | string | Android package name. |
| contact_email | string | Optional: emailed when the build is ready. |
Output
| order_id | string | Token to poll at /orders/{order_id}. |
| status | string | pending until built, then delivered. |
| eta | string | Fulfilment promise. |
| result | object | Links to the bundle, the apk and the source. |