Developer documentation · MCP v2 / REST v1

ZillaSales in your assistant

Find independent sales opportunities and representatives. Compare public information, then connect your account for favorites, drafts and confirmed applications.

Connect to ZillaSales

Public discovery needs no API key or sign-in. To use account features, choose Connect ZillaSales in your assistant, sign in on the ZillaSales website and approve the requested permissions. OAuth handles the connection automatically. Developers testing a remote MCP connection can use this Streamable HTTP endpoint.

https://zillasales.com/api/mcp

Try “Find recurring-commission software opportunities covering Ontario” or “Find reps with commercial lighting experience in Texas.” Use country code CA for Canada; territory code CA means California. Use get_catalog_options for accepted filters.

OAuth 2.1 uses S256 PKCE, registered redirects and resource-bound tokens. Access tokens last one hour; refresh tokens rotate and connections expire after 90 days. The permission screen describes account reads and writes separately. UI is optional: every tool also returns structured data and text. A working MCP endpoint does not imply public directory approval.

A working Node.js quickstart

Use a current Node.js LTS release. In an empty project, install the MCP SDK and save the following as quickstart.mjs. This example makes public read-only calls; it creates no accounts, favorites or applications.

npm init -y
npm install @modelcontextprotocol/sdk@1.30.0
node quickstart.mjs
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from
  "@modelcontextprotocol/sdk/client/streamableHttp.js";

const client = new Client({ name: "my-zillasales-integration", version: "1.0.0" });

async function call(name, args = {}) {
  const result = await client.callTool({ name, arguments: args });
  if (result.isError) {
    // Preserve result._meta for your host's OAuth linking handler.
    const message = result.content
      .filter(item => item.type === "text")
      .map(item => item.text).join("\n");
    throw Object.assign(new Error(message), { mcpResult: result });
  }
  return result.structuredContent;
}

try {
  await client.connect(new StreamableHTTPClientTransport(
    new URL("https://zillasales.com/api/mcp")
  ));
  const { tools } = await client.listTools();
  console.log("Available tools:", tools.map(tool => tool.name));
  const catalog = await call("get_catalog_options");
  console.log("Working countries:", catalog.workingCountries);

  const filters = { query: "software", take: 6 };
  const first = await call("search_opportunities", filters);
  console.log("Matching opportunities:", first.total);
  for (const item of first.opportunities) {
    console.log(item.title, item.opportunityUrl, item.availableActions);
  }
  if (first.nextCursor) {
    const next = await call("search_opportunities", {
      ...filters, cursor: first.nextCursor,
    });
    console.log("Next page:", next.opportunities.length);
  }
  if (first.opportunities.length) {
    const detail = await call("get_opportunity", {
      opportunityId: first.opportunities[0].opportunityId,
    });
    console.log("Missing facts:", detail.missingInformation);
  }
} finally {
  await client.close();
}

The SDK handles initialization and the Streamable HTTP headers. Successful calls return structuredContent and an equivalent JSON text result. Check isError before reading success fields. The live inventory changes, so a valid search can return zero results.

Find recurring software opportunities in Ontario

Pass this argument object to search_opportunities. Leave applicationsAvailable unset to include SEO-only listings; set it to true when the user wants to apply.

{
  "query": "software",
  "countries": [
    "CA"
  ],
  "territories": [
    "ON"
  ],
  "commissionRecurrence": "RECURRING",
  "take": 6
}
Filter or resultHow to use it
territories / countriesUse catalog state/province codes or names for territories. CA means California there; countries: ["CA"] means Canada. Multiple choices within each list match any selected value. Different filters combine.
industriesOpportunity search accepts catalog industry IDs, labels or category slugs. Drafts require industry IDs such as tech or energy.
minimumCommissionSupply commissionUnit: PERCENT or FLAT with the amount. Listings with an unknown lower bound do not match. A flat amount has no verified currency in this response; do not assume one.
includeRelated / matchTypeRelated results are opt-in and match some keywords while retaining the structured filters. EXACT means all query words matched public text; it is not a guarantee of business suitability.
nextCursorPass it back with unchanged filters and take. Pagination is over current inventory, not a frozen snapshot; deduplicate opportunityId if collecting several pages.
missingInformation / availableActionsKeep absent facts unknown. Use the returned action flags and applicationNotice instead of inferring application availability from a public page.

Find and inspect sales representatives

Run these calls inside the quickstart’s try block. Rep search accepts a business query, territories, take and offset. Put products, buyer types and channels in the query; it does not accept the opportunity search’s country or commission filters.

// Use the connected client and call helper from the quickstart.
const repFilters = {
  "query": "commercial lighting for electrical wholesalers",
  "territories": [
    "TX"
  ],
  "take": 6,
  "offset": 0
};
const page = await call("search_reps", repFilters);
for (const candidate of page.profiles) {
  console.log(candidate.whyFit, candidate.interest);
}
if (page.profiles.length) {
  const candidateRef = page.profiles[0].candidateRef;
  const detail = await call("get_rep_profile", { candidateRef });
  console.log(detail.profile);
}
if (page.nextOffset !== null) {
  const next = await call("search_reps", {
    ...repFilters, offset: page.nextOffset,
  });
  console.log("Next page:", next.profiles.length);
}

Candidate references expire after 24 hours. References issued to a connected account stay bound to that account; public references can be used after connecting. Fetch the profile again before acting because eligibility and contact permissions can change. Match evidence describes possible fit, not confirmed availability, interest or buyer relationships.

Connect an account with OAuth

MCP clients should discover the authorization server from the protected resource metadata. Register a client with the actual redirect URI, then use authorization code flow with S256 PKCE and the resource https://zillasales.com/api/mcp. Keep the verifier and tokens in your client’s secure storage. A REST API key cannot substitute for an OAuth grant.

Protected resource metadata
https://zillasales.com/.well-known/oauth-protected-resource

Authorization server metadata
https://zillasales.com/.well-known/oauth-authorization-server/api/mcp/oauth

Issuer
https://zillasales.com/api/mcp/oauth
  1. Start anonymously for public browsing. When an account tool returns a linking challenge, let the host open the authorization flow.
  2. The user signs in to ZillaSales with Google or an email code and approves the scopes. The assistant should never request their password or email code in chat.
  3. Validate state and exchange the authorization code using the original PKCE verifier, redirect URI and resource. Use the returned bearer access token for MCP requests.
  4. Refresh before the one-hour access token expires. Store the newly rotated refresh token atomically; do not reuse the old one. Reused refresh tokens revoke their grant family.
  5. Reconnect after revocation or the 90-day connection expiry. Users can disconnect under Settings → Integrations.

All protected tools require account:read in addition to the feature scope below. Scopes do not override the account role, record ownership or existing contact entitlements. Request only the permissions the integration uses.

ActionAccountFeature scope
Public discovery and comparisonAnyoneNone
Account, profile recommendations, application previewConnected account; rep for recommendations and previewaccount:read
Read favoritesRep or companyfavorites:read
Save opportunity / candidateRep / company respectivelyfavorites:write
Own opportunities, draft review, applicantsCompanyopportunities:read
Create, edit, submit draftCompanyopportunities:write
Own application historyRepapplications:read
Confirm an applicationRepapplications:write

Account workflows, with an explicit review

The examples below are tool argument templates for an OAuth-connected client. Replace placeholders with values returned by previous calls. They are separate steps: display the review and wait for the user’s approval before sending a confirmation call.

Company: create a private draft and submit it for moderation

After the company supplies these facts and asks you to save them, call create_opportunity_draft. Generate a new requestId for each new draft; retain that ID and the identical payload for retries. These are illustrative facts, not recommended terms for every opportunity.

{
  "requestId": "example-lighting-draft-001",
  "title": "Independent commercial lighting sales representative",
  "description": "Sell commercial LED lighting to electrical wholesalers in Texas. Commission is 10% on one-time product sales.",
  "industries": [
    "energy"
  ],
  "territories": [
    "TX"
  ],
  "commissionMin": 10,
  "commissionMax": null,
  "commissionUnit": "PERCENT",
  "commissionRecurrence": "ONE_TIME",
  "revenueModel": "TRANSACTIONAL"
}
  1. Creation returns an opportunityId and revision. The draft is private and unpublished. Commission amounts can be null; do not invent missing amounts or other claims.
  2. To edit, call get_my_opportunity, then update_opportunity_draft with its opportunityId, revision and all content fields. This is a complete replacement, not a partial patch.
  3. Call review_opportunity_draft with the opportunityId. Display its full opportunity and effects, and ask the company to approve submission.
  4. Only after approval, call submit_opportunity_for_review with that reviewToken and confirmed: true. This sends the draft to moderation and starts normal enrichment. It does not publish it.
{
  "reviewToken": "<reviewToken returned by review_opportunity_draft>",
  "confirmed": true
}

Rep: preview an application, then confirm

  1. Search with applicationsAvailable: true and let the user choose an opportunity. Use save_opportunity if they only ask to save it.
  2. Call prepare_application with the chosen opportunityId. Display the returned opportunity, applicant profile, alreadyApplied and sharing effects.
  3. Only after the user approves, pass its reviewToken and confirmed: true to submit_application. Submission creates an application and may notify the company under the website’s existing rules.
  4. Read the result and use get_my_applications for history. Retrying does not create a second application.
{
  "opportunityId": "<opportunityId returned by search_opportunities>"
}

Review tokens last 15 minutes and bind the account, action and reviewed revisions. If the listing, draft or applicant profile changes, obtain a new review and confirmation. Never generate a review token yourself or automatically set confirmed: true merely because a token was returned.

Company: shortlist candidates and review applicants

Use search_reps → get_rep_profile → save_to_shortlist with the selected candidateRef when asked to save. Read favorites with get_saved_items. For applicants, call get_my_opportunities, then get_opportunity_applicants with your opportunityId. Contact details follow existing website access; saving neither unlocks contacts nor sends outreach.

Account lists use take and offset. Increase offset by take while hasMore is true. Recheck current records before an action instead of treating an earlier list as permanent authorization.

Errors and troubleshooting

A successful HTTP response can contain an MCP tool error. Check isError, then parse the JSON text for its error code and message. Input-schema failures can also be returned directly by the MCP SDK. Read the current tools/list schema when an argument is rejected.

{
  "isError": true,
  "content": [
    {
      "type": "text",
      "text": "{\"error\":{\"code\":\"REVIEW_REQUIRED\",\"message\":\"Review this action again before confirming it.\"}}"
    }
  ]
}
Tool error codeRecovery
RATE_LIMIT / SERVER_BUSY (HTTP 429)Respect Retry-After, then retry with bounded backoff. Connected accounts have separate allowances; anonymous traffic shares a source-IP allowance.
AUTH_REQUIRED / SCOPE_REQUIREDConnect or reconnect using OAuth and approve the requested permission. Pass the mcp/www_authenticate metadata to the host’s linking flow.
WRONG_ACCOUNT_ROLE / CONNECT_REP_ACCOUNTUse a rep account for applications and saved opportunities; use a company account for drafts and saved candidates.
SAVE_PERMISSION_REQUIREDConnect the appropriate account and grant favorites:write.
INVALID_TERRITORY / INVALID_CURSORUse catalog codes or names. Reuse a cursor only with exactly the same filters and page size; restart when filters change.
SEARCH_TOO_BROADNarrow the requested coverage. No partial count is returned when the inventory scan limit is reached.
CANDIDATE_UNAVAILABLE / OPPORTUNITY_UNAVAILABLESearch again. A reference can expire, belong to another account, or point to a record whose visibility changed.
APPLICATION_UNAVAILABLE / SAVE_UNAVAILABLERespect the listing’s supported actions. SEO-only listings are browseable but do not support applying or saving.
REVISION_CHANGED / REVIEW_REQUIREDRead the current record, display a fresh review and ask for confirmation again. Never silently approve changed content.
REQUEST_CONFLICTThe requestId was already used for different draft content. Retry the original payload or use a new ID for a genuinely new draft.
DRAFT_REQUIREDThe record is no longer a private draft. Use its manageUrl for the website’s reviewed edit flow.
PROFILE_UNAVAILABLEComplete or restore the account’s active sales rep profile on the website.
UNAVAILABLERetry transient failures with bounded backoff. Contact support if they persist.

At the HTTP layer: 401 means the token is invalid or expired; 403 can mean a disallowed browser Origin; 405 means the stateless endpoint received GET or DELETE instead of POST; 413 means the request exceeded 32 KB; 429 means a rate limit. Respect Retry-After and use bounded backoff. Use an MCP client instead of opening the endpoint as a webpage.

For an optional in-chat comparison, pass up to eight opportunityIds to show_opportunities or candidateRefs to show_reps. These return public comparison data and a UI resource hint. Clients without MCP Apps support can render the same structured data as text. The component cannot fetch arbitrary websites or reveal extra contacts.

MCP tools

Public discovery

search_opportunities · get_opportunity · search_reps · get_rep_profile · get_catalog_options

Browse redacted public opportunities, including SEO-only listings, and candidate previews without signing in. Search by products and required sales coverage. Public search never exposes private drafts or company contact details.

Compare and save

show_opportunities · show_reps · save_opportunity · save_to_shortlist · get_saved_items

Compare selected results in compatible chat clients. Connect a rep account to save opportunities or a company account to save candidates. Repeated saves are safe. Saving does not apply or contact anyone.

For sales reps

get_recommended_opportunities · prepare_application · submit_application · get_my_applications

Find opportunities grounded in your profile. Review the exact opportunity, your profile and what will be shared before confirming an application. SEO-only listings remain available to browse and do not accept applications.

For companies

get_my_opportunities · get_my_opportunity · create_opportunity_draft · update_opportunity_draft · review_opportunity_draft · submit_opportunity_for_review · get_opportunity_applicants

Work on your own private drafts using facts you supply. Confirm the reviewed draft before submitting it to moderation. Read your applicants with the same contact permissions as on the website. Nothing publishes automatically.

Account access

get_account_access

Check the connected account role and saving permissions. OAuth scopes limit each connection. Already-authorised contact access is respected; these tools never unlock contacts or spend credits.

Access and permissions

Anonymous results contain the same redacted information available publicly on the website. Hidden, closed and private opportunities are excluded from public tools. Companies can read their own records; reps can read their own applications. Connected company profile reads include only contact details already permitted by their website access. Opportunity detail reads show company identity to its owner or a rep with an existing contact unlock. Public search and comparison results stay redacted.

OAuth scopes include account:read, favorites:read, favorites:write, opportunities:read, opportunities:write, applications:read and applications:write. Your account role is checked independently. Draft submission and applications require a fresh review token and explicit confirmation. Another account cannot use that review token.

Disconnect an assistant in Settings → Integrations. This stops future access; previously saved favorites, drafts and applications remain in your account. It does not erase information already returned to the assistant.

Limits and retry behavior

OAuth MCP allows 120 requests per minute per ZillaSales account, shared across its connections. Anonymous browsing has a separate shared source-IP allowance, initially 600 requests per minute. Server capacity protection can also return 429 with Retry-After during bursts. These are service protection limits, not guaranteed throughput or an OpenAI quota. Search returns up to 24 results per page; comparison views accept eight records. Candidate references expire after 24 hours.

Public keyword search checks redacted text; broad inventory scans are capped at 10,000 eligible records and return a narrowing instruction rather than incomplete counts. Rep discovery uses basic matching against stored profile evidence and required territories, without LLM or embedding calls. Discovery and saving spend no contact credits.

MCP uses one tool interface for all connections; the available actions depend on your account and permissions. Connect with OAuth for account workflows. API keys do not grant OAuth scopes. REST integrations have plan-based daily allowances and per-minute limits; use REST /account to check them.

  • Invalid input: read the tool schema and correct the supplied filters.
  • Authentication required: connect the account or grant the missing permission through OAuth.
  • Unavailable record: search again; visibility may have changed.
  • Changed revision or review: read and review again before confirming.
  • Rate limit: respect Retry-After, then retry with backoff.
  • Retrying draft creation: reuse requestId and identical content. Retrying an application or save does not create duplicates.

MCP tool errors use isError and JSON text; transport failures use HTTP status codes. JSON-RPC POST is stateless. GET and DELETE return 405, and JSON-RPC batches are rejected. Tools declare their own input/output schemas, read/write annotations and security requirements.

Data shared with your assistant

ZillaSales receives tool arguments and the connected account identity, when authorised. We store connection metadata, granted scopes and token hashes to operate account access. Account actions create the same favorites, drafts and application records used by the website. The optional in-chat view has no advertising trackers or direct third-party network requests.

The assistant provider receives the tool results you request, which can include authorised private account data. Its handling of those results follows its own terms and your settings. Keep search queries focused on business requirements; do not supply passwords or contact lists. Privacy policy · Terms

Integration support

For partner setup, compatibility questions or an access issue, contact support@zillasales.com. Include the tool name and error code, without tokens or private profile content.