SDK
Agents
Everything you do to a customer's agent (create it, talk to it, configure it, and clean it up) is a method on maritime.agents.
Provision (get-or-create)
provision is idempotent on externalId: first call creates the agent, later calls return the same one. That makes it safe to run on every sign-in: you never double-create, and you never have to store Maritime's agent id. Use create instead if you want a hard failure when the name already exists.
const agent = await maritime.agents.provision({
externalId: `customer_${user.id}`,
name: `assistant-${user.id}`,
template: 'openclaw', // see /docs/frameworks
instructions: 'You are a helpful assistant.',
idleTtlSeconds: 3600, // 0 = always-on
})Chat
Send a message and wait for the reply. Sleeping agents wake automatically. Pass a conversationId to keep a thread. chat resolves with { response, error? }. It does not throw on a delivery failure (a still-deploying agent, an LLM error), so check error.
const { response, error } = await maritime.agents.chat(agent.id, 'What can you do?')
if (error) throw new Error(error)
// continue a thread:
await maritime.agents.chat(agent.id, 'And after that?', { conversationId: 'thread-1' })Per-customer secrets & config
Give each customer's agent its own credentials and config as env vars. Secrets are encrypted at rest; changes reach a running container after a reload.
await maritime.agents.setEnv(agent.id, 'ACME_API_KEY', user.acmeKey, { secret: true })
await maritime.agents.setEnv(agent.id, 'ACME_REGION', 'us-east', { secret: false })
await maritime.agents.reloadEnv(agent.id) // push into the running container
// read them back (secret values come back masked):
for (const v of await maritime.agents.listEnv(agent.id)) {
console.log(v.key, v.isSecret ? '(secret)' : v.value)
}Lifecycle, logs & teardown
Agents sleep and wake on their own, but you can drive the lifecycle directly. Find an agent by your own id, read its logs, or tear it all down when a customer leaves.
const [agent] = await maritime.agents.list({ externalId: `customer_${user.id}` })
await maritime.agents.logs(agent.id, { limit: 100, level: 'error' })
await maritime.agents.sleep(agent.id) // cheapest resting state
await maritime.agents.start(agent.id) // wake it
await maritime.agents.restart(agent.id)
await maritime.agents.delete(agent.id) // container + volume + networkScheduled wakes (inside a custom agent)
A sleeping micro-VM's timers never fire, so an agent that must act on a schedule (daily digest, follow-up reminders) publishes its schedule and lets Maritime be the alarm clock. These helpers run inside your agent image, not in your backend: they read the credentials Maritime injects into every agent and are silent no-ops anywhere else, so they're safe in local dev. Prefer nextRunAt (the next occurrence as your scheduler computed it); an entry with prompt is delivered to your POST /chat right after the wake. No SDK? Serve GET /schedules returning the same array and Maritime polls it.
import { observeScheduler, pushSchedules } from 'maritime-sdk'
// One line: every add/remove on your scheduler re-publishes the snapshot.
observeScheduler(myScheduler, {
getSnapshot: (s) => s.jobs.map(j => ({ id: j.id, nextRunAt: j.next.toISOString() })),
})
// Or push explicitly. Send the FULL list; [] clears all synced wakes.
await pushSchedules([
{ id: 'digest', cron: '0 9 * * 1-5', tz: 'America/New_York', prompt: 'Send the digest' },
{ id: 'followup', nextRunAt: '2026-08-01T14:00:00Z' },
])Handling errors
Every failure is a typed subclass of MaritimeError. Catch the base to catch them all, or narrow by type.
import { MaritimeConflictError, MaritimePaymentRequiredError } from 'maritime-sdk'
try {
await maritime.agents.create({ name: 'dupe', template: 'openclaw' })
} catch (err) {
if (err instanceof MaritimeConflictError) {
// 409: an agent with that name already exists
} else if (err instanceof MaritimePaymentRequiredError) {
// 402: plan limit reached, or an add-on needs a paid plan
} else {
throw err
}
}The full error hierarchy (MaritimeAuthError, MaritimeNotFoundError, MaritimeRateLimitError, MaritimeConnectionError) is in the API reference.