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.
Tool reference
Section titled “Tool reference”| Tool | Purpose | MCP default | REST default | Cost |
|---|---|---|---|---|
wire_explore | Browse the container: its shape, and the entries in it | ✅ | ✅ | 1 |
wire_search | Fuzzy hybrid retrieval over raw content | ✅ | ✅ | 5 |
wire_navigate | Traverse from an entry: relationships (filterable by type), siblings, source | ✅ | ✅ | 1 |
wire_write | Save an entry to the container | ✅ | ✅ | 0 |
wire_delete | Remove an entry by ID | ✅ | ✅ | 0 |
wire_status | Container status snapshot | ❌ | ✅ | 0 |
wire_files_list | List uploaded files | ❌ | ✅ | 0 |
wire_files_upload | Upload a file | ❌ | ✅ | 0 |
wire_files_delete | Delete a file and its derived entries | ❌ | ✅ | 0 |
wire_claim | Convert an ephemeral container to a permanent one (ephemeral only) | ✅ | ✅ | 0 |
wire_query | Read-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.
Annotations and output schemas
Section titled “Annotations and output schemas”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.
| Tool | title | Read-only | Destructive | Open world | outputSchema requires |
|---|---|---|---|---|---|
wire_explore | Explore container | ✅ | ❌ | ❌ | (open) |
wire_search | Search | ✅ | ❌ | ❌ | (open) |
wire_navigate | Navigate from an entry | ✅ | ❌ | ❌ | (open) |
wire_write | Write an entry | ❌ | ❌ | ❌ | entryId |
wire_delete | Delete an entry | ❌ | ✅ | ❌ | entryId, deleted |
wire_status | Container status | ✅ | ❌ | ❌ | containerId |
wire_query | Query 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.
wire_explore
Section titled “wire_explore”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.
| Parameter | Type | Required | Description |
|---|---|---|---|
mode | string | No | list (default) or get |
id | string | get only | Entry id |
tag | string | No | list only: return only entries carrying this tag |
limit | number | No | list only: how many entries to return (default 25, max 100) |
offset | number | No | list 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, andmentions(how many entries reference this entity).schemas[]— the field profile of each structured object, per source:object,source,mode(declaredwhen the source declared its schema,accretedwhen Wire folded it from writes), andfields[]ofname/type. An object whose records have coordinates also carriesspatial, the names of its latitude and longitude fields ({ "lat": "lat", "lng": "lng" }), which is how an agent learns thatwire_searchnearapplies to it.composites[]— the shell entry for each uploaded file or chunked write:compositeType,fileName,chunkCount,source. Pass a composite’sidtowire_navigateto 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).
Example call
Section titled “Example call”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{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "wire_explore", "arguments": { "mode": "list", "limit": 25 } }}Return shape
Section titled “Return shape”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 }}countis how many entries this page returned;entryCountis the container total.entities/schemas/composites/entityTypes/hasMoreare omitted entirely when they’d be empty.
get:
entry: the entry, withcontent,source,tags,propertiesandcreatedAt.relationships:from,to, andbyType— seewire_navigate.
wire_search
Section titled “wire_search”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.
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | Yes, unless near is sent | Free-text query |
limit | number | No | Max results. Default 10. Clamped to 1–100. |
topK | number | No | Alias for limit, same clamp. If you send both, limit wins. |
near | object | No | A 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. |
object | string or string[] | No | Only 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. |
matchLinked | object | No | Let 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.
Searching near a place
Section titled “Searching near a place”near brings location into a search. What it does depends on whether you also send radius_km and a query:
| You send | What comes back |
|---|---|
query and near with radius_km | Only records whose coordinates fall inside the radius, ranked by relevance to the query. Entries without coordinates are left out. |
query and near without radius_km | The 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 query | Records 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.
{ "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:
{ "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 field | Required | What it does |
|---|---|---|
objects | Yes | The objects whose records may lift a result, such as ["note", "visit"]. Up to 20. |
types | No | The 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. |
direction | No | incoming (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, somatchLinkedis ignored and the search runs as a plain distance sort, nearest records first. A custom tool withmatchLinkedfixed 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 withradius_kmit 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_searchcall, at the same 5 credits.
Example call
Section titled “Example call”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{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "wire_search", "arguments": { "query": "pricing concerns" } }}Return shape
Section titled “Return shape”Each match carries:
id: the entry uuid. Pass this directly towire_navigateorwire_delete.score: hybrid relevance score (post-rerank).content: the raw chunk or entry text.distance_km: present only on anearsearch, and only on results with coordinates. Kilometres from thenearpoint.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 whenmatchLinkedfound the result through linked records. Up to 5 of them, best first, each with itsid,object, the edgetypethat joins it, itsscore, its fullcontent, and its declaredfields.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 carriesfields_truncated: true. Fetch the full record byidwithwire_explorewhen you need every value.provenance: typed metadata object holdingsource,sourceFileId,chunkIndex,totalChunks,ingestedAt,tags,fileName,sectionHeader,chunkSummary, and more. Read the fields directly, no parsing needed._meta.wire.navigatecarries affordance hints forwire_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 whichwire_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: earliestingestedAtacross 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: latestingestedAtacross 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 towire_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 withsourceandingestedAt, 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.
wire_navigate
Section titled “wire_navigate”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.
| Parameter | Type | Required | Description |
|---|---|---|---|
entryId | string | Yes | The entry to traverse from (e.g. a wire_search match id) |
mode | string | No | relationships (default), siblings, or source |
range | number | siblings only | Chunks before AND after the anchor (default 3, max 50) |
type | string or string[] | relationships only | Return only these edge types, and only the records they connect. Omit for all edges. |
direction | string | relationships only | Which 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. |
limit | number | relationships only | Most 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, aswire_exploregetreturns it, plusfieldswith its declared field values when it’s a record of an object.linked: the records at the other end of its edges, whole: fullcontentand every declared field, newest first. See Connected records.relationships: the edges themselves.fromholds edges where this entry is the source (“this entrycontradictsX”), andtoholds edges where it’s the target (“Xcontradictsthis entry”). The direction matters: labels are written from the writer’s perspective, so those two are different statements. The same edges come back again underbyType, 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).
Connected records
Section titled “Connected records”Each item in linked is one record, however many edges join it to the entry:
| Field | What it holds |
|---|---|
id | The record’s id |
types | Every edge type joining it to the entry, sorted |
direction | incoming (its edge points at the entry), outgoing (the entry points at it), or both |
content | The record’s full content |
object | The record’s object, when it’s a record of one |
fields | Its declared field values, all of them, when it’s a record of an object |
tags | Its tags, when it has any |
createdAt | When it was written, or the occurredAt the write gave, in milliseconds since the epoch |
source | What 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" }Filtering by edge type
Section titled “Filtering by edge type”type takes one label or a list of them:
| Family | Types |
|---|---|
| Provenance | corroborates, elaborates, supersedes, contradicts |
| Structural | part_of (chunk → its document), instance_of (entry → its duplicate group) |
| Entity graph | mentions (entry → a canonical entity), related_via (entity → entity) |
| CRM mirrors | crm_assoc:{field}, one per lookup field on the mirrored record |
| Your links | Any 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.
Example call
Section titled “Example call”After a wire_search call returns a chunk id, fetch surrounding context:
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{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "wire_navigate", "arguments": { "entryId": "entry_abc123", "mode": "relationships", "type": "contradicts" } }}Return shape
Section titled “Return shape”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.
wire_write
Section titled “wire_write”Save an entry to the container.
| Parameter | Type | Required | Description |
|---|---|---|---|
content | string or object | Yes | The entry content. Strings are stored as text. Objects are stored as structured data. |
id | string | No | A 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. |
tags | string[] | No | Tags for categorization and filtering |
metadata | object | No | Arbitrary key-value metadata |
source | string | No | Where this entry came from. Defaults to "agent:mcp" for MCP calls and "webhook:<api-key-name>" for REST calls. |
occurredAt | string | No | When 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. |
object | string | No | Group same-shaped records under an object name, e.g. "expenses". See Objects. |
fields | object | No | The 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. |
links | object[] | No | Links from this entry to other entries in the container, e.g. [{"to": "<place id>", "type": "about"}]. See Linking entries. |
Writing records
Section titled “Writing records”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:
| Field | Meaning |
|---|---|
object | The object’s canonical name. May differ in case from what you passed, because the first spelling an object gets is the one it keeps. |
unknownFields | Present 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).
Linking entries
Section titled “Linking entries”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:
| Field | Required | Meaning |
|---|---|---|
to | Yes | The 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. |
type | Yes | The 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. |
properties | No | Details 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 everycrm_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 itsproperties, andsourcecan’t be set by the caller. - The links on a write are the entry’s full set. Writing the same
idagain replaces the links the entry had with the ones you send, and a rewrite withoutlinksremoves 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
withLinkedonwire_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.
Example call
Section titled “Example call”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{ "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"] } }}Return shape
Section titled “Return shape”{ "entryId": "entry_abc123", "status": "stored", "chunks": 1, "links": 1}entryId: the entry’s id. Pass it towire_deleteorwire_navigatelater, or to another write’slinks.status:stored, orprocessingfor 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.objectandunknownFields: present for records, as described in Writing records.links: present when you sentlinks, with how many the entry now has.
wire_delete
Section titled “wire_delete”Remove an entry by its ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
entryId | string | Yes | The ID of the entry to delete |
includeDuplicates | boolean | No | Also delete every other instance of the same content (the whole group). Default false: only the specified entry. |
withLinked | object | No | Also 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.
Deleting linked entries
Section titled “Deleting linked entries”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" }}typeslists 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.directionisincoming: 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.
Example call
Section titled “Example call”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{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "wire_delete", "arguments": { "entryId": "entry_abc123" } }}Return shape
Section titled “Return shape”{ "entryId": "entry_abc123", "deleted": true, "cascadedChunks": 0}entryId: echoes the deleted id.deleted:truewhen the entry existed and is gone.cascadedChunks: how many chunks of a split document went with it.duplicatesDeleted: present withincludeDuplicates, when other instances were removed.linkedDeleted: present withwithLinked, the ids of the linked entries removed.
wire_claim
Section titled “wire_claim”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.
Example call
Section titled “Example call”curl -X POST \ -H "x-api-key: YOUR_API_KEY" \ https://YOUR_ORG_SLUG.api.usewire.io/container/YOUR_CONTAINER_ID/claim{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "wire_claim", "arguments": {} }}Return shape
Section titled “Return shape”{ "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. Callwire_claimagain to get a fresh one.
wire_status
Section titled “wire_status”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.
Example call
Section titled “Example call”curl -H "x-api-key: YOUR_API_KEY" \ https://YOUR_ORG_SLUG.api.usewire.io/container/YOUR_CONTAINER_ID/statusReturn shape
Section titled “Return shape”{ "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:truewhen the container is initialized, complete, and not paused. Use this as the gate before kicking off work.initializationStatus: one ofpending,analyzing,generating_tools,complete.counts.entries.pending: eligible entries awaiting their first analysis pass.0means everything has been analyzed at least once.analysis.cadence:automaticormanualper the container’s setting.
wire_files_list
Section titled “wire_files_list”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.
Example call
Section titled “Example call”curl -H "x-api-key: YOUR_API_KEY" \ https://YOUR_ORG_SLUG.api.usewire.io/container/YOUR_CONTAINER_ID/filesReturn shape
Section titled “Return shape”[ { "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.
wire_files_upload
Section titled “wire_files_upload”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:
| Parameter | Type | Required | Description |
|---|---|---|---|
file | multipart file | Yes | The file to upload. Filename + content type are read from the part headers. |
Over MCP (once enabled):
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | File name including extension. The extension drives how the file is processed. |
content | string | One of | UTF-8 text content |
contentBase64 | string | One of | Base64-encoded bytes, for anything not plain text |
mimeType | string | No | Content type, if you want to state it explicitly |
Pass either content or contentBase64, not both, and keep the decoded size under 1 MB.
Example call
Section titled “Example call”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/filesReturn shape
Section titled “Return shape”{ "id": "file_abc123", "name": "customers.csv", "size": 245760, "uploadedAt": "2026-03-30T12:00:00Z", "processingStatus": "pending"}id: the new file’s id. Pass towire_files_deleteor towire://resource URIs.processingStatus:pendingimmediately after upload, orskippedwhen the file type is stored for download only. Background workflows extract entries; checkwire_statusorwire_files_listto 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.
wire_files_delete
Section titled “wire_files_delete”Delete a file and all entries derived from it.
Default visibility: REST on, MCP off. Invoke over REST as DELETE /container/:id/files/:fileId.
| Parameter | Type | Required | Description |
|---|---|---|---|
fileId | string | Yes | The file ID returned by wire_files_list. |
Example call
Section titled “Example call”curl -X DELETE \ -H "x-api-key: YOUR_API_KEY" \ https://YOUR_ORG_SLUG.api.usewire.io/container/YOUR_CONTAINER_ID/files/file_abc123Return shape
Section titled “Return shape”{ "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.
wire_query
Section titled “wire_query”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.
| Parameter | Type | Required | Description |
|---|---|---|---|
sql | string | Yes | A single read-only SQL statement (SELECT, or WITH … SELECT) |
params | array | No | Values 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.
What you can query
Section titled “What you can query”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| Column | Meaning |
|---|---|
object | Object name, which is also the table name |
source | Where the records came from (a file, a connector, an agent write) |
name | Field name, which is also the column name |
type | integer, number, boolean, date, text, json, or mixed when values disagree |
present | How many of the profiled records actually carry this field |
seen | How many records were profiled for this object and source |
geo | lat 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 withwire_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
SELECTorWITH. Anything that writes, changes schema, or reaches outside the container is refused, includingPRAGMAand its table-valued forms. - Only declared tables.
FROMandJOINmay 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 runSELECT * FROM fields. - CTE names must be new. A
WITHclause can’t reuse the namefieldsor the name of an object table, in any case. Such a statement is refused with an error asking for a different name. json_eachandjson_treeare 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.
Distance queries
Section titled “Distance queries”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 kmFROM placesWHERE 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) <= ?3ORDER BY kmwith "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.
Recurring events
Section titled “Recurring events”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.
| Function | Returns |
|---|---|
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.
| Repeats | rrule |
|---|---|
| Every weekday | FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR |
| Every Tuesday | FREQ=WEEKLY;BYDAY=TU |
| First Friday of the month | FREQ=MONTHLY;BYDAY=1FR |
| Last Friday of the month | FREQ=MONTHLY;BYDAY=-1FR |
| Last day of the month | FREQ=MONTHLY;BYMONTHDAY=-1 |
| Every other Thursday | FREQ=WEEKLY;INTERVAL=2;BYDAY=TH |
| Every Tuesday until the end of the year | FREQ=WEEKLY;BYDAY=TU;UNTIL=20261231 |
| Ten times, daily | FREQ=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 occurrencesFROM eventsWHERE (rrule IS NULL AND starts_at >= ?1 AND starts_at < ?2) OR wire_rrule_occurs(rrule, starts_at, ?1, ?2) = 1ORDER BY titlewith "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 startsFROM events e, json_each(wire_rrule_between(e.rrule, e.starts_at, ?1, ?2)) oORDER BY startsThe 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.
Example call
Section titled “Example call”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{ "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" } }}Return shape
Section titled “Return shape”{ "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 matchcolumns. An absent value isnull. A binary value (fromrandomblob,unhexand similar) is returned as{"blob_base64": "…"}.rowCount: number of rows inrows.truncated:truewhen 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_explorefor classified canonical entries: what entity types exist, list them, get one by id, filter on a field, keyword-search within a type.wire_searchwhen the question is in natural language and the answer is somewhere in raw content.wire_querywhen 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’sfiltermode returns matching rows;wire_querycomputes over them.- Near a place:
wire_searchwithnearwhen the question is in words (“a quiet coffee shop near here”),wire_querywithwire_distance_kmwhen it is a computation (“how many visits within 10 km of the office last month”). - On a date:
wire_querywithwire_rrule_occursandwire_rrule_betweenwhen 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.
Activating and deactivating tools
Section titled “Activating and deactivating tools”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/listand is callable viatools/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.
Public container visibility
Section titled “Public container visibility”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.
Next Steps
Section titled “Next Steps”- Core Concepts - Learn about entries and containers
- Custom Tools - Define your own tools on top of these
- MCP Overview - How to connect and use tools
- REST API - Transport details for invoking tools over HTTP