---
title: "Migrate from Octolens"
description: "Point your Octolens code, CLI and integrations at Mentio by changing the base URL and the key, copy your keywords, feeds and filters over with one script, then cut over."
canonical: https://docs.mentio.dev/migrate/octolens
markdown: https://docs.mentio.dev/migrate/octolens.mdx
---

# Migrate from Octolens

Point your Octolens code, CLI and integrations at Mentio by changing the base URL and the key, copy your keywords, feeds and filters over with one script, then cut over.

Mentio serves an Octolens-compatible API at `https://api.mentio.dev/compat/octolens`. It answers Octolens' `/api/v2` endpoints with Octolens' own request and response shapes, so the code you wrote against Octolens, the `octolens` CLI and the integrations built on its API keep working after two changes: the base URL and the API key. Most workspaces move in under an hour.

|                  | Octolens                                       | Mentio                                                                   |
| ---------------- | ---------------------------------------------- | ------------------------------------------------------------------------ |
| Base URL         | `https://app.octolens.com`                     | `https://api.mentio.dev/compat/octolens`                                 |
| Auth             | `Authorization: Bearer <octolens key>`         | `Authorization: Bearer mk_live_...` (a Mentio API key)                   |
| Paths and shapes | `/api/v2/...`                                  | the same `/api/v2/...`, the same JSON                                    |
| Errors           | `{ "error": { "code", "message", "status" } }` | the same envelope and codes                                              |
| Rate limit       | 500 requests an hour                           | 600 requests a minute per workspace                                      |
| Price            | Monthly or annual plans with a mention quota   | $5 per keyword a month plus $0.008 per matched mention, prepaid, no plan |

The compatible API is a second door to the same workspace: a keyword created through it is a Mentio keyword, with the same 30-day look-back, balance checks and alerts as one created through [`/v1`](/api) or the dashboard. When you are ready to use what Mentio adds (keyword groups, people, share of voice, Telegram alerts, the MCP server), move to `/v1` at your own pace.

## Step 1: Swap the base URL (drop-in path)

Create an API key in the dashboard (**Settings**, **API keys**), then point your client at Mentio.

### The octolens CLI

The CLI reads its base URL and key from the environment:

```bash
export OCTOLENS_BASE_URL=https://api.mentio.dev/compat/octolens
export OCTOLENS_API_KEY=mk_live_...

octolens whoami --json
octolens keywords list
octolens mentions list --source reddit
```

`whoami` names your Mentio workspace. `octolens login` is not needed; the key in the environment is used as is.

### Your own code

Change the base URL and the key; nothing else.

```ts
const BASE = 'https://api.mentio.dev/compat/octolens'; // was https://app.octolens.com
const headers = { Authorization: `Bearer ${process.env.MENTIO_API_KEY}`, 'Content-Type': 'application/json' };

const res = await fetch(`${BASE}/api/v2/mentions`, {
  method: 'POST',
  headers,
  body: JSON.stringify({ filters: { source: ['reddit', 'twitter'], sentiment: ['negative'] }, limit: 50 }),
});
const { data, pagination } = await res.json();
```

The same goes for anything built on the Octolens API: a Zapier or n8n HTTP step, an Attio workflow, a script. Change the URL and the key.

## Step 2: Copy your workspace

Run this once with both keys. It reads your keywords, feeds, global filters and company profile from Octolens and creates them in Mentio through the compatible API, rewriting keyword ids inside feed filters on the way.

```js title="copy-from-octolens.mjs"
// node copy-from-octolens.mjs  (Node 18 or later, no dependencies)
const OCTO = { base: 'https://app.octolens.com', key: process.env.OCTOLENS_API_KEY };
const MENTIO = { base: 'https://api.mentio.dev/compat/octolens', key: process.env.MENTIO_API_KEY };

async function call(to, method, path, body) {
  const res = await fetch(`${to.base}/api/v2${path}`, {
    method,
    headers: { Authorization: `Bearer ${to.key}`, 'Content-Type': 'application/json' },
    body: body === undefined ? undefined : JSON.stringify(body),
  });
  const json = await res.json();
  if (!res.ok) throw new Error(`${method} ${path}: ${json.error?.code} ${json.error?.message}`);
  return json;
}

// Company profile: what the classifier reads to judge relevance.
const company = await call(OCTO, 'GET', '/org/company');
const { name, description, productUseCases, competitors, relevanceContext, relevanceGuidelines, twitter, linkedin } = company;
const profile = Object.fromEntries(
  Object.entries({ name, description, productUseCases, competitors, relevanceContext, relevanceGuidelines, twitter, linkedin }).filter(([, v]) => v),
);
await call(MENTIO, 'PATCH', '/org/company', profile);

// Keywords, remembering Octolens id -> Mentio id for the feeds. One that
// cannot be copied is reported and skipped, never the end of the run.
const ids = new Map();
for (const k of (await call(OCTO, 'GET', '/keywords')).data) {
  const { id, isSubReddit, symbolSensitive, ...rest } = k;
  if (isSubReddit) { console.warn(`skipped subreddit keyword ${k.keyword}`); continue; }
  try {
    const created = await call(MENTIO, 'POST', '/keywords', rest);
    ids.set(String(id), String(created.id));
    console.log(`keyword ${k.keyword}: ${id} -> ${created.id}`);
  } catch (err) {
    console.warn(`keyword ${k.keyword} not copied: ${err.message}`);
  }
}

// Feeds (saved filters). Destinations are set up in Mentio (step 3). A
// keyword that was not copied is dropped from a feed's list; a feed left
// with no keyword of its list is skipped rather than widened.
let skip = false;
const remap = (c) => {
  if (c.field !== 'Keywords') return c;
  const mapped = c.values.split(',').map((v) => ids.get(v.trim())).filter(Boolean);
  // Excluding a keyword that was not copied excludes nothing; wanting only
  // such keywords would widen the feed to every keyword.
  if (mapped.length === 0 && !String(c.operator ?? 'in').startsWith('not')) skip = true;
  return { ...c, values: mapped.join(',') };
};
for (const f of (await call(OCTO, 'GET', '/feeds')).data) {
  if (f.isDefault) continue;
  skip = false;
  const keep = (c) => c.field !== 'Keywords' || c.values !== '';
  const body = {
    name: f.name,
    simpleFilters: f.simpleFilters && { conditions: f.simpleFilters.conditions.map(remap).filter(keep) },
    advancedFilters: f.advancedFilters && {
      ...f.advancedFilters,
      groups: f.advancedFilters.groups.map((g) => ({ ...g, conditions: g.conditions.map(remap).filter(keep) })).filter((g) => g.conditions.length > 0),
    },
  };
  if (skip) { console.warn(`feed ${f.name} skipped: none of its keywords was copied`); continue; }
  try {
    await call(MENTIO, 'POST', '/feeds', body);
    console.log(`feed ${f.name}`);
  } catch (err) {
    console.warn(`feed ${f.name} not copied: ${err.message}`);
  }
}

// Global filters.
await call(MENTIO, 'PATCH', '/filters/global', await call(OCTO, 'GET', '/filters/global'));
console.log('done');
```

Keywords start collecting at once, each with the newest posts of the last 30 days. A keyword is created only while the balance covers one more day of every running keyword; the $5.80 welcome credit covers a handful, and a keyword past what the balance covers is reported as not copied (`QUOTA_EXCEEDED`): add funds from **Billing** and run the script again (keywords already copied are returned as they are). A keyword that tracks only Product Hunt, Medium, newsletters, podcasts or Reddit comments, or only review pages, is reported too.

## Step 3: Move your notifications

Octolens notifications (a feed sent to Slack, email or a webhook) become Mentio [alerts](/alerts): a rule with a filter, sent instantly or as a daily digest to Slack, Telegram, email or a webhook. Create them in the dashboard (**Alerts**) or with [`POST /v1/alerts`](/api). The compatible API answers `notifications` with `501` for now.

A Mentio webhook carries a signature (`X-Mentions-Signature-V2`, see [Webhooks](/webhooks)); Octolens webhooks are unsigned, so add the check when you move the handler.

## Step 4: Cut over

1. Run step 2, then let both tools run side by side for a day and compare `POST /api/v2/mentions` from each.
2. Switch your code and the CLI to Mentio (step 1).
3. Recreate your notifications as Mentio alerts (step 3) and send a test from each.
4. Pause your Octolens keywords, then cancel the plan once nothing reads from it.

## How the compatible API differs

| Topic               | Behavior                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Ids                 | Integers, as on Octolens. Each Mentio object gets its integer the first time the compatible API shows it and keeps it. A mention's `sourceId` is its Mentio id (`mm_...`); `GET /api/v2/mentions/{sourceId}` also takes the integer `id`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| One row per keyword | A post that matches two of your keywords is two mention rows, each with one entry in `keywords`. Octolens folds them into one row.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| Lists               | Relevant mentions by default, as on Octolens. Mentions by people you muted and mentions you snoozed stay out of lists, as in the Mentio feed. A filter names at most 50 keyword ids.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| Relevance           | Mentio scores 0 to 100. `relevanceScore` is 0 (high, 70 and up), 1 (medium, 40 to 69) or 2 (low); `relevance` is `relevant` from 40 up, the same line Mentio alerts use. Lists return relevant mentions unless `includeAll` is `true`, as on Octolens.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| Relevance writes    | Marking a mention relevant (`relevance` 0 or 1) sets its score to 100, so it reads back as high (0); 2 marks it not relevant; 3 restores the classifier's verdict.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| Relevance filters   | Buckets high; high and medium; low; or all three. Low leaves out mentions not scored yet. Medium alone, medium and low, or high and low answer `501`: Mentio filters by a floor. Analytics count high and medium by default, as on Octolens.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| Tags                | Mentio's intents under Octolens' names (`product_question`, `customer_testimonial`, `user_feedback`, `promotional_post`, ...), plus `own_brand_mention` and `competitor_mention` from the keyword's tag and `ai_generated` for machine-made posts. `industry_insights` and `launch_announcement` are never set.                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| Platforms           | Reddit comments are Reddit replies: a filter on `reddit_comment` returns Reddit replies, and a keyword or analytics query naming Reddit comments without Reddit answers `501`. Mentio searches Reddit, X (`twitter`), Hacker News, GitHub, Stack Overflow, DEV (`dev`), YouTube, LinkedIn, TikTok, Bluesky and news, plus Instagram, which Octolens has no name for (a keyword update never removes it). Product Hunt, Medium, newsletters and podcasts are not searched: a keyword naming only those is refused. Review pages (Trustpilot, Google, App Store, Google Play) are connected per keyword in the dashboard or through `/v1`.                                                                                                               |
| Keyword matching    | Always a whole phrase: `symbolSensitive: false` is accepted and has no effect. A wildcard exclusion takes a `*` at the start or the end only. Subreddit keywords (`isSubReddit`) are not supported; use the `positiveSubreddits` global filter.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| Filters             | Source, sentiment, keyword, language, tag, relevance, review rating, post type, follower bounds and dates, including the `!` exclusions, except excluding star ratings. In the advanced form, OR works between conditions on the same field, and between groups of one condition each. Two tag conditions that must both hold, one tag list mixing intent tags with `own_brand_mention`, `competitor_mention` or `ai_generated`, the tags `industry_insights` and `launch_announcement`, engagement thresholds, bookmarks and a feed's time range answer `501` rather than returning a wider set than you asked for. A filter that can match nothing (two platforms that must both hold, only platforms Mentio does not search) returns an empty list. |
| Content filters     | A feed's `Content` condition answers `501`: Octolens matches whole words of the post, and Mentio's text search is a substring of the post or the author's name, which would return more. Search mentions with the list's `search` field instead.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| Global filters      | As on Octolens: `add` skips values already on the list and reports under `dropped` the ones refused because they sit in the other subreddit list; `remove` ignores values not on the list and refuses to empty one without `allowEmpty: true` (`CLEAR_NOT_CONFIRMED`).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| Feeds               | Saved as Mentio views. The icon is not kept; a feed with destinations is refused (use alerts). A view made in Mentio with a condition this API cannot show (a group, a link host, author tags) is listed, but its filter is only edited in Mentio (`409 FEED_FILTER_WRITE_CONFLICT`).                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| Lists and exports   | Pages of 20 by default, 100 at most. An export returns at most 5,000 mentions (Octolens allows 50,000; `X-Total-Count` is the rows sent) and takes no `author` (use `mentions/by-author`); a read-only key may list and export.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| Follower filters    | `TwitterFollowerCount` takes `>=`, `<=` and `=`; each scopes the feed to X, where follower counts exist.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| Paths               | Only `/api/v2`. Other versions and unknown paths answer `404 NOT_FOUND`; a path with a trailing slash is not found.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| Keywords            | A term of 2 to 80 characters and the rule limits of the Mentio API. The same term again returns the keyword already tracked; `allowDuplicate: true` answers `409 ITEM_EXISTS`, since Mentio tracks a term once per group.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| Company profile     | `relevanceGuidelines` are Mentio's guidelines; `classificationGuidelines` answers `501` (write your rules in `relevanceGuidelines`).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| Analytics           | `sentiment`, `volume`, `sources`, `keywords` and `summary` over UTC days, 30 days by default, high and medium relevance unless `relevance=0,1,2`. Filtering them by tag or sentiment answers `501`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| Usage               | `mentions.limit` is what your balance still pays for this month, not a plan quota.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| Rate limit          | 600 requests a minute per workspace, shared with `/v1` and MCP, with `X-RateLimit-*` headers.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |

## Not available

These answer `501` with the code `FEATURE_DISABLED`: agency workspaces, setup scans and proposals, the AI filter wizard and monitoring recommendations, on-demand search, keyword suggestions, estimates and health, notifications, Slack channel search, attention items, export download links and app deep links. Mentio has its own version of most of them in `/v1` and the dashboard.
