Generation from your own server
Submit generation and analysis jobs from a backend or app you host yourself: a server client with a personal token, or generation on a signed-in person's own credits.
Simpler on Maru. A Maru module doesn't need a server or a token to generate.
face.appGenerationasks the host, Maru shows the consent page, and each person's own credits pay. See Add generation to an instrument. It's free to start, and you never handle keys.
Use this page when your own backend or app submits jobs. There are two ways, depending on whose account pays:
| Who pays | Credential | Section |
|---|---|---|
| Your account | A personal token with inference:run, on your server only | Connect with a token |
| The signed-in person | Their creator-app session, after they approve generation | Generate on the person's credits |
The jobs, tasks and results are the same either way; the jobs reference covers HTTP, CLI and MCP forms, audio inputs and account samples.
Connect with a token
Use maruaudio/api from your app server with an existing personal token that
has inference:run. Keep the token in your secret manager. After maru login,
the CLI uses its saved session; the MCP connector uses its approved grant.
import { createMaruClient } from "maruaudio/api"
const maru = createMaruClient({
auth: { apiKey: process.env.MARU_TOKEN! },
})
// Persist this key before submitting, so a lost response is recoverable.
const requestKey = crypto.randomUUID()
const handle = await maru.audio.jobs.submit({
schema: "maru-audio-job/v1",
requestKey,
task: {
type: "generate_audio",
prompt: "a soft wooden percussion hit, dry recording",
audioType: "one-shot",
seconds: 2,
seeds: [1042],
},
persistence: { mode: "artifact_only" },
})
const result = await maru.audio.jobs.wait(handle.id)
if (result.handle.status !== "done") {
throw new Error(result.handle.error?.message ?? result.handle.status)
}
const take = result.artifacts.find(artifact => artifact.role === "audio_variant")
if (!take) throw new Error("No audio take was returned")
const { bytes } = await maru.audio.artifacts.download({
jobId: handle.id,
artifactId: take.artifactId,
})
// Hand these verified encoded bytes to your existing audio/resource loader.
A raw job creates private artifacts. It does not add a sample to Generated Sounds. The client verifies the downloaded byte length and SHA-256 against the artifact receipt. Browser-compatible client code returns bytes; only the Node CLI writes local files.
Inspect await maru.audio.capabilities() before offering an action. Availability
requires an implemented executor and a live compatible worker. A catalog entry
alone does not mean a model is deployed. A server without a suitable worker
refuses the submission before creating a job or charging credits.
jobs.wait polls until done, error or cancelled and returns that terminal
state. Use its onUpdate option for interface updates. Cancel an explicit user
request with jobs.cancel(id). Use jobs.events(id, lastSequence) to replay
persisted events after reconnecting; keep the returned sequence with the job.
If submission loses its response, call jobs.lookup(requestKey) or repeat the
same submission with the same key. Both recover the accepted job. Changing the
input under that key is refused. A new key means a new musical request.
Generate on the person's credits
A creator app that runs outside Maru uses the generation SDK's hosted
connector with an app credential: an authenticated fetch and your client id.
The connector adds the app header to every request. With a creator app OAuth
session, createCreatorAppGenerationConnector builds that credential from the
session's request function:
import { createCreatorAppGenerationConnector } from "@maruaudio/gen-sdk/service/hosted"
const cloud = createCreatorAppGenerationConnector({ config, request: session.request })
// equivalent to credentials: { kind: "app", fetch, clientId }
Here config contains your registered { issuer, clientId }, and session
is the signed-in creator app session from createCreatorAppSession in
maruaudio/api. Request the person's inference:run consent during sign-in.
This connector uses the app-bound account gateway rather than the personal
token API example above. In the source workspace, collect its audio like this:
import { createAudioGenerationService, collectGenerationJob } from "@maruaudio/gen-sdk/service"
await cloud.ready
const service = createAudioGenerationService({ connectors: [cloud] })
try {
const result = await collectGenerationJob(service.submit(cloud.id, {
prompt: "a soft wooden percussion hit",
audioType: "one-shot",
seconds: 2,
model: "auto",
seeds: [1042],
}))
// result.items contains indexed PCM audio. Check result.itemErrors and
// result.terminal before handing it to your app's existing sample loader.
console.log(result.items, result.itemErrors, result.terminal)
} finally {
service.dispose()
}
An app owns playback, storage and the choice of when to install the new sample. No Node filesystem helper is needed for this browser-compatible service.
Keys and secrets
| Where your code runs | What it holds |
|---|---|
| A module face, a browser app, a DAW plug-in | The public app id only. Sign-in uses PKCE. Never a secret. |
| Your own server that signs people in | The app id, optionally a client secret from Creator apps |
A client secret is optional and only for an app whose token exchange runs on
its own server. Creator apps shows it once and keeps only a digest; pass it as
clientSecret to a server-mode createCreatorAppSession. Maru refuses a
secret sent from a browser. A module-only app cannot have one.