On this page
Guides / Authentication

Authentication

Create your API key, understand scopes, and keep the key safe.

Updated View .md

The Meteor API authenticates with a secret API key. Every request carries the key in the Authorization header, and the key defines which workspace it belongs to and what it can do (its scopes).

Key format

Every key starts with a prefix that tells you its type:

The key is a server-side secret. Never ship it to a browser or a mobile app, and never commit it. If it leaks, revoke it from the dashboard and create a new one.

Create a key

In your Meteor dashboard: Settings → Developers → Create API key. You pick the scopes it needs and the environment (live or test). The key is shown once — store it the moment you create it.

You can create and manage keys before the workspace has a plan. To use them, the workspace needs a current monthly Meteor plan; a trial or a sponsored plan also enables operation. Approved Tech Partners may receive a special USD 0 fixed monthly plan enabled only by Meteor, with Energy billed at the premium rate.

Use the key

Keep it in an environment variable, never hardcoded:

export MET_API_KEY=met_live_your_key_here

With the SDK:

import Met from '@meteor.ia/sdk';

const met = new Met(process.env.MET_API_KEY, { workspaceId: 7 });
import os
from meteor_ia import Met

met = Met(os.environ["MET_API_KEY"], workspace_id=7)

With curl, the key goes as a Bearer token:

curl https://api.met.meteor.com.co/api/v1/workspaces/7/runs \
  -H "Authorization: Bearer $MET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":"Hello"}'

Know which identity you are calling with

A key does not tell you whose it is just by looking at it. To ask the server, use GET /me: it returns the identity the API recognises for the key you are calling with — who owns it, which workspace it operates, with what scopes and in which environment.

curl https://api.met.meteor.com.co/api/v1/me \
  -H "Authorization: Bearer $MET_API_KEY"
const me = await met.me.retrieve();
console.log(me.workspace?.id, me.api_key.scopes, me.livemode);
me = met.me.retrieve()
print(me["workspace"]["id"], me["api_key"]["scopes"], me["livemode"])

The response as it arrives on the wire. Every successful API response travels inside an envelope, and the resource lives in data:

{
  "success": true,
  "data": {
    "object": "identity",
    "livemode": true,
    "api_key": {
      "id": "key_01HXYZ",
      "object": "api_key",
      "owner_type": "workspace",
      "env": "live",
      "scopes": ["items:read", "runs:execute", "runs:read"],
      "rate_limit_rpm": 120,
      "monthly_quota": null
    },
    "workspace": { "object": "workspace", "id": 7, "slug": "acme" },
    "partner": null
  }
}

The official SDKs hand you the contents of data directly, which is why yo in the examples above is the inner object. If you call with curl or your own client, read .data. A client generated from the contract already knows: the OpenAPI declares the envelope. See Errors and idempotency.

What each field is for:

FieldWhat you get from it
api_key.idIdentifies the row in the dashboard and in your request logs. It is not the secret and it authenticates nothing: you can paste it into a support ticket safely.
api_key.owner_typeworkspace if the key belongs to a customer, partner if it belongs to a Tech Partner.
api_key.scopesWhat the key can do, according to the server. When a call returns 403 missing_scope, this is the list to check it against.
livemode / api_key.envWhether what you do has real effects. Read it here rather than from the key prefix: it is the same criterion the server applies.
workspace.idThe workspace this request operates. With a workspace key it is always its own; with a partner key it is the attributed customer you are operating.
partner.idThe partner that owns the key, or null.
rate_limit_rpm · monthly_quotaThe ceilings that actually apply to you: the stricter of the key's and your plan's. null means no cap. These are the numbers behind a 429.

The endpoint never returns the key secret, nor its prefix, its last characters or its length. If you lost the key you cannot recover it here: create a new one in the dashboard.

GET /me currently requires the runs:read scope. If your key does not carry it, the call returns 403 missing_scope — the key is valid, it just cannot reach this endpoint.

From the terminal

met whoami prints the same thing, and --save-workspace stores the workspace the server resolved in your local config, so you no longer pass --workspace on every command:

met whoami
met whoami --save-workspace
met whoami --json

If your local config points at one workspace and the key operates another, met whoami says so: that mismatch is the biggest time sink there is, because everything answers 200 and the data simply is not the data you expected.

Scopes

A key can only do what it has a scope for. Scopes follow the pattern domain:action, where :write covers create, update and delete (there is no separate :update). Ask for the ones your integration needs and nothing else.

These tables come from the API scope catalog and the OpenAPI contract. The Endpoints column says how many REST operations ask for that scope today.

A under Endpoints means no REST operation asks for that scope today. It stays valid: a key that already carries it keeps working.

Watch out for conversions:read: no REST endpoints, but webhook events behind them. To subscribe an endpoint to those events, you need the scope.

rag:read, rag:write and sites:read are no longer offered when issuing a new key or registering an OAuth app. Keys that already carry them are unaffected; they simply unlock no calls.

Running Mets

ScopeAllowsEndpoints
agents:readView Mets6
agents:writeCreate and configure Mets10
runs:executeRun Mets — spends Energy2
runs:readRead runs and history3
skills:readView skills9
skills:manageCreate, edit and remove skills12
functions:readView AI Functions (code and Met tools)5
functions:writeCreate and edit AI Functions6
rag:readQuery indexed collections
rag:writeIndex content so it can be queried later
flows:writeCreate, edit, duplicate and archive flows5
automations:readView triggers14
automations:writeCreate, edit and delete triggers7
automations:executeFire automations4

Data

ScopeAllowsEndpoints
collections:readRead collections13
collections:writeCreate, edit and delete collections22
items:readRead items9
items:writeCreate, edit and delete items27
contacts:readRead CRM contacts12
contacts:writeCreate, edit and delete contacts17
tasks:readRead agentic tasks (the steps a Met runs)7
tasks:writeCreate, edit and delete tasks16
variables:readRead workspace variables5
variables:writeCreate, edit and delete variables6
files:readRead files and the media library2
files:writeUpload and delete files7

Conversations and channels

ScopeAllowsEndpoints
conversations:readRead WhatsApp and CRM conversations9
conversations:writeCreate and edit chat quick replies3
handoff:manageAutopilot, assignment and operator message4
channels:readChannel status and broadcasts5
channels:sendSend over WhatsApp and broadcasts13
channels:manageCreate and delete WhatsApp templates2

Integrations and platform

ScopeAllowsEndpoints
integrations:readView MCP integrations3
integrations:manageEnable integrations and store their credentials3
integrations:executeRun integration tools4
mcp:useOpen a session on the MCP server2
webhooks:manageInbound webhooks and outbound event subscriptions20
events:readWorkspace events: live and from the log6
reminders:readView reminders1
reminders:writeCreate, edit and delete reminders3
billing:readPlan and usage (there is no billing:write)11
conversions:readConversion status and settings, plus the conversion.sent and conversion.discarded events
conversions:writeReport a conversion to Meta (CAPI)2
sites:readView websites
snapshots:readBrowse the template catalog and view your own2
snapshots:installInstall a template into the workspace2

Partner keys only

These scopes cannot be issued on a workspace key: the server re-checks on every request and answers 403. See the Partners API.

ScopeAllowsEndpoints
snapshots:publishPublish a template to the marketplace1
workspaces:provisionCreate client accounts3
workspaces:rechargeTop up energy on an account you provisioned1
partner:billing:manageThe partner's payment method, used to pay for the accounts it manages2
partner:clients:readAttributed clients1
partner:leads:readView leads4
partner:leads:writeCreate, edit, move and delete leads7
partner:projects:readView implementation projects5
partner:projects:writeCreate and edit projects, tasks, milestones and phase progress10
partner:commissions:readCommissions (always read-only)1
partner:payouts:readPayouts (always read-only)2
partner:support:readYour own tickets and your clients'3
partner:support:writeOpen tickets, reply and change status3

What test mode does and does not do

A met_test_ key cannot exercise scopes with real external effects: integrations:execute, channels:send, conversions:write, channels:manage, workspaces:provision, workspaces:recharge, partner:billing:manage and snapshots:install answer 403 with test_mode_restricted. The restriction holds over REST and over the MCP server alike: a tool whose scope is restricted does not even appear in the catalog your agent sees.

That, plus cost, is everything test mode separates from production:

With a met_test_ key
Run EnergyNot debited: the run is recorded at zero cost.
How many runsThere is a daily cap on test runs per workspace. Once you hit it, 429 with test_run_daily_cap. Your plan can raise that cap, or set it to zero.
External effectsThe scopes above answer 403 test_mode_restricted.
livemodefalse on the run resource and on the events that run produces.
Where the data livesIn the same database and the same tables as production. There is no copy, no mirror, no separate environment.
Contacts, items, collections, tasks, filesReal. If the key carries items:write, your tests create items your team sees in the dashboard.
UndoNone. Nothing is rolled back when the key expires or is revoked.

Only two things carry the livemode mark and can therefore be told apart afterwards: runs and events. Everything else you write with a test key is indistinguishable from what your production wrote, so there is also no way to ask for "delete my test data": those records are deleted one by one, like any other.

The one exception cleans itself up: a run with no conversation_id runs in an ephemeral conversation, and those are purged after a few days — whether they came from a test key or a live one.

To test without touching your data, from least to most work:

  1. Issue the test key with read scopes only (plus runs:execute if you are going to run Mets). It is a one-line decision at creation time and it closes the whole problem: what has no write scope does not write.
  2. Use a separate workspace for development and issue the test key there. It is the closest thing to a separate environment that exists today, and it is what you can delete wholesale when you no longer need it.
  3. Look at the shape of the responses first with the portal's demo key: it asks for no sign-up, runs against a fictional business, and is read-only. It is on the home page, under *Not ready to sign up?*.

If a key tries something outside its scopes, the API answers 403 with the code missing_scope, and the param field carries the missing scope. Do not retry — ask the key owner to widen the scopes.

Revoke

A revoked key stops working in under 60 seconds. Revoke and rotate keys at the first sign of a leak, and use a different key per environment (one for test, one for production).

Apps that operate across workspaces

If you are building an app that other workspaces connect, do not ask them for an API key. Register an OAuth app in your dashboard: Settings → Developers → OAuth apps. The user sees the requested scopes and authorizes (or revokes) your app.

Follow the OAuth apps guide: it uses Authorization Code with PKCE S256, exact callbacks, and rotating refresh tokens.