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.appGeneration asks 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 paysCredentialSection
Your accountA personal token with inference:run, on your server onlyConnect with a token
The signed-in personTheir creator-app session, after they approve generationGenerate 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.

ts
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:

ts
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:

ts
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 runsWhat it holds
A module face, a browser app, a DAW plug-inThe public app id only. Sign-in uses PKCE. Never a secret.
Your own server that signs people inThe 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.