networks.md

Networks

Build a platform

Maritime Networks is the network layer for agents and computers. Every agent and computer has three network settings: what it may reach on the internet, where its traffic exits, and which of your private systems it may use. The defaults reach everything from a shared address and nothing private. Change one setting and the platform enforces it on the host, outside the workload, so the workload cannot talk its way around it.

The three settings

Setting Values
internet all reaches everything (the default). list reaches only the entries of a preset plus the workload's own allow list. none reaches nothing on the internet. A deny list of addresses or CIDRs wins over every allow.
exit shared leaves from the account's shared address (the default). fixed gives the workload its own address that stays the same, for allowlists on the other side. country leaves from a residential IP in a chosen country. through leaves through one of your private networks, so the other side sees your office or your server.
access The private resources the workload may reach, by id. A resource is a host and port you published from your own machine or server with maritime connect --allow host:port. Inside the workload the resource answers to its name, such as db.office.private.

Allow entries are domains (api.example.com), wildcards (*.example.com, subdomains at any depth, not the apex), IPv4 addresses, or IPv4 CIDRs. Deny entries are IPv4 addresses or CIDRs. The Maritime platform itself stays reachable in every mode. list with no entries and no preset equals none. A fourth switch, cutOff, stops all traffic at once and restores it when turned off.

Presets

A preset is a named allow and deny list shared by every agent and computer in the account. Two are built in. coding allows package registries (PyPI, npm, crates, Go, Maven, RubyGems, NuGet, pub.dev), source hosting (GitHub), container registries (Docker Hub, GHCR), Linux package mirrors, Nix, and Hugging Face. llm-apis allows the hosted model APIs (OpenAI, Anthropic, Google, OpenRouter, Mistral, Groq, Together, xAI, DeepSeek, Cohere, Fireworks). Your own presets live in Company settings, Policies, the CLI, and the API. Deleting a preset that workloads use is refused and the refusal counts them.

Residential IPs

The country exit sends the workload's traffic out through a residential IP: a home broadband or mobile address in the country you pick, from Maritime's residential provider. Sites that refuse cloud addresses see an ordinary home connection. TCP goes through the exit and names resolve there. UDP is dropped, except DNS.

Field Values
countryCode An ISO code such as US or DE.
state A US state code such as CA. Only with countryCode US.
city A city such as Austin. The provider checks it when you save; a city it does not know is refused with 422.
session sticky (the default) keeps one IP for the workload for up to 24 hours. rotating gets a new IP for every connection.

Each workload has its own session, so two workloads never share a sticky IP. To get a new sticky IP at once, call new-ip; the next connection uses a new address. Changing the state, the city, or the session also starts a new session.

Residential traffic is billed per GB, in and out, at the rate the Exit row shows next to the option (options.country.priceLabel in the API). It counts toward the account's monthly cap (countryDataCapGb in account settings). Past the cap, connections are refused with outcome over_cap until the month ends. Bytes are counted when a connection closes, so a long connection counts when it ends.

With your own proxy (ownProxyUrl in account settings), the country exit goes through it instead. It is not billed, and state, city, and session do not apply.

CLI:

# A residential IP in California that stays the same for the agent
maritime agents update <agent_id> --egress country:US --egress-state CA

# A new residential IP in Austin for every connection
maritime agents update <agent_id> --egress country:US --egress-city Austin --egress-session rotating

# A new sticky IP now
maritime agents network new-ip <agent_id>

TypeScript:

await client.agents.updateNetwork(agentId, {
  exit: { mode: 'country', countryCode: 'US', state: 'CA', session: 'sticky' },
})
await client.agents.newIp(agentId)

Python:

client.agents.update_network(
    agent_id,
    exit={"mode": "country", "countryCode": "US", "state": "CA", "session": "sticky"},
)
client.agents.new_ip(agent_id)

curl:

curl -X PUT https://api.maritime.sh/api/v1/network/workloads/agent/$AGENT_ID \
  -H "Authorization: Bearer $MARITIME_API_KEY" -H 'Content-Type: application/json' \ 
  -d '{"exit": {"mode": "country", "countryCode": "US", "city": "Austin", "session": "rotating"}}'

curl -X POST https://api.maritime.sh/api/v1/network/workloads/agent/$AGENT_ID/new-ip \
  -H "Authorization: Bearer $MARITIME_API_KEY"

Dashboard

Open an agent, then Settings. The Network card shows the three settings as three rows, each one a click to change. The Exit row shows the address the internet sees today. The Test box tries one connection from inside the agent and names what stopped it. View activity lists what the workload connected to, with the outcome. Computers show the same card.

CLI

The same six flags set the network on maritime agents create, maritime agents update, and maritime computers create. Flags not given keep their current value; none clears a list.

Flag Meaning
--internet all|none|list The internet mode. --allow-out implies list.
--allow-out <entry> One allow entry, repeatable: domain, *.domain, IPv4 address, or CIDR.
--deny-out <cidr> One deny entry, repeatable. Deny wins over allow.
--network-policy <preset> A preset by name or id, such as coding or llm-apis.
--egress shared|fixed|country:<CC>|through:<network> Where the traffic exits. country:<CC> is a residential IP.
--egress-state <ST>, --egress-city <city>, --egress-session sticky|rotating Target the residential IP. Need --egress country:<CC>.
--access <resource> One private resource by address or id, repeatable.
# Only package registries, source hosting, and one API of your own
maritime agents create --name worker --template-id codex \
  --internet list --network-policy coding --allow-out api.example.com

# Change a running agent: its own fixed address, and a database on your machine
maritime agents update <agent_id> --egress fixed --access db.office.private

# What is in force, and the address the internet sees
maritime agents network <agent_id>

# Try one connection from inside the agent
maritime agents network test <agent_id> api.example.com:443

# Presets shared by every agent and computer in the account
maritime network presets list
maritime network presets create --name partners --allow api.partner.com --allow '*.partner.com'
maritime network presets delete <preset_id>

maritime agents network test exits non-zero when the destination is not reached, so it works in scripts. A create that asks for a setting the account cannot have is refused with 422 and creates nothing.

API

One object, the same everywhere. Read and change it per workload, send it as network at creation, and manage presets at /api/v1/network/presets. The workload kind is agent, computer, or sandbox; a sandbox takes the shared and country exits only. Every field in an update is optional and replaces what it names; presetId: null clears the preset.

Operation Route
read GET /api/v1/network/workloads/{kind}/{id}
change PUT /api/v1/network/workloads/{kind}/{id} {internet?, exit?, access?, cutOff?}
test POST /api/v1/network/workloads/{kind}/{id}/test {destination}
new IP POST /api/v1/network/workloads/{kind}/{id}/new-ip: a new sticky residential session
at creation POST /api/agents and POST /api/v1/computers with network: {internet?, exit?, access?}
presets GET /api/v1/network/presets, POST /api/v1/network/presets {name, allow, deny}, PUT /api/v1/network/presets/{id} {name?, allow?, deny?}, DELETE /api/v1/network/presets/{id}
private networks GET and POST /api/v1/network/private-networks {name}, PATCH and DELETE /api/v1/network/private-networks/{id}
resources POST /api/v1/network/private-networks/{id}/resources {name, target}, DELETE /api/v1/network/resources/{id}
install link POST /api/v1/network/private-networks/{id}/install {allowInternet?}: a one-time link and the commands that connect a machine
account settings GET and PUT /api/v1/network/settings {cutOff?, allowFixed?, allowCountry?, allowThrough?, patientData?, ownProxyUrl?, countryDataCapGb?}
activity GET /api/v1/network/activity ?networkId, workloadKind, workloadId, limit: connections, newest first

Change:

PUT /api/v1/network/workloads/agent/{agent_id}
{
  "internet": { "mode": "list", "presetId": "coding", "allow": ["api.example.com"], "deny": [] },
  "exit": { "mode": "fixed" },
  "access": ["<resource_id>"]
}

At creation:

POST /api/agents
{
  "name": "worker",
  "templateId": "codex",
  "network": { "internet": { "mode": "list", "presetId": "llm-apis" } }
}

Test:

POST /api/v1/network/workloads/agent/{agent_id}/test
{ "destination": "api.example.com:443" }

{ "destination": "api.example.com:443", "reached": false, "durationMs": null,
  "reason": "Refused by the internet policy." }

The read returns the settings, exitIp (the address the internet sees today, null for country and through), enforcement (pending until the host confirms, then active), metadataUrl, and options: the presets, and for each exit mode whether the account may pick it and the sentence to show when it may not. A 422 carries that same sentence.

SDK

TypeScript

// At creation. A 422 names the reason and creates nothing.
const agent = await maritime.agents.create({
  name: 'worker',
  template: 'codex',
  network: { internet: { mode: 'list', presetId: 'coding', allow: ['api.example.com'] } },
})

// Later. Every field given replaces what it names.
await maritime.agents.updateNetwork(agent.id, { exit: { mode: 'fixed' } })

const net = await maritime.agents.network(agent.id)
net.exitIp          // the address the internet sees today
net.enforcement     // 'active' once the host confirms

const probe = await maritime.agents.testNetwork(agent.id, 'api.example.com:443')
probe.reached       // false
probe.reason        // 'Refused by the internet policy.'

// Presets
const presets = await maritime.network.presets.list()
await maritime.network.presets.create({ name: 'partners', allow: ['api.partner.com'] })

// Computers take the same object
await maritime.computers.create({ externalUserId: 'u_123', network: { internet: { mode: 'none' } } })

Python

# At creation. A 422 names the reason and creates nothing.
agent = client.agents.create(
    "worker",
    template="codex",
    network={"internet": {"mode": "list", "presetId": "coding", "allow": ["api.example.com"]}},
)

# Later. Every argument given replaces what it names.
client.agents.update_network(agent["id"], exit={"mode": "fixed"})

net = client.agents.network(agent["id"])
net["exitIp"]          # the address the internet sees today
net["enforcement"]     # "active" once the host confirms

probe = client.agents.test_network(agent["id"], "api.example.com:443")
probe["reached"]       # False
probe["reason"]        # "Refused by the internet policy."

# Presets
presets = client.network.presets.list()
client.network.presets.create("partners", allow=["api.partner.com"])

# Computers take the same object
client.computers.create(external_user_id="u_123", network={"internet": {"mode": "none"}})

Inside the workload

A program inside the workload reads its own settings at http://100.127.254.1/network. It is the live view: it changes the moment you change a setting. The access entries name what to connect to.

$ curl -s http://100.127.254.1/network
{
  "kind": "agent",
  "workloadId": "...",
  "enforcement": "active",
  "cutOff": false,
  "internet": { "mode": "list", "allow": ["github.com", "api.example.com", "..."], "deny": [] },
  "exit": { "mode": "fixed", "ip": "203.0.113.7", "countryCode": null, "networkName": null },
  "access": [
    { "name": "db.office.private", "address": "100.127.0.3", "port": 5432,
      "target": "localhost:5432", "networkName": "Office" }
  ]
}

Agents also get a snapshot in their environment at boot:

Variable Value
MARITIME_NETWORK_URL http://100.127.254.1/network
MARITIME_INTERNET The internet mode: all, none, or list.
MARITIME_EXIT_IP The exit address, when known at boot.
MARITIME_ACCESS_<NAME> One per private resource: the name in upper case with other characters replaced by _. The value is the address to connect to.

Limits