Skip to content

Quickstart

View .md

This walk-through uses a test key. Test mode runs every validation, returns real (sample) output files on a realistic timeline, fires real signed webhooks and never charges you. Nothing here needs a balance.

  1. In the dashboard at api.moan.hot/dashboard, open API keys and create a key in Test mode with the generations:write, generations:read and account:read scopes. The key starts with mh_test_ and is shown once. Store it as an environment variable:

    Terminal window
    export MOANHOT_API_KEY="mh_test_..."

    Check it works:

    Terminal window
    curl https://api.moan.hot/v1/account \
    -H "Authorization: Bearer $MOANHOT_API_KEY"

    The response has "mode": "test" and your limits.

  2. Every effect lists the variants it supports, with a fixed price for each. Take the first effect and its first variant:

    Terminal window
    curl "https://api.moan.hot/v1/effects?type=video&limit=1" \
    -H "Authorization: Bearer $MOANHOT_API_KEY"

    Note the slug and one entry from variants, for example:

    { "variant_key": "5s_640p", "duration": "5s", "resolution": "640p", "audio": false, "prep": false, "price_micros": 100000, "typical_seconds": 95 }
  3. POST /files takes one JPEG, PNG or WebP image up to 20 MB and keeps it 24 hours. You can skip this step and pass a public https:// image URL in the next one instead.

    Terminal window
    curl https://api.moan.hot/v1/files \
    -H "Authorization: Bearer $MOANHOT_API_KEY" \
    -F "file=@photo.jpg"

    The response contains "id": "file_...".

  4. Send the effect slug, the variant, the input and a fresh Idempotency-Key. The key makes the call safe to retry: if the network drops, send the same request with the same key and you get the same generation back instead of a second one.

    Terminal window
    curl https://api.moan.hot/v1/generations \
    -H "Authorization: Bearer $MOANHOT_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: $(uuidgen)" \
    -d '{
    "effect": "EFFECT_SLUG",
    "variant": { "duration": "5s", "resolution": "640p", "audio": false, "prep": false },
    "inputs": { "image": "file_..." },
    "client_reference_id": "order-1042",
    "webhook_url": "https://example.com/webhooks/moanhot"
    }'

    The answer is 202 Accepted with the generation in queued. Every inputs slot the effect lists in inputs[] with required: true must be present; most video effects take one slot called image.

  5. When the output is ready we POST a signed generation.succeeded event to your webhook_url (and to any endpoint registered in the dashboard). A minimal receiver that verifies the signature:

    // npm install express
    import express from 'express';
    import crypto from 'node:crypto';
    const secret = Buffer.from(process.env.MOANHOT_WEBHOOK_SECRET.replace(/^whsec_/, ''), 'base64');
    const app = express();
    app.post('/webhooks/moanhot', express.raw({ type: 'application/json' }), (req, res) => {
    const id = req.header('webhook-id');
    const ts = req.header('webhook-timestamp');
    const sigs = (req.header('webhook-signature') ?? '').split(' ');
    const expected = crypto.createHmac('sha256', secret).update(`${id}.${ts}.${req.body}`).digest('base64');
    const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
    const valid = sigs.some((s) => {
    const sig = s.split(',')[1] ?? '';
    return sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
    });
    if (!fresh || !valid) return res.sendStatus(400);
    const event = JSON.parse(req.body);
    if (event.type === 'generation.succeeded') {
    console.log(event.data.id, event.data.outputs[0].url);
    }
    res.sendStatus(200);
    });
    app.listen(3000);

    The signing secret (whsec_...) for per-request webhook_url deliveries is on the dashboard’s Webhooks page, one per mode. To test locally, expose the port with a tunnel such as cloudflared tunnel --url http://localhost:3000 and use its https URL.

  6. Without a webhook, read the generation until it is terminal. Output URLs are signed, valid for one hour, and re-issued on every read.

    Terminal window
    curl https://api.moan.hot/v1/generations/gen_... \
    -H "Authorization: Bearer $MOANHOT_API_KEY"

    Poll no more than every 5 seconds; typical_seconds on the variant tells you how long to expect.

  1. Wait for account review to finish. Test keys work while you wait; live creates answer 403 account_pending_review until then.
  2. Top up your balance with USDT.
  3. Create a mh_live_ key and swap it in. Nothing else changes: same URLs, same payloads, same webhooks (signed with the live secret).