NewsOperate As is live. Send your first client one link

Developers

Your clients' access, one API away

One key reaches every client's tools across 4,000+ services, from your code, an AI agent or n8n. Their tokens stay with us.

main.pyRun
from operate_as import OperateAs
from operate_as_sheets import Sheets

operate = OperateAs("oa_••••")
miller = operate.client("b2f4c3e1-…")

rows = Sheets(miller).read_rows(
    spreadsheet_id="1BxiM…",
    range="Bookings!A2:C5",
)
Response
Waiting for a run…

Quickstart

Any framework, same tools

Your code, an AI agent or an n8n workflow all use one key and reach the same clients and tools. Pick how you connect: each tab installs it, creates the client with your key, lists your clients and reads rows from one client's sheet.

The Python library, published as operate-as, with a typed package for each service. No dependencies beyond the standard library.

  • Each service's package types its actions, so your editor completes each input
  • Errors are typed, so you can catch ReconnectRequiredError and alert your team
  • Sheets(miller).request(...) calls any endpoint of the service's API
pip install operate-as operate-as-sheets
import os

from operate_as import OperateAs
from operate_as_sheets import Sheets

# Create the client once, with the key from the environment
operate = OperateAs(os.environ["OPERATE_AS_API_KEY"])

# One of your clients, by the id the dashboard shows
miller = operate.client("6f1c2a7e-...")

# Read rows from their sheet
rows = Sheets(miller).read_rows(spreadsheet_id="1BxiM...", range="A1:D20")
print(rows.status, rows.data["values"])

Tools

64,000+ tools your agents can call

Every service comes with ready-made tools for its API, plus a request tool for any endpoint they don't cover. Pick a service and search what an agent can do with it.

  • Loading every tool…
  • requestAny other Stripe endpoint, called with the client's accesshttps://api.stripe.com/…

On every key

  • search_tools

    Finds the right tool from a few words, so an agent never loads thousands at once

  • get_tool

    Reads one tool's inputs, with their full JSON Schemas, before running it

  • run_tool

    Runs any tool it found for one client, with that client's access attached

  • request

    Calls any endpoint of a service's own API, for anything the tools don't cover

  • list_clients

    Shows which clients this key reaches, and what each has connected

Over MCP, a tool's dots become underscores: sheets.read_rows is sheets_read_rows. In n8n you pick it from a list instead.

HTTPS API

Ready-made actions, or any endpoint

Use a ready-made action for the common jobs, or pass any request through to the service's own API. Either way we attach the client's access on our server and hand back the service's response.

Endpoints

GET /v1/me/clients
The clients this key can reach, with the services each has connected.
GET /v1/actions
Ready-made actions a page at a time, each with its inputs as name, description, type and required. Every one is listed under Tools above.
POST /v1/actions/{name}
Run an action for one client. Send clientId and input, with input names in snake_case such as spreadsheet_id (camelCase works too). Get back the service's own status and data.
ANY /v1/clients/{clientId}/{service}/{path}
Pass-through to any endpoint of the service's API, for example GET /v1/clients/{id}/sheets/v4/spreadsheets/{spreadsheetId}.
POST /v1/keys
A short-lived key for one agent's run. It reaches no more than your key, narrowed further with clientIds, services and actions, and stops after expiresIn seconds (an hour by default) or the moment your key is revoked.

Errors, in plain words

Every error is JSON with one field, {"detail": "..."}, written to be shown to a person.

401
The key is wrong, revoked, or a short-lived key that expired.
402
The workspace has no plan, such as after a trial ends. Choose one in the dashboard.
403
The request is outside the clients, services, actions or addresses this key allows, or your plan.
404
There's no such action or client.
409
The client hasn't connected this service yet, or their access ended. Send them their link to reconnect.
429
Your plan allows no more calls this period. Retry-After says how many seconds until you can try again.
502
The service didn't answer. Try again shortly.
503
The provider can't renew the client's access right now. Try again later.

List actions

curl "https://api.operate.as/v1/actions?service=sheets" \
  -H "Authorization: Bearer $OPERATE_AS_API_KEY"

{
  "items": [
    {
      "name": "sheets.read_rows",
      "service": "sheets",
      "description": "Read rows from a range of a spreadsheet",
      "inputs": [
        {
          "name": "spreadsheet_id",
          "description": "The ID in the sheet's address, after /d/",
          "type": "string",
          "required": true
        },
        { "name": "range", "description": "Which cells, e.g. Sheet1!A1:D20", "type": "string", "required": true }
      ]
    },
    ...
  ],
  "page": 1,
  "pageSize": 50,
  "total": 19
}

Pass-through to any endpoint

curl https://api.operate.as/v1/clients/6f1c2a7e-.../sheets/v4/spreadsheets/1BxiM... \
  -H "Authorization: Bearer $OPERATE_AS_API_KEY"

{
  "status": 200,
  "data": { "spreadsheetId": "1BxiM...", "sheets": [...] }
}

A short-lived key for an agent

curl -X POST https://api.operate.as/v1/keys \
  -H "Authorization: Bearer $OPERATE_AS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"expiresIn": 900, "services": ["sheets"]}'

{
  "secret": "oa_tmp_Xr4q...",
  "expiresAt": "2026-10-19T13:15:00Z",
  "services": ["sheets"]
}

Catching reconnect required

HTTP/1.1 409 Conflict

{
  "detail": "Miller Dental hasn't connected Google yet. Send them their link."
}

API keys

  • Create keys on the dashboard's API & webhooks page
  • Keys start with oa_ and are shown once; we keep only a hash
  • Scope a key to specific clients, services and actions
  • Lock a key to your servers' IP addresses, so a copy is refused anywhere else
  • Hand agents short-lived oa_tmp_ keys, so they never hold one that lasts
  • Revoke a key at any time, and it stops working at once
  • A key never carries a client's token, so a leaked key exposes only what it was scoped to

Security

Your clients' tokens never leave us

Every call passes through Operate As, which attaches the client's access on our server. So if your code or a workflow export ever leaks, it holds one key you can revoke, and nothing of your clients'.

How we handle your clients' data
.env · leaked in a workflow exportexposed
OPERATE_AS_API_KEY=oa_••••3kd9# Scoped to chosen clients and actions. Revoke it in one click.
Never in itGOOGLE_REFRESH_TOKENSTRIPE_SECRET_KEYHUBSPOT_PRIVATE_APP_TOKENClient passwords

Notifications

Know before a workflow breaks

We check every connection daily, so dead access turns up before your workflow runs into it. If a call does reach an ended connection, it returns 409 with a reconnect message instead of failing silently. The reconnect link is the client's same original link.

Email

Sent to your agency the moment something changes.

Dashboard

A notification beside the client it's about.

Webhooks

Signed JSON to an HTTPS URL you set on the dashboard's API & webhooks page.

Events

connection.connected
The client approved access to a service.
connection.expired
Renewal failed: the client revoked access in the provider, changed their password, an admin blocked it, or it went unused for six months.
connection.removed
Your team disconnected it, or the client removed access from their page.
connection.account_changed
The client connected a different account from the one before.
connection.limited
The client paused or resumed a connection, or allowed only reads.
client.passkey_saved
The client saved a passkey, so they can connect what you asked for.
client.completed
The client connected everything you asked for.
api_key.new_address
An API key was used from an address it hadn't been used from before.
api_key.refused_address
An API key was tried from outside the addresses it's locked to, and refused.
webhook.test
Sent when you press Send test on the API & webhooks page, to check your endpoint.

Point the webhook at an n8n Webhook trigger to pause the client's workflows, alert your Slack, or email the client their reconnectUrl.

Webhook delivery

POST https://your-server.example/hooks/operate-as
Operate-As-Signature: t=1760620800,v1=5f2b...

{
  "id": "evt_...",
  "type": "connection.expired",
  "createdAt": "2026-10-16T08:00:00+00:00",
  "data": {
    "clientId": "6f1c2a7e-...",
    "clientName": "Miller Dental",
    "provider": "google",
    "services": ["sheets", "gmail"],
    "reconnectUrl": "https://..."
  }
}

Check it really came from us

Anyone can send a request to your webhook's address, so check each one before acting on it. With our libraries it's one line.

  1. 01

    Keep your webhook secret

    It's shown once when you save the webhook on the API & webhooks page, and starts with whsec_. Store it like a password, in an environment variable.

  2. 02

    Pass the request as it arrived

    Give the check the body as raw text, before you parse the JSON, and the Operate-As-Signature header. Parsing first changes the text, and the check fails.

  3. 03

    Act only if it passes

    You get the event back when it's real. It's refused if the signature doesn't match, or if it was sent more than five minutes ago, so an old request can't be replayed.

import os
from operate_as import SignatureVerificationError, Webhook

def handle(raw_body: bytes, headers: dict[str, str]) -> int:
    """raw_body: the request body bytes exactly as received"""
    try:
        event = Webhook.verify(
            raw_body,
            headers["Operate-As-Signature"],
            os.environ["OPERATE_AS_WEBHOOK_SECRET"],  # whsec_...
        )
    except SignatureVerificationError:
        return 400  # not from us: ignore it

    if event["type"] == "connection.expired":
        email_client(event["data"]["reconnectUrl"])
    return 204
Verifying without a library

Get started

Send your next client one link

Add a client, select the services, copy the link. You'll get an email the moment their access is ready. Start free, no card required. Paid plans are one flat monthly price, and a busy month never takes your agents offline.