Documentation
MacroLinker docs
How to connect your company’s outside tools once, hand each app a token, and let those apps use the tools without ever holding the keys.
On this page
MacroLinker is where your company connects the outside services it works with: the CRM, email, chat, files, code and the support desk. You connect each service once. Then the apps you trust, such as MacroCaptain or your own code, can use that service through MacroLinker without ever holding its password or key.
Each app gets its own consumer token. When the app needs something from a service, it sends the request to MacroLinker with that token. MacroLinker checks the app may use the connection, makes the call with your credential, retries if the service has a hiccup, paces calls so no service is flooded, and writes the call to a log. When the call comes from MacroCaptain, MacroLinker also reports what it cost.
MacroCaptain (formerly MacroAgentic) plans and runs work across the Macro agents. There are two kinds of connection, and they live in different places.
Macro agents connect in MacroCaptain
Outside tools connect in MacroLinker
Some agents need both. MacroPipe, for example, is a Macro agent, so its key lives in MacroCaptain. It also works in your CRM, so it needs HubSpot connected in MacroLinker.
When an agent in a MacroCaptain run needs an outside service, it asks MacroCaptain. MacroCaptain decides whether the step may do it and how much it may spend, then sends the call to MacroLinker with a short-lived grant. MacroLinker makes the call and reports the cost back. Today MacroCaptain keeps the MacroLinker token per person: each person pastes their own token in MacroCaptain’s Settings, and a run uses its owner’s token.
- 1
Sign in
Go to macrolinker.com/login and continue with Google, or ask for a sign-in link by email. Your first sign-in creates an organization named after you, with one workspace called Default. You are its owner. - 2
Connect a service
Open Connections and pick a service. Leave it set to Company and All workspaces unless you need otherwise, then choose Connect with a credential and paste the key the card asks for. The card links to the service’s own instructions for creating one. - 3
Create a consumer token
Open Consumers, name the app that will use it, and say who uses it: MacroCaptain or your own code (see Consumers). Choose Issue token and copy it right away. It starts withml_and is shown only once. - 4
Use the token
For MacroCaptain, open MacroCaptain, go to Settings and paste it into MacroLinker consumer token. For your own code, send it as a bearer token to the execute API.
A connection is one service, connected once, with its credential stored encrypted in MacroLinker. You make two choices when you connect.
Company or personal
A company connection belongs to the organization. It keeps working when the person who added it leaves, and it is the only kind an app can use. Only owners and admins can add or remove one. A personal connection belongs to you. Only you can see it, it is never handed to an app, and it leaves with you.
All workspaces or one
An organization can have several workspaces, for example one per team or client. A connection made for All workspaces is shared by every workspace. A connection made for one workspace is private to it, and it wins over the shared one for the same service. Owners and admins create workspaces from the workspace menu at the top of the console.
Which apps may use it
By default every consumer token in the workspace may use a connection. On a connected card you can narrow that to a chosen list of consumers. To cut off access entirely, disconnect the service.
When a connection expires
A connection made by signing in with the service carries a key that expires. When an app uses it close to that time, MacroLinker renews it first. If renewing fails, the connection is marked as needing attention, everyone in the organization gets an alert, and calls fail until someone reconnects it. The Overview page shows how many connections need attention.
These are the services MacroLinker can connect today. Every one can be connected by pasting a credential. Signing in with the service is offered on a card only where it has been switched on for MacroLinker; it is not switched on for any service yet, so today you connect by pasting.
| Service | What you paste | Execute API base address |
|---|---|---|
| Google Drive | Service account JSON | https://www.googleapis.com/Signed-in connections only |
| Slack | Bot token | https://slack.com/api/ |
| GitHub | Personal access token | https://api.github.com/ |
| Intercom | Access token | https://api.intercom.io/ |
| Zoom | Account ID, Client ID, Client secret | https://api.zoom.us/v2/Signed-in connections only |
| Vercel | Access token, Team ID (optional) | https://api.vercel.com/ |
| Snowflake | Account identifier, User, Password or PAT, Warehouse (optional), Role (optional) | https://{account}.snowflakecomputing.com/api/v2/ |
| Salesforce | Access token, Instance URL | https://your-instance.my.salesforce.com/services/data/ |
| Zendesk | Subdomain, Agent email, API token | https://{subdomain}.zendesk.com/api/v2/ |
| Atlassian | Site URL, Account email, API token | https://api.atlassian.com/ |
| Dropbox | Access token | https://api.dropboxapi.com/ |
| HubSpot | Private app token | https://api.hubapi.com/ |
| Gmail | Access token | https://gmail.googleapis.com/ |
| Access token | https://api.linkedin.com/ | |
| Access token, Instagram user ID | https://graph.instagram.com/ |
The base address is where the execute API sends a call. A part in braces, such as {subdomain}, is filled in from what you pasted.
For Google Drive and Zoom, what you paste is a key file or app credentials, not a token the execute API can send. The execute API works with these once they are connected by signing in with the service.
For Zendesk, the execute API needs the pasted credential, because the address is built from a field you paste.
A consumer is an app that may use your connections: MacroCaptain, a script, a service you run. Give each one its own token, so you can cut one off without touching the others.
Used by MacroCaptain, or by your own code
When you issue a token you say who will use it. A token for MacroCaptain only works with grants MacroCaptain signed, with a key that only MacroCaptain and MacroLinker hold. If the token leaks, it reaches nothing on its own. A token for your own code works with grants your code writes itself, so keep it as safe as the services it can reach. Tokens issued before 1 November 2026 count as your own code; to get the protection, issue a new MacroCaptain token and revoke the old one.
The token
A token looks like ml_ followed by 32 random characters. It is shown once, when you issue it. MacroLinker keeps only a fingerprint of it and the last four characters, which the Consumers page shows so you can tell tokens apart. If you lose a token, issue a new one.
Scope
Each token belongs to the workspace that was open when it was issued. It can reach that workspace’s connections and the ones shared with all workspaces, and nothing else: not another workspace, not another organization, and never anyone’s personal connection. The workspace comes from the token itself, so a caller cannot ask for a different one.
Revoking
On the Consumers page, choose Revoke next to the token. It stops working on the next request. Revoked tokens stay listed, marked as revoked, so the history still makes sense. Only owners and admins can issue or revoke tokens, and everyone else in the organization gets an alert when they do.
The execute API makes one call to a connected service on your behalf. The credential never leaves MacroLinker: you name the service and the request, and MacroLinker adds the key.
curl -X POST https://macrolinker.com/api/v1/execute \
-H "authorization: Bearer ml_…" \
-H "content-type: application/json" \
-d '{
"grant": {
"grantId": "grant_8f2c",
"runId": "run_41",
"stepId": "step_3",
"agentId": "macropipe",
"provider": "hubspot",
"issuedAt": "2026-11-01T15:00:00.000Z",
"expiresAt": "2026-11-01T15:05:00.000Z",
"ceilingCents": 500
},
"call": {
"operation": "http",
"params": {
"method": "GET",
"path": "crm/v3/objects/contacts",
"query": {
"limit": "10"
}
}
}
}'{
"ok": true,
"result": {
"status": 200,
"body": { "results": [ … ] }
},
"costCents": 0,
"retries": 0,
"rateLimited": false,
"callId": "cm3x9…"
}The grant
The execute API was built for MacroCaptain, which writes and signs a grant for every approved step. If you call it from your own code with a token for your own code, you write the grant yourself and leave issuer and sig out. The ids are your own labels: they are stored with the call and sent with its cost report.
| Field | Meaning |
|---|---|
provider | The service to call, by its id: hubspot, slack, gmail and so on (see the list below). |
grantId, runId, stepId, agentId | Labels for the grant, the run, the step and the agent making the call. Each must be non-empty. |
issuedAt, expiresAt | Times as ISO 8601 strings. The call is refused once expiresAt has passed. |
ceilingCents | What this grant may spend, in cents. Every call made on the grant counts against it, and a call that would go past it is refused. |
operation | Optional. A label for what the grant allows. |
issuer, sig | Set by MacroCaptain: who signed the grant and the signature. Required for a MacroCaptain token, which refuses a grant whose fields were changed after signing. |
The call
| Field | Meaning |
|---|---|
operation | Always http. Anything else fails without calling the service. |
params.method | GET, POST, PUT, PATCH or DELETE. Defaults to GET. |
params.path | The path, relative to the service's base address. Required. |
params.query | Optional. Query string values, all strings. |
params.headers | Optional. Extra headers. MacroLinker always sets the authorization header itself. |
params.body | Optional. Any JSON value. It is sent as JSON. |
Write the path without a leading slash, so it is added to the base address. With Slack’s base https://slack.com/api/, the path chat.postMessage reaches https://slack.com/api/chat.postMessage. A leading slash starts from the root of the host instead. A path that would leave the service’s host is refused.
Service ids: google_drive, slack, github, intercom, zoom, vercel, snowflake, salesforce, zendesk, atlassian, dropbox, hubspot, gmail, linkedin, instagram.
The response
Once MacroLinker has tried the call, it answers with HTTP 200, even when the service said no. Read ok to know whether the service accepted it.
| Field | Meaning |
|---|---|
ok | True when the service answered with a 2xx status. |
result.status | The service's HTTP status, or 0 if no answer came back. |
result.body | The service's answer: parsed JSON when it is JSON, otherwise the raw text. |
error | Present when ok is false: HTTP 404 and the like, or the reason the call could not be made. |
costCents | What the call cost, in cents. See cost reporting. |
retries | How many times MacroLinker retried before this answer. |
rateLimited | True if the service asked MacroLinker to slow down at any point during the call. |
callId | The id of the call in MacroLinker's log. |
08
Errors
These answers come back before the service is called. Nothing is sent to the service, nothing is logged as a call, and nothing is charged. Apart from the first four, the body also carries "ok": false, "costCents": 0 and "retries": 0.
| Status | Error | What it means |
|---|---|---|
| 401 | missing bearer token | No token was sent. |
| 403 | invalid token | The token is not one MacroLinker issued. |
| 403 | token revoked | Someone revoked this token. Issue a new one. |
| 400 | A list of problems | The body does not match the shape above. |
| 403 | grant expired | expiresAt is in the past. |
| 403 | grant has no remaining budget | ceilingCents is zero or less. |
| 403 | this consumer only accepts grants signed by macrocaptain | A MacroCaptain token was sent with a grant MacroCaptain didn't sign, or one changed after signing. |
| 402 | this call would take the grant past its … budget | The calls already made on this grant plus this one would cost more than ceilingCents. |
| 503 | grant signatures can’t be checked right now | MacroLinker is missing the key it checks MacroCaptain’s grants with. Try again later. |
| 429 | rate limit for … | MacroLinker’s own pacing. Wait for the retry-after header (one second) and try again. The body has "rateLimited": true. |
| 404 | … is not connected | No company connection for this service in the token’s workspace, or an unknown service id. code is not_found. |
| 403 | this consumer is not allowed … | The connection is limited to other consumers. code is forbidden. |
| 409 | The renewal error | The connection expired and could not be renewed. Someone must reconnect it. code is expired. |
| 422 | no API base known for … | The execute API cannot reach this service yet, or the connection lacks the field the address is built from. |
| 409 | connection for … holds no usable credential | The pasted credential is not a token MacroLinker can send. Connect by signing in with the service instead. |
Retries
If the service times out, cannot be reached, answers 408 or 429, or answers with a 5xx error, MacroLinker tries again, up to 3 more times. It waits 0.5 s, then 2 s, then 5 s between tries, or as long as the service’s retry-after header asks. Each try may take up to 60 s. Any other 4xx answer comes straight back, because trying again would not change it.
All of this happens inside your one request, so a slow service can keep the request open for a few minutes. Set your client’s timeout with that in mind.
Rate limits
MacroLinker paces calls to each service: up to 10 at once, then 5 a second. This is a guard against bursts, shared by everyone calling that service, not a quota you buy. A call over the limit gets a 429 straight away and is not sent. Services also have their own limits. When a service answers 429, MacroLinker waits and retries as above, and the response says "rateLimited": true.
One more endpoint takes the same consumer token. It lists what the token may use: company connections that are working and that this consumer is allowed to use, one per service, and never a credential.
curl https://macrolinker.com/api/v1/connections \
-H "authorization: Bearer ml_…"
{
"org": { "id": "…", "slug": "…", "name": "Acme" },
"workspace": { "id": "…" },
"consumer": { "id": "…", "name": "MacroCaptain" },
"connections": [
{
"provider": "hubspot",
"name": "HubSpot",
"authType": "credential",
"capabilities": ["Read contacts & deals", "Update records", "Enroll in sequences"],
"accountLabel": null,
"scopes": [],
"connectedAt": "2026-10-30T18:22:41.000Z"
}
]
}MacroLinker never hands a credential to a consumer. An older endpoint that did, POST /api/v1/connections/<service>/token, was retired on 1 November 2026 and now answers 410. Send the call through the execute API instead, so it is paced, logged and costed.
What is checked on every call
Before the execute API calls a service, MacroLinker checks, in order: that the token is valid and not revoked; that the grant has not expired; for a MacroCaptain token, that MacroCaptain signed the grant and nothing in it changed since; that the calls already made on the grant plus this one fit its budget; that the service is within its pace; that the token’s workspace has a company connection for the service; and that this consumer is on the connection’s list, if it has one. Finally, the path must stay on the service’s own host. The larger decisions, such as which agent may use which service and how much a run may spend, are made in MacroCaptain before the call is sent.
The audit trail
MacroLinker keeps two records. Every time a credential is used, whether by the execute API or handed out by the credential API, it records which connection, which consumer and when. It never records the credential. The Overview page shows the latest of these as Recent access.
Every call the execute API tries is also logged: the consumer, the service, the grant, run, step and agent ids, the method and path, the service’s status, whether it succeeded, how many retries it took and what it cost. Query strings, headers and the bodies sent and received are not stored. This call log is not shown in the console yet.
Each call through the execute API has a cost, set per service. Today it is zero for every service except Snowflake, which counts 1 cent a call. Calls refused before they reach the service cost nothing.
After each call, MacroLinker reports the cost to MacroCaptain with the call id, the service and the run, step and agent it belongs to, so MacroCaptain can book it against the run. MacroCaptain counts each call id once, so a repeated report is never charged twice. If the report cannot be delivered, MacroLinker notes the failure and keeps its own record of the call; it does not hold up your answer.
13
Alerts
The bell at the top of the console shows alerts for the workspace you have open. Everyone in the organization gets them. MacroLinker raises one when a connection expires and cannot be renewed; when someone issues or revokes a consumer token (the person who did it is not alerted); when 5 or more calls to one service fail within 15 minutes; when calls to a service are being slowed down; and when a workspace’s call costs run unusually high within an hour. Repeat alerts about failures, slowdowns and costs are sent at most once an hour, so a bad afternoon does not bury you.
You sign in to MacroLinker with Google or with a sign-in link sent to your email. Your account is your verified email address. If you first signed in with Google, a sign-in link to the same address opens the same account. The other way round does not work today: if you started with an emailed link, keep using emailed links.
Each Macro product has its own account. Across Macro products, accounts are treated as the same person only when they use the same verified email address. Nothing is matched by name or company. MacroLinker and MacroCaptain are tied together by the consumer token you paste, not by your sign-in.
If you use a different email in another Macro product, create an account there with the same email you use here, then contact support to ask whether your data can be moved.
Signing out, from the Account page, ends your session in that browser only. Your connections and tokens keep working, so apps are not interrupted.
15
Billing
MacroLinker is in early access. It has no paid plans yet and nothing in it charges you. The per-call cost above is reported to MacroCaptain for its budgets and records; it is not a MacroLinker bill.
When paid plans start, a MacroLinker account will be paid either directly or through MacroCaptain (see Coming next). Links between Macro products, such as the token MacroCaptain uses to reach MacroLinker, are free on every plan.
16
Security
Credentials are encrypted before they are stored, with AES-256-GCM and a key kept apart from the database. They are decrypted only at the moment a call is made or a credential is handed out. They never reach the browser: the console shows a masked hint, such as xox…9f2c, so you can tell which key is connected. When you paste a credential, only the fields that service asks for are kept.
Consumer tokens are stored only as a SHA-256 fingerprint, so not even MacroLinker can show one again. A token is tied to one workspace in one organization, and that is read from the token, never from the request. Personal connections are never handed to an app. Answers that carry a credential are marked so they are never cached.
The execute API sends calls only to the service’s own host, and it always sets the authorization header itself, so a caller cannot redirect a key elsewhere. What is logged is listed under the audit trail: who used which connection and when, and the outline of each call. Credentials, headers and the content of requests and answers are not logged.
Not live yet
These are planned. None of them works today.
One-click Connect from MacroCaptain
Link MacroCaptain to MacroLinker with one click, so nobody has to copy and paste a token.
One token per company
The MacroLinker token in MacroCaptain will belong to the company or workspace instead of to each person.
Billing through MacroCaptain
Accounts that MacroCaptain creates will be billed through MacroCaptain at each product's list price, with package discounts for using several products.