Pass-through requests
Call any endpoint of a service's own API with the client's access attached.
Actions cover common jobs. A pass-through request covers everything else: you write the call the way the service's own docs describe it, and Operate As adds the client's access and sends it on. It works for every service in the catalog, and every plan includes it.
Build the URL
Take the call from the service's docs, drop its base URL, and put what's left after your client and the service's key:
https://api.operate.as/v1/clients/{clientId}/{service}/{path}| Part | What it is |
|---|---|
clientId | The client's ID, from GET /v1/me/clients |
service | The service's key, such as sheets, slack, hubspot |
path | The path from the service's API docs, after its base URL |
So Google Sheets' own GET https://sheets.googleapis.com/v4/spreadsheets/1BxiM... becomes:
GET https://api.operate.as/v1/clients/6f1c2a7e-.../sheets/v4/spreadsheets/1BxiM...Send it with your Operate As key, not the client's. Every service's base URL is on its page under Providers; pasted-key services are on API-key services.
curl "https://api.operate.as/v1/clients/6f1c2a7e-.../sheets/v4/spreadsheets/1BxiM..." \
-H "Authorization: Bearer $OPERATE_AS_API_KEY"The libraries and the MCP request tool send bodies as JSON. For a form or file body, such as Stripe's POST calls, use the HTTPS API as in the examples below, or the service's action, which encodes it for you.
The path stays under the service's API
A path with a .. or . segment, or a backslash, is refused with 400 (The path can't leave the service's API.). That stops a key that may reach one API on a host from reaching another API on the same host.
Encode anything in a segment that isn't a plain letter, digit or symbol the service expects. A range like Sheet1!A1:D10 can go as it is; a sheet name with a space needs %20. The libraries keep your slashes and encode the rest.
What's passed on
| Part of your request | What happens |
|---|---|
| Method | GET, POST, PUT, PATCH and DELETE go on as sent |
| Query string | Sent as is. Repeated parameters keep their order, so ?id=1&id=2 arrives with both |
| Body | Sent as the raw bytes, with your Content-Type. JSON, form-encoded and file bodies all work |
| Headers | Only a short allowlist, below |
| Header | Why |
|---|---|
Content-Type, Accept | What the body is, and what you want back |
Idempotency-Key | Lets a service, such as Stripe, ignore a retried create |
Service-specific ones, such as Stripe-Version and Dropbox-API-Arg | Headers a service's API needs to work |
developer-token, login-customer-id, linked-customer-id | Google Ads wants your agency's own developer token on every call, and your manager account's ID when you work through one |
DeveloperToken, CustomerId, CustomerAccountId | The same for Microsoft Advertising |
LinkedIn-Version | The month of LinkedIn's marketing API you're writing against, such as 202509 |
X-Amz-Target | The operation an AWS JSON API runs, such as DynamoDB_20120810.ListTables |
Your own Authorization is never passed on.
How access is attached
| The client connected by | Operate As adds |
|---|---|
| Signing in (Google, Microsoft, Slack, HubSpot and the rest) | Authorization: Bearer <the client's token>, renewed first if it's about to run out |
| Pasting a key | The key in that service's header, such as x-api-key for Anthropic or api-key for Brevo, or as a query parameter, such as Pipedrive's api_token |
| Pasting a key and a second secret | Both, each where the service reads it, such as Datadog's DD-API-KEY and DD-APPLICATION-KEY |
| Pasting a key the API takes in its body | The key added to your JSON body, such as Mailchimp Transactional's key |
| Pasting a key the API takes in its address | The key in the address itself, such as Telegram's /bot<token>/ |
| Pasting AWS access keys | An AWS Signature Version 4 signature on each call, so the secret key never leaves Operate As |
| Either, when the API needs a version | Notion-Version for Notion, anthropic-version for Anthropic, revision for Klaviyo |
The answer
The same result as an action:
{ "status": 200, "data": { "spreadsheetId": "1BxiM...", "sheets": [] }, "encoding": "json" }encoding is "json", "text" or "base64". Binary answers, such as file downloads, come back base64-encoded in data. A service's error comes back the same way, with its status in status. Errors from Operate As itself, such as a client who needs to reconnect, are HTTP errors. See Errors.
Examples
Read a sheet's details:
curl "https://api.operate.as/v1/clients/6f1c2a7e-.../sheets/v4/spreadsheets/1BxiM...?includeGridData=false" \
-H "Authorization: Bearer $OPERATE_AS_API_KEY"Update cells:
curl -X PUT "https://api.operate.as/v1/clients/6f1c2a7e-.../sheets/v4/spreadsheets/1BxiM.../values/Sheet1!B2?valueInputOption=USER_ENTERED" \
-H "Authorization: Bearer $OPERATE_AS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "values": [["Paid"]] }'Batch reads repeat a parameter:
curl "https://api.operate.as/v1/clients/6f1c2a7e-.../sheets/v4/spreadsheets/1BxiM.../values:batchGet?ranges=A1:B2&ranges=D1:D5" \
-H "Authorization: Bearer $OPERATE_AS_API_KEY"Limiting keys to pass-through
A key with an actions list can only make pass-through calls to a service listed as <service>.request, such as hubspot.request. A key with no actions list can make pass-through calls to any service it may use. See API keys.