Skip to content

Tools

Tools are the unit of capability on a Wire container. MCP and REST are two transports: same tools, different connection mechanisms. This page is the source of truth for what each tool does, its parameters, and its default visibility on each transport.

ToolPurposeMCP defaultREST defaultCost
wire_exploreBrowse the container: its shape, and the entries in it✅✅1
wire_searchFuzzy hybrid retrieval over raw content✅✅5
wire_navigateTraverse from an entry: relationships (filterable by type), siblings, source✅✅1
wire_writeSave an entry to the container✅✅0
wire_deleteRemove an entry by ID✅✅0
wire_statusContainer status snapshot❌✅0
wire_files_listList uploaded files❌✅0
wire_files_uploadUpload a file❌✅0
wire_files_deleteDelete a file and its derived entries❌✅0
wire_claimConvert an ephemeral container to a permanent one (ephemeral only)✅✅0
wire_queryRead-only SQL over structured objects (opt-in, off by default)❌❌1

Every tool is independently toggleable per transport from the container’s Tools page in the dashboard. The defaults above are what new containers ship with; users can flip any tool on or off for either transport. Inactive tools are hidden from tools/list on MCP and return 404 NOT_FOUND on REST.

The five standard tools (wire_explore, wire_search, wire_navigate, wire_write, wire_delete) are on for both transports on every new container. wire_query is the one tool that ships off on both transports, so no agent sees it until someone turns it on. See wire_query for what it does and who can enable it.

A container can also have custom tools: tools with your own names, descriptions and inputs, each mapped onto one of the tools above. They appear beside the built-in tools in tools/list and on REST, and cost what their built-in tool costs.

Every tool in tools/list carries MCP annotations, the hints a client uses to decide how careful to be before it runs a call: whether the tool only reads, whether it can delete, and whether it reaches anything outside the container. The built-in tools also declare an outputSchema, a JSON Schema of the result’s structuredContent, so a client knows the shape of the answer before it asks.

TooltitleRead-onlyDestructiveOpen worldoutputSchema requires
wire_exploreExplore container✅❌❌(open)
wire_searchSearch✅❌❌(open)
wire_navigateNavigate from an entry✅❌❌(open)
wire_writeWrite an entry❌❌❌entryId
wire_deleteDelete an entry❌✅❌entryId, deleted
wire_statusContainer status✅❌❌containerId
wire_queryQuery records (SQL)✅❌❌columns, rows, rowCount, truncated

The read tools’ schemas are open: they name the top-level fields each mode returns and allow more, because the result depends on the mode. Every result still carries its data as JSON text in content too, for clients that read only that.

A custom tool inherits its built-in tool’s annotations, and is marked open world when it calls an action on an agent’s server.

The typical agent journey is explore → search → navigate: use wire_explore to discover what’s in the container and fetch specific rows, use wire_search when you have a question and need fuzzy retrieval across raw content to find the right starting point, and use wire_navigate to move around from a content entry you’ve already landed on.

Browse the container: what shape it has, and the entries in it. Two modes, list and get.

Use wire_explore to find your footing — what kinds of thing this container holds, and which entries exist. For fuzzy/semantic retrieval over raw content, use wire_search; to move around from an entry you’ve already landed on, use wire_navigate.

ParameterTypeRequiredDescription
modestringNolist (default) or get
idstringget onlyEntry id
tagstringNolist only: return only entries carrying this tag
limitnumberNolist only: how many entries to return (default 25, max 100)
offsetnumberNolist only: pagination offset into entries

list (default): the container’s shape. entries holds the rows you and your agents wrote — paginated by limit/offset, filterable by tag. Beside it, four groups describe everything else the container holds. Each appears only when the container has that kind, so their presence is itself the answer to “does this container have a knowledge graph / structured objects / uploaded files?”

  • entities[] — the canonical entity rows background analysis built: id, name, entityType, and mentions (how many entries reference this entity).
  • schemas[] — the field profile of each structured object, per source: object, source, mode (declared when the source declared its schema, accreted when Wire folded it from writes), and fields[] of name / type. An object whose records have coordinates also carries spatial, the names of its latitude and longitude fields ({ "lat": "lat", "lng": "lng" }), which is how an agent learns that wire_search near applies to it.
  • composites[] — the shell entry for each uploaded file or chunked write: compositeType, fileName, chunkCount, source. Pass a composite’s id to wire_navigate to reach its chunks.
  • entityTypes[] — the entity type names in this container.

These groups describe the container, not a page of it: each is capped at 50 entries, with hasMore naming any group that was cut, and none of them is affected by limit, offset or tag — those page entries. entryCount is the total live rows in the container, all kinds included.

get: one entry by id, with its relationships (the same shape wire_navigate returns in relationships mode). The _composite entry with _compositeType: 'file' is the system-created entry for every uploaded file. To download the file itself, find its wire:// URI with resources/list and call resources/read on it. See File downloads (Resources).

REST
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"mode": "list", "limit": 25}' \
https://YOUR_ORG_SLUG.api.usewire.io/container/YOUR_CONTAINER_ID/tools/explore
MCP (tools/call)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": { "name": "wire_explore", "arguments": { "mode": "list", "limit": 25 } }
}

list:

{
"entries": [{ "id": "…", "content": "…", "source": "agent:mcp", "tags": ["meeting"], "properties": {}, "createdAt": 1767225600000 }],
"count": 25,
"entryCount": 1432,
"entities": [{ "id": "…", "name": "Acme Corp", "entityType": "Company", "mentions": 7 }],
"schemas": [{ "id": "…", "object": "expenses", "source": "file:expenses.csv", "mode": "accreted", "fields": [{ "name": "amount", "type": "number" }] }],
"composites": [{ "id": "…", "compositeType": "file", "fileName": "runbook.md", "chunkCount": 42, "source": "file:runbook.md" }],
"entityTypes": ["Company", "Person"],
"hasMore": { "entities": true }
}
  • count is how many entries this page returned; entryCount is the container total.
  • entities / schemas / composites / entityTypes / hasMore are omitted entirely when they’d be empty.

get:

  • entry: the entry, with content, source, tags, properties and createdAt.
  • relationships: from, to, and byType — see wire_navigate.

Fuzzy hybrid retrieval over raw content in the container (file chunks, agent writes, and other unstructured entries). Always runs a hybrid pipeline: BM25 + vector embedding + HyDE + LLM rerank.

Use this when you have a question and need semantic search to find relevant passages. To see what the container holds, or to fetch one entry by id, use wire_explore.

Flat 5 credits per call, whichever parameters you send, matchLinked included.

ParameterTypeRequiredDescription
querystringYes, unless near is sentFree-text query
limitnumberNoMax results. Default 10. Clamped to 1–100.
topKnumberNoAlias for limit, same clamp. If you send both, limit wins.
nearobjectNoA point to search around: { "lat": 37.77, "lng": -122.42, "radius_km": 5 }, in decimal degrees. radius_km is optional and, when sent, must be greater than 0. See Searching near a place.
objectstring or string[]NoOnly return records of this object, or of any of these objects (up to 20), matched case-insensitively across every source that writes them. ["place", "note"] returns places and notes. An unknown name matches nothing.
matchLinkedobjectNoLet records linked to a result count toward it, so a place is found through a note about it. Used only with a query, and ignored without one. See Finding a record through what’s linked to it.

limit and topK are the same parameter under two names — topK is the original spelling and keeps working. Values outside 1–100 are clamped rather than rejected, so limit: 0 returns one result and limit: 5000 returns at most 100.

Each match includes _meta.wire.navigate affordance hints telling the agent what a wire_navigate call on this match would find. See the return shape below for the full structure.

near brings location into a search. What it does depends on whether you also send radius_km and a query:

You sendWhat comes back
query and near with radius_kmOnly records whose coordinates fall inside the radius, ranked by relevance to the query. Entries without coordinates are left out.
query and near without radius_kmThe usual relevance ranking, with location as context. Nearness can help a relevant record make the cut, but it never adds a record that doesn’t match the query, and the final order is by relevance, not distance. Entries without coordinates (notes, documents) still appear on the strength of their content.
near and no queryRecords with coordinates, nearest first, inside radius_km when you send one.

A record has coordinates when its object has a coordinate pair: two numeric fields named lat or latitude and lng, lon, long or longitude (any case), with values in range. The container recognizes the pair on its own as records are written or files are uploaded, and numeric text such as "37.77" in those fields is stored as a number. The wire_query fields catalog shows which objects have one.

Every result with coordinates carries distance_km, the great-circle distance from the near point. Results without coordinates have none. Without radius_km, weigh distance_km yourself: it’s there so the agent can decide how much nearness matters for the question. When distance is a hard requirement (“within 2 km”), send radius_km. It’s the only mode that filters by distance.

With radius_km, a radius that covers more records than a single search can rank returns an error instead of a partial answer. Narrow the radius or add object and try again.

MCP (tools/call)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "wire_search",
"arguments": { "query": "quiet coffee shop", "near": { "lat": 37.7749, "lng": -122.4194, "radius_km": 2 }, "object": "places" }
}
}

Finding a record through what’s linked to it

Section titled “Finding a record through what’s linked to it”

matchLinked finds a record through the records linked to it. Ask for “cortado” among saved places, and a place whose own text never says cortado is still found when a note or a visit about it does:

MCP (tools/call)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "wire_search",
"arguments": {
"query": "cortado",
"object": "place",
"near": { "lat": 37.7825, "lng": -122.4074 },
"matchLinked": { "objects": ["note", "visit"] }
}
}
}

The same query also ranks the records of the objects in matchLinked.objects. Each one that matches lifts the records it’s linked to into the results, and a result found this way lists what lifted it in matchedVia.

matchLinked fieldRequiredWhat it does
objectsYesThe objects whose records may lift a result, such as ["note", "visit"]. Up to 20.
typesNoThe edge types to follow, named as wire_navigate’s type names them. Omit it to follow every link an agent wrote with wire_write links, and nothing else: the analysis graphs’ edges are followed only when you name them.
directionNoincoming (default): the linked record points at the result, as a note about a place does. outgoing: the result points at it. both.

The rules:

  • It’s ignored when there is no query. A near-only search has nothing to match linked records against, so matchLinked is ignored and the search runs as a plain distance sort, nearest records first. A custom tool with matchLinked fixed in its arguments can therefore still answer a call that sends only a location.
  • The search’s own filters still apply. A lifted result must be a record of object, and with radius_km it must be inside the radius: a place lifted by a note, but outside the radius, is never returned.
  • The score is the better of the two. A result lifted by a linked record takes that record’s score, and a result that also matches directly keeps the higher of its own score and the lifted one. Nothing is added up, so a place with ten notes doesn’t outrank one with a single better note.
  • One call, one charge. The linked ranking is part of the same wire_search call, at the same 5 credits.
REST
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "pricing concerns"}' \
https://YOUR_ORG_SLUG.api.usewire.io/container/YOUR_CONTAINER_ID/tools/search
MCP (tools/call)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": { "name": "wire_search", "arguments": { "query": "pricing concerns" } }
}

Each match carries:

  • id: the entry uuid. Pass this directly to wire_navigate or wire_delete.
  • score: hybrid relevance score (post-rerank).
  • content: the raw chunk or entry text.
  • distance_km: present only on a near search, and only on results with coordinates. Kilometres from the near point.
  • object: the object the match is a record of, such as "place". Absent when the match isn’t a record of an object.
  • matchedVia: present only when matchLinked found the result through linked records. Up to 5 of them, best first, each with its id, object, the edge type that joins it, its score, its full content, and its declared fields.
  • fields: present only when the match is a record of a declared object. The record’s declared field values, keyed by field name, with the coordinate pair first when the object has one, so an agent can use a place’s name, address, or coordinates without a second call. Up to 4 KB per match: when a record’s values are longer, they are trimmed to fit and the match also carries fields_truncated: true. Fetch the full record by id with wire_explore when you need every value.
  • provenance: typed metadata object holding source, sourceFileId, chunkIndex, totalChunks, ingestedAt, tags, fileName, sectionHeader, chunkSummary, and more. Read the fields directly, no parsing needed.
  • _meta.wire.navigate carries affordance hints for wire_navigate:
    • hasSiblings: count of adjacent chunks in the same source file. Zero means this chunk stands alone.
    • relationshipTypes: map of edge type to count, only non-zero entries. Example: { "elaborates": 3, "corroborates": 1 }. Tells the agent upfront which wire_navigate mode: "relationships" type: ... calls would find something.
  • _meta.wire.duplicates: present only when this result represents more than one instance of the same content (identical after whitespace normalization). Wire keeps every instance as a complete entry with its own timestamp, source, and metadata (repetition is signal, not noise), and collapses them to one representative at search time so results stay clean. The number is the total count of instances in the group, including this representative. Applies to any kind of repetition: agent retries, file re-ingests, recurring connector pulls, repeated log lines.
  • _meta.wire.firstSeenAt: earliest ingestedAt across the instance group. Useful when a recurring source keeps re-ingesting the same content and you want to know how long it’s been in the container.
  • _meta.wire.mostRecentAt: latest ingestedAt across the group. The representative is the most recent instance, so its provenance answers “when did this last happen” directly.
  • _meta.wire.instanceId: id of the group’s instance record. Pass it to wire_navigate (mode: "relationships") to enumerate every instance in the group, each with its own timestamp and source.
  • _meta.wire.versions: up to 5 instances from the group with source and ingestedAt, ordered newest first. The representative result’s own provenance gives the latest instance; this list lets agents see prior sources if they need to.

Use these hints to decide whether a wire_navigate call is worth making, to calibrate range or limit params, and to reason about whether content is fresh or recurring. A high duplicates count is itself information (how many times an alert fired, how often the same page was pulled) that browsing the group via instanceId turns into a timeline.

Traverse from an entry you’ve already landed on, such as a wire_search match or a record you saved. By default it returns the entry, its connections, and the connected records themselves, so “this place and every note about it” is one call. It can also move to adjacent chunks or to the entry’s source document. Flat 1 credit per call.

ParameterTypeRequiredDescription
entryIdstringYesThe entry to traverse from (e.g. a wire_search match id)
modestringNorelationships (default), siblings, or source
rangenumbersiblings onlyChunks before AND after the anchor (default 3, max 50)
typestring or string[]relationships onlyReturn only these edge types, and only the records they connect. Omit for all edges.
directionstringrelationships onlyWhich connected records to return: incoming (records whose edge points at this entry), outgoing (records this entry points at), or both (default). The edge list is not affected.
limitnumberrelationships onlyMost connected records to return, newest first. Default 50, clamped to 1–200.

relationships (default): the entry and its connections, in one call. It returns three things:

  • entry: the entry itself, as wire_explore get returns it, plus fields with its declared field values when it’s a record of an object.
  • linked: the records at the other end of its edges, whole: full content and every declared field, newest first. See Connected records.
  • relationships: the edges themselves. from holds edges where this entry is the source (“this entry contradicts X”), and to holds edges where it’s the target (“X contradicts this entry”). The direction matters: labels are written from the writer’s perspective, so those two are different statements. The same edges come back again under byType, bucketed by label, so “what contradicts this?” is one call whether or not you filtered.

Connections include every kind of edge: links an agent wrote with wire_write links, the provenance and entity graphs’ edges, structural edges, and CRM lookups. type narrows both the edges and the records to the types you name.

siblings: adjacent chunks from the same source document. Positional navigation — given a chunk, returns range chunks before and range chunks after it.

source: the source (composite) entry this one was chunked from. When that source is an uploaded file, its original bytes are available through the file’s wire:// URI, listed by resources/list. See File downloads (Resources).

Each item in linked is one record, however many edges join it to the entry:

FieldWhat it holds
idThe record’s id
typesEvery edge type joining it to the entry, sorted
directionincoming (its edge points at the entry), outgoing (the entry points at it), or both
contentThe record’s full content
objectThe record’s object, when it’s a record of one
fieldsIts declared field values, all of them, when it’s a record of an object
tagsIts tags, when it has any
createdAtWhen it was written, or the occurredAt the write gave, in milliseconds since the epoch
sourceWhat wrote it

The list is newest first, holds only live records the caller can read, and never includes the entry itself. linkedTotal counts every connected record the filters allow, and linkedTruncated is true when limit cut the list. direction and limit shape linked only: relationships always lists every matching edge.

To get a place and every note and visit about it, navigate from the place with the types those records link with, and direction: "incoming", since the notes and visits point at the place:

{ "entryId": "<place id>", "type": ["about", "visited", "held_at"], "direction": "incoming" }

type takes one label or a list of them:

FamilyTypes
Provenancecorroborates, elaborates, supersedes, contradicts
Structuralpart_of (chunk → its document), instance_of (entry → its duplicate group)
Entity graphmentions (entry → a canonical entity), related_via (entity → entity)
CRM mirrorscrm_assoc:{field}, one per lookup field on the mirrored record
Your linksAny type an agent wrote with wire_write links, such as about or attended

A term ending in : matches a whole family by prefix — type: "crm_assoc:" returns every crm_assoc:{field} edge, which is the only way to ask for them without knowing the source system’s field names. Nothing else is a wildcard: contradicts never matches a longer type.

An unrecognized type simply matches nothing. A type that isn’t a string or an array of strings is an error rather than a silently unfiltered result.

wire_search hits carry _meta.wire.navigate.relationshipTypes — a map of edge type to count for that entry, computed from the same grouping — so an agent can see which filtered call would find something before making it.

After a wire_search call returns a chunk id, fetch surrounding context:

REST
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"entryId": "entry_abc123", "mode": "siblings", "range": 3}' \
https://YOUR_ORG_SLUG.api.usewire.io/container/YOUR_CONTAINER_ID/tools/navigate
MCP (tools/call)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": { "name": "wire_navigate", "arguments": { "entryId": "entry_abc123", "mode": "relationships", "type": "contradicts" } }
}

relationships:

{
"relationships": {
"from": [{ "fromId": "entry_abc123", "toId": "entry_def456", "type": "contradicts", "properties": {} }],
"to": [],
"byType": { "contradicts": { "from": [{ "fromId": "entry_abc123", "toId": "entry_def456", "type": "contradicts", "properties": {} }], "to": [] } }
},
"type": ["contradicts"],
"entry": { "id": "entry_abc123", "content": "…", "source": "agent:mcp", "tags": [], "properties": {}, "createdAt": 1767225600000 },
"linked": [
{ "id": "entry_def456", "types": ["contradicts"], "direction": "outgoing", "content": "…", "createdAt": 1767139200000, "source": "agent:mcp" }
],
"linkedTotal": 1,
"linkedTruncated": false
}

type echoes the filter and is absent when you didn’t send one. byType is always present; it’s {} when the entry has no edges, and linked is then [].

siblings returns siblings[], and source returns source — entries in the same shape wire_explore mode: "get" returns.

Save an entry to the container.

ParameterTypeRequiredDescription
contentstring or objectYesThe entry content. Strings are stored as text. Objects are stored as structured data.
idstringNoA stable entry id (a lowercase UUID). Writing again with the same id updates that entry in place instead of adding a new one, which makes re-imports idempotent.
tagsstring[]NoTags for categorization and filtering
metadataobjectNoArbitrary key-value metadata
sourcestringNoWhere this entry came from. Defaults to "agent:mcp" for MCP calls and "webhook:<api-key-name>" for REST calls.
occurredAtstringNoWhen this entry happened, as an ISO 8601 date. Defaults to the write time and must not be in the future. Use it to backfill historical memory (imported notes, past conversations) so the entry’s ingestedAt provenance reflects when the event happened, not when it was imported.
objectstringNoGroup same-shaped records under an object name, e.g. "expenses". See Objects.
fieldsobjectNoThe record’s queryable values, e.g. {"amount": 42.5, "vendor": "Acme"}. Only meaningful with object, and optional when content is already a JSON object, in which case its keys are used.
linksobject[]NoLinks from this entry to other entries in the container, e.g. [{"to": "<place id>", "type": "about"}]. See Linking entries.

Naming an object turns a write into a row rather than a note: its values are stored so they can be filtered and aggregated, and the container keeps a field profile for the object describing what it has seen.

{
"content": "Lunch with the Acme team",
"object": "expenses",
"fields": { "amount": 42.5, "vendor": "Acme", "date": "2026-07-24" }
}

No setup is needed: the first write creates the object and later writes widen it. Reuse the same object name for every record of a kind; a new name per write is capped and makes the profile useless.

The response echoes what the entry was filed under:

FieldMeaning
objectThe object’s canonical name. May differ in case from what you passed, because the first spelling an object gets is the one it keeps.
unknownFieldsPresent only when writing into an object whose schema comes from a connector, listing keys that schema doesn’t define. The write still lands; this is a heads-up, not an error.

Object names must start with a letter and contain only letters, digits, and underscores. entries, relationships, and fields are reserved, as is any name starting with sqlite_ (in any case).

links connects the entry you’re writing to entries already in the container, so a note can point at the place it’s about and a visit at the trip it belongs to. A link is a relationship like the ones analysis finds: wire_navigate returns it from both ends, grouped under its type, and the type filter works on it. It’s there as soon as the write returns, and it doesn’t depend on either analysis graph being on.

{
"content": "Great cortado, slow service on weekends",
"object": "notes",
"fields": { "place_id": "9f1c2a4e-5b6d-4e7f-8a9b-0c1d2e3f4a5b" },
"links": [{ "to": "9f1c2a4e-5b6d-4e7f-8a9b-0c1d2e3f4a5b", "type": "about" }]
}

Each link has three fields:

FieldRequiredMeaning
toYesThe id of an entry in this container. An id that isn’t in the container (including one from another container) is refused, and nothing is written.
typeYesThe link’s name: lowercase letters, digits, _, : and -, starting with a letter, up to 64 characters. Use a : family, such as trip:day, to filter a group of types with one prefix.
propertiesNoDetails about the link, as a JSON object, e.g. {"role": "host"}.

A few rules:

  • The types analysis uses are reserved. corroborates, elaborates, supersedes, contradicts, mentions, related_via, part_of, instance_of, and every crm_assoc: type are refused, so a link can’t be mistaken for something Wire worked out itself. Every link reads back with "source": "agent" in its properties, and source can’t be set by the caller.
  • The links on a write are the entry’s full set. Writing the same id again replaces the links the entry had with the ones you send, and a rewrite without links removes them, the same way it replaces tags and metadata. Relationships from analysis, and links other entries have to this one, stay put.
  • Deleting either entry removes the link. To delete an entry together with the entries linked to it, use withLinked on wire_delete.
  • Up to 32 links per write. Writing links is free, like the write itself.

To read them back, navigate from either end. From the place, {"entryId": "<place id>", "type": "about"} returns every note about it in linked, each with its full text and fields, and the edges under relationships.to. From a note, the same call returns its place in linked and the edge under relationships.from.

REST
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content": "Acme Corp wants to upgrade to Enterprise; needs SSO.", "tags": ["meeting", "acme-corp"]}' \
https://YOUR_ORG_SLUG.api.usewire.io/container/YOUR_CONTAINER_ID/tools/write
MCP (tools/call)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "wire_write",
"arguments": { "content": "Acme Corp wants to upgrade to Enterprise; needs SSO.", "tags": ["meeting", "acme-corp"] }
}
}
{
"entryId": "entry_abc123",
"status": "stored",
"chunks": 1,
"links": 1
}
  • entryId: the entry’s id. Pass it to wire_delete or wire_navigate later, or to another write’s links.
  • status: stored, or processing for a write large enough to be split into chunks. A split write’s links attach to the entry this call returns.
  • chunks: how many entries the content was stored as.
  • object and unknownFields: present for records, as described in Writing records.
  • links: present when you sent links, with how many the entry now has.

Remove an entry by its ID.

ParameterTypeRequiredDescription
entryIdstringYesThe ID of the entry to delete
includeDuplicatesbooleanNoAlso delete every other instance of the same content (the whole group). Default false: only the specified entry.
withLinkedobjectNoAlso delete the entries that link to this one, e.g. {"types": ["about"], "direction": "incoming"}. See Deleting linked entries.

When content exists as multiple instances (see _meta.wire.duplicates on wire_search), each instance is independent: deleting one leaves the others untouched, and the group’s counts update. includeDuplicates: true removes the entire group in one call, and the response adds duplicatesDeleted with the number of additional instances removed.

withLinked deletes an entry together with every entry whose links point at it with one of the types you name. Removing a place with {"types": ["about"], "direction": "incoming"} also removes the notes about it, and leaves a visit linked with visited alone.

{
"entryId": "9f1c2a4e-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
"withLinked": { "types": ["about"], "direction": "incoming" }
}
  • types lists one to ten link types. There’s no “every type” option. The types analysis uses aren’t accepted, and a relationship analysis found is never followed, even when it has the same name.
  • direction is incoming: the entries whose links point at this one.
  • One level only. A reply linked to a deleted note stays; only its link to the note goes.
  • All or nothing past 500. If more than 500 entries would go, the call deletes nothing and says so. Delete some of them first, or name fewer types.

Each linked entry is deleted exactly as a direct wire_delete of it would be, and each shows up in the container’s activity. The response adds linkedDeleted with their ids, so an agent can tell the user what went.

REST
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"entryId": "entry_abc123"}' \
https://YOUR_ORG_SLUG.api.usewire.io/container/YOUR_CONTAINER_ID/tools/delete
MCP (tools/call)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": { "name": "wire_delete", "arguments": { "entryId": "entry_abc123" } }
}
{
"entryId": "entry_abc123",
"deleted": true,
"cascadedChunks": 0
}
  • entryId: echoes the deleted id.
  • deleted: true when the entry existed and is gone.
  • cascadedChunks: how many chunks of a split document went with it.
  • duplicatesDeleted: present with includeDuplicates, when other instances were removed.
  • linkedDeleted: present with withLinked, the ids of the linked entries removed.

Generate a claim link for an ephemeral container. Takes no parameters. Returns a URL that the user can open in a browser to sign up or log in and claim the container permanently.

Claim links are short-lived. Call wire_claim again to generate a new one if needed.

This tool only appears on ephemeral containers (created via instant onboarding). Once a container is claimed and belongs to a permanent organization, wire_claim is no longer listed.

REST
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
https://YOUR_ORG_SLUG.api.usewire.io/container/YOUR_CONTAINER_ID/claim
MCP (tools/call)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": { "name": "wire_claim", "arguments": {} }
}
{
"claim_url": "https://app.usewire.io/onboarding/create-account?claimToken=...",
"expires_in": 3600
}
  • claim_url: open in a browser to sign up or log in and claim the container.
  • expires_in: seconds until the claim link expires. Call wire_claim again to get a fresh one.

Container status snapshot: initialization state, paused flag, counts (tools, entries, entity types), analysis cadence. Useful for readiness checks before kicking off work.

Default visibility: REST on, MCP off. Invoke over REST as GET /container/:id/status.

Takes no parameters.

REST
curl -H "x-api-key: YOUR_API_KEY" \
https://YOUR_ORG_SLUG.api.usewire.io/container/YOUR_CONTAINER_ID/status
{
"containerId": "abc123",
"containerName": "My Container",
"is_ephemeral": false,
"initialized": true,
"initializationStatus": "complete",
"paused": false,
"ready": true,
"counts": {
"tools": 9,
"composites": 28,
"entityTypes": 17,
"entries": { "total": 7381, "eligible": 7102, "analyzed": 7044, "pending": 58 }
},
"analysis": { "inProgress": false, "cadence": "automatic" }
}
  • ready: true when the container is initialized, complete, and not paused. Use this as the gate before kicking off work.
  • initializationStatus: one of pending, analyzing, generating_tools, complete.
  • counts.entries.pending: eligible entries awaiting their first analysis pass. 0 means everything has been analyzed at least once.
  • analysis.cadence: automatic or manual per the container’s setting.

List files uploaded to this container with their processing status.

Default visibility: REST on, MCP off. Invoke over REST as GET /container/:id/files.

Takes no parameters.

REST
curl -H "x-api-key: YOUR_API_KEY" \
https://YOUR_ORG_SLUG.api.usewire.io/container/YOUR_CONTAINER_ID/files
[
{
"id": "file_abc123",
"name": "document.pdf",
"size": 245760,
"mimeType": "application/pdf",
"uploadedAt": "2026-03-30T12:00:00Z",
"processingStatus": "complete",
"chunkCount": 42
}
]

Array of file records. Pass id to wire_files_delete or to the wire:// URI scheme via MCP resources to download the original bytes.

processingStatus is pending while the file is queued, processing during extraction, complete once its entries exist, skipped for a download-only file type, and failed if extraction gave up. chunkCount is null until processing finishes.

Over REST this array arrives as the data field of the standard envelope: {"success": true, "data": [ ... ]}. See the REST API reference.

Upload a file to this container. Wire processes it automatically into entries (chunks, extracted entities, embeddings) after upload.

Default visibility: REST on, MCP off. REST is the path built for real files: invoke as POST /container/:id/files with a multipart body containing a file field, and it carries up to 25 MB per file. An owner can also turn the tool on for MCP, where an agent passes the file’s text or bytes as ordinary tool arguments instead. That path is capped at 1 MB, since tool arguments are not a file transfer channel, so anything larger has to go over REST. Files between 25 MB and 50 MB (the platform storage cap) upload from the dashboard. See Supported File Types for accepted formats.

The request must carry a Content-Length header — the endpoint refuses a body whose size it cannot check up front with 411 LENGTH_REQUIRED. curl -F, fetch with a FormData body, and every mainstream HTTP client set it for you.

Over REST:

ParameterTypeRequiredDescription
filemultipart fileYesThe file to upload. Filename + content type are read from the part headers.

Over MCP (once enabled):

ParameterTypeRequiredDescription
namestringYesFile name including extension. The extension drives how the file is processed.
contentstringOne ofUTF-8 text content
contentBase64stringOne ofBase64-encoded bytes, for anything not plain text
mimeTypestringNoContent type, if you want to state it explicitly

Pass either content or contentBase64, not both, and keep the decoded size under 1 MB.

REST
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@customers.csv" \
https://YOUR_ORG_SLUG.api.usewire.io/container/YOUR_CONTAINER_ID/files
{
"id": "file_abc123",
"name": "customers.csv",
"size": 245760,
"uploadedAt": "2026-03-30T12:00:00Z",
"processingStatus": "pending"
}
  • id: the new file’s id. Pass to wire_files_delete or to wire:// resource URIs.
  • processingStatus: pending immediately after upload, or skipped when the file type is stored for download only. Background workflows extract entries; check wire_status or wire_files_list to watch progress.

Over REST the response is 201 Created and this object arrives as the data field of the standard envelope. The content type Wire records is the one it detects from the file’s own bytes, not the one the multipart part declares.

Called over MCP, the tool returns the new file’s id, its name, and a status of either processing or stored. stored means the file type is download-only and no entries will be created from it.

Delete a file and all entries derived from it.

Default visibility: REST on, MCP off. Invoke over REST as DELETE /container/:id/files/:fileId.

ParameterTypeRequiredDescription
fileIdstringYesThe file ID returned by wire_files_list.
REST
curl -X DELETE \
-H "x-api-key: YOUR_API_KEY" \
https://YOUR_ORG_SLUG.api.usewire.io/container/YOUR_CONTAINER_ID/files/file_abc123
{
"message": "File deleted",
"fileId": "file_abc123",
"entriesDeleted": 42
}

entriesDeleted is how many entries derived from the file were removed with it. Deleting a file that isn’t there returns 404 NOT_FOUND, and a file cannot be deleted while it is still being processed.

Read-only SQL over the container’s structured objects, for the answers retrieval cannot give: sums, averages, counts, grouping, exact filters across many records at once, distances, and the dates a repeating record falls on.

The tool reaches objects only. Raw content (file chunks, prose entries), the knowledge graph, and container internals are not queryable here; those stay behind wire_explore, wire_search, and wire_navigate.

Flat 1 credit per call.

ParameterTypeRequiredDescription
sqlstringYesA single read-only SQL statement (SELECT, or WITH … SELECT)
paramsarrayNoValues for the statement’s ? or ?1, ?2… placeholders, in order. Strings, numbers, booleans, or null; up to 64 of them.

Pass values through params rather than writing them into sql. A bound value is only ever compared as a value, so text that happens to look like SQL cannot change the statement, and the statement stays the same from one call to the next.

Two kinds of table exist, and nothing else is reachable.

fields is the catalog. One row per object, source, and field, so an agent can learn the shape of the container before writing a real query. Start every session here:

SELECT * FROM fields
ColumnMeaning
objectObject name, which is also the table name
sourceWhere the records came from (a file, a connector, an agent write)
nameField name, which is also the column name
typeinteger, number, boolean, date, text, json, or mixed when values disagree
presentHow many of the profiled records actually carry this field
seenHow many records were profiled for this object and source
geolat or lng when this field is half of the object’s coordinate pair, otherwise NULL

No rows back means the container has no objects yet, and there is nothing to query. present versus seen is worth reading before filtering: a field that appears in 4 of 12 records will silently drop rows from a WHERE clause.

SELECT object, name, geo FROM fields WHERE geo IS NOT NULL lists every object that has coordinates, and which columns hold them.

One table per object. Its columns are that object’s profiled field names, plus two the container always adds:

  • _entry_id: the underlying entry id, so a row in an aggregate can be traced back with wire_navigate.
  • _source: the source the record came from.

They carry the underscore prefix because real source data collides: a CSV routinely has its own id and source columns. The same object name written under two sources becomes one table whose columns are the union of both profiles, with _source telling the rows apart; a column missing from one of the profiles reads NULL for those rows.

  • One statement per call. A second statement, or one hidden after a comment, is refused.
  • Reads only. The statement must begin with SELECT or WITH. Anything that writes, changes schema, or reaches outside the container is refused, including PRAGMA and its table-valued forms.
  • Only declared tables. FROM and JOIN may name the catalog, an object table, or a CTE the statement defines itself. Any other name comes back as an error telling the agent to run SELECT * FROM fields.
  • CTE names must be new. A WITH clause can’t reuse the name fields or the name of an object table, in any case. Such a statement is refused with an error asking for a different name.
  • json_each and json_tree are the only table-valued functions callable. They expand JSON the statement already has.
  • Results are capped at 200 rows or roughly 256 KB of serialized rows, whichever comes first. Aggregate in SQL rather than paging raw rows.
  • Statements are bounded. A statement may be up to 100,000 characters long and may run for up to 30 seconds. It also has limits on how large a single value can be and how much memory it can use. A statement that goes over one of these limits fails and doesn’t return partial rows.

A refused or invalid statement returns the reason as an error, including the database’s own message for things like an unknown column, which is usually enough for an agent to correct itself and retry.

A statement that runs out of time is different: it isn’t treated as an error in the statement. The call answers 503 on REST, or JSON-RPC error -32001 on MCP, with a Retry-After of a few seconds. Retrying often succeeds, because the data the first attempt read is now warm. If it keeps timing out, aggregate more narrowly or filter on indexed columns such as coordinates.

wire_distance_km(lat1, lng1, lat2, lng2) returns the great-circle distance between two points in kilometres, with coordinates in decimal degrees. It works on any object whose coordinate columns are numeric: check that type is integer or number in fields.

For “everything within a radius”, put a bounding box on the coordinate columns first and then filter on the exact distance. The box is what lets the container answer from its coordinate index instead of reading every record, and the distance filter trims the box’s corners:

SELECT name, wire_distance_km(?1, ?2, lat, lng) AS km
FROM places
WHERE lat BETWEEN ?1 - ?3 / 111.0 AND ?1 + ?3 / 111.0
AND lng BETWEEN ?2 - ?3 / (111.0 * cos(radians(?1)))
AND ?2 + ?3 / (111.0 * cos(radians(?1)))
AND wire_distance_km(?1, ?2, lat, lng) <= ?3
ORDER BY km

with "params": [37.7749, -122.4194, 5] for a 5 km radius around a point. Use the object’s own column names from fields. When the circle reaches a pole or crosses the ±180° meridian, drop the two lng bounds and keep the lat ones; the distance filter still gives the exact answer.

Records whose coordinates are joined from elsewhere work the same way. A visits object with a place_id column can be filtered by distance with a join to places.

Three functions answer “does this repeating record happen in this window, and when”: store the repeat as a recurrence rule next to the record’s first start, and the container works out the occurrences. Any object can use them. A record doesn’t need to be an event, only to have a rule and a start.

FunctionReturns
wire_rrule_occurs(rrule, dtstart, from, to)1 if an occurrence starts at or after from and before to, otherwise 0. Use it in WHERE.
wire_rrule_between(rrule, dtstart, from, to[, limit])The occurrence starts in that window as a JSON array, earliest first. limit defaults to 50 and is capped at 366.
wire_rrule_next(rrule, dtstart, after)The first occurrence start after after, or NULL when the rule has ended.

rrule is a recurrence rule in the iCalendar (RFC 5545) format, the same one calendar apps export. The RRULE: prefix is optional. dtstart is the first occurrence.

Repeatsrrule
Every weekdayFREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
Every TuesdayFREQ=WEEKLY;BYDAY=TU
First Friday of the monthFREQ=MONTHLY;BYDAY=1FR
Last Friday of the monthFREQ=MONTHLY;BYDAY=-1FR
Last day of the monthFREQ=MONTHLY;BYMONTHDAY=-1
Every other ThursdayFREQ=WEEKLY;INTERVAL=2;BYDAY=TH
Every Tuesday until the end of the yearFREQ=WEEKLY;BYDAY=TU;UNTIL=20261231
Ten times, dailyFREQ=DAILY;COUNT=10

The supported parts are FREQ (DAILY, WEEKLY, MONTHLY, or YEARLY), INTERVAL, COUNT, UNTIL, BYDAY, BYMONTHDAY, BYMONTH, BYSETPOS, and WKST. UNTIL is inclusive, and a date on its own includes that whole day. Put dtstart on a day the rule produces: it counts as an occurrence only when it matches the rule.

Times are local, with no time zone. Write dtstart, from, to, and after as the wall-clock time where the record happens: 2026-09-29T17:30, 2026-09-29T17:30:00, or a date alone for midnight. Because no zone is involved, daylight saving never shifts anything: a 17:30 rule is at 17:30 on every occurrence. A time with Z or an offset such as +02:00 returns NULL rather than being guessed at. Occurrences come back in the same shape as dtstart.

A bad row returns NULL and the query carries on. An invalid or unsupported rule, such as an hourly one, or a malformed time gives NULL for that row. The rest of the query still runs. Each call also has a fixed amount of work it will do, and a rule that can’t be answered within it (one that never matches across a very wide window, for example) returns NULL as well. It never returns a partial list.

To find what’s on in a window, match one-off records on their start and recurring ones with wire_rrule_occurs, and list the concrete starts with wire_rrule_between:

SELECT title, starts_at,
CASE WHEN rrule IS NULL THEN json_array(starts_at)
ELSE wire_rrule_between(rrule, starts_at, ?1, ?2) END AS occurrences
FROM events
WHERE (rrule IS NULL AND starts_at >= ?1 AND starts_at < ?2)
OR wire_rrule_occurs(rrule, starts_at, ?1, ?2) = 1
ORDER BY title

with "params": ["2026-10-03", "2026-10-04"] for everything on Saturday 3 October. A weekday happy hour doesn’t appear, and a Saturday market does. Store a duration or an end time next to the first start, and the agent can work out each occurrence’s end from its start.

For one row per occurrence, expand the array with json_each:

SELECT e.title, o.value AS starts
FROM events e, json_each(wire_rrule_between(e.rrule, e.starts_at, ?1, ?2)) o
ORDER BY starts

The functions combine with everything else in a statement: a join to places with the distance filter above answers “what’s on near here this weekend” in one call.

REST
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT vendor, sum(amount) AS total FROM expenses GROUP BY vendor ORDER BY total DESC"}' \
https://YOUR_ORG_SLUG.api.usewire.io/container/YOUR_CONTAINER_ID/tools/query
MCP (tools/call)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "wire_query",
"arguments": { "sql": "SELECT vendor, sum(amount) AS total FROM expenses GROUP BY vendor ORDER BY total DESC" }
}
}
{
"columns": ["vendor", "total"],
"rows": [["Acme", 412.5], ["Globex", 96]],
"rowCount": 2,
"truncated": false
}
  • columns: column names in result order, taken from the statement’s own projection.
  • rows: one array per row, values positioned to match columns. An absent value is null. A binary value (from randomblob, unhex and similar) is returned as {"blob_base64": "…"}.
  • rowCount: number of rows in rows.
  • truncated: true when the row cap or the size cap cut the result short. Rewrite the query to aggregate rather than asking for the next page.

Choosing between explore, search, and query

Section titled “Choosing between explore, search, and query”
  • wire_explore for classified canonical entries: what entity types exist, list them, get one by id, filter on a field, keyword-search within a type.
  • wire_search when the question is in natural language and the answer is somewhere in raw content.
  • wire_query when the answer is a number or a breakdown over records: totals, averages, counts, group-by, or a precise predicate applied across everything at once. wire_explore’s filter mode returns matching rows; wire_query computes over them.
  • Near a place: wire_search with near when the question is in words (“a quiet coffee shop near here”), wire_query with wire_distance_km when it is a computation (“how many visits within 10 km of the office last month”).
  • On a date: wire_query with wire_rrule_occurs and wire_rrule_between when records repeat (“what’s on Saturday”, “when is the next book club”). Search can find a recurring record, but it can’t tell whether that record happens on a given day.

Every tool can be toggled independently per transport from the container’s Tools page in the dashboard. Three controls govern visibility:

  • Active / Inactive: global kill switch. Inactive tools are hidden everywhere.
  • MCP pill: when active, controls whether the tool appears in MCP tools/list and is callable via tools/call.
  • REST pill: when active, controls whether the underlying REST endpoint serves requests. A disabled REST tool returns 404 NOT_FOUND.

The defaults in the tool reference table at the top of this page are what new containers ship with. User toggles in the dashboard always win over the defaults and are preserved across container wakes.

Visibility is per container and applies to everyone who connects. To limit one agent to a subset of tools instead, limit its API key. A custom tool runs its built-in tool even when that built-in tool is off, so you can turn wire_search off and leave a custom tool built on it available.

Toggling a tool is an admin-level action. It takes an organization owner or admin who also has admin permission on the container itself (the creator has it automatically, otherwise it comes from a grant). Organization members cannot toggle tools. That is also the gate on switching wire_query on. See Roles & Permissions.

On a container a connected agent manages, that agent sets every tool’s visibility, built-in tools included, and the toggles are locked until you uninstall it. The Tools page says “Managed by” the agent.

A public container is readable by any signed-in Wire user. Connecting still takes a sign-in, which the MCP client prompts for, and Wire then grants the visitor read-only access.

A public visitor sees the read tools: wire_explore, wire_search, and wire_navigate, plus wire_query and wire_status where the owner has switched them on. Write and claim tools (wire_write, wire_delete, wire_claim) are hidden from them. Members of the owning organization, and anyone holding a grant on the container, see the full set their permission allows.

wire_query is read-only, so it follows the same rule. Leave it off if the container is public and its object data is not meant to be aggregated by anyone who connects.