We connected Instinct to a flight API through Swytchcode. Here's how it went
Instinct has no way to plug in custom APIs, so we tried connecting it to Duffel's flight API through a Google Sheet, with Swytchcode holding the keys and the rules. How it works, and an honest look at how Swytchcode did.

Instinct is an AI assistant you text on WhatsApp, iMessage or SMS. It works through a fixed list of connectors (Gmail, Calendar, Sheets, Notion, Slack and others), and it can't plug in custom ones today.
Watch the run: How to connect Instinct AI to a custom API with Swytchcode.
We wanted to try two things:
- Can an assistant like that reach an API it has no connector for?
- How does Swytchcode do as the layer in between, holding the keys and enforcing the rules, in a setup we didn't design it for?
We picked Duffel flight booking in test mode (a fake airline, no real money). Booking means spending, so it's a good test of limits and approvals.
The setup
Instinct ─▶ Google Sheet ─▶ bridge ─▶ Swytchcode ─▶ Duffel (flights)
│ ─▶ Resend (email)
├─ holds the keys
├─ checks each call against policies.json
└─ asks in Slack or Telegram when a rule says soInstinct can edit a Google Sheet, so that's how it hands over a request. A small Python bridge reads each new row and makes the calls through Swytchcode. Swytchcode holds the Duffel, Google and Resend keys, checks every call against a rules file, and sends approvals to Slack.
A run looks like this:
Me: Use only this Google Sheet: https://docs.google.com/spreadsheets/d/1PI8_1KIRAOb-Bdnc0JVhn19dTrykclmG_ONhUOBUla6I. Read its "How to use" tab and follow it. When I ask for flights, add a row to the Requests tab, wait 15 seconds, then read me the result.
Me: Find me a flight from London to New York on 2027 Mar 19 under $500.
Instinct: I've read the instructions and will use only this new sheet: add a Requests row, wait 15 seconds, then read you the result. I won't reuse any previous customer or flight details.
Instinct: Trips returned three nonstop options for March 19, 2027:
1. British Airways: $217.38
2. American Airlines: $217.92
3. Iberia: $222.53
All are listed as departing at 13:56 and arriving at 17:59 the same day. These are the sheet's results, not independently verified fares or time zones.
The rules
The rules are one JSON file in the project. Here are four of the ten:
{
"version": 2,
"defaults": { "on_no_match": "deny" },
"policies": [
{
"id": "trip-cap-ticket",
"target": ["duffel_flights.order.create"],
"when": { "field": "body.data.payments[0].amount", "operator": ">", "value": 300 },
"action": { "type": "POLICY_BLOCKED", "message": "Over your $300 per ticket limit" }
},
{
"id": "trip-cap-day",
"target": ["duffel_flights.order.create"],
"when": { "operator": "sum", "field": "body.data.payments[0].amount", "window": "24h", "max": 500 },
"action": { "type": "QUOTA_EXCEEDED", "message": "Over your $500 a day limit" }
},
{
"id": "trip-only-found",
"target": ["duffel_flights.order.create"],
"when": { "operator": "not", "conditions": [{
"field": "body.data.selected_offers[0]", "operator": "returned_by",
"value": { "tools": ["duffel_flights.offer_request.create"], "field": "data.offers[*].id" },
"window": "1d" }] },
"action": { "type": "POLICY_BLOCKED", "message": "That flight didn't come from a search" }
},
{
"id": "trip-approve-booking",
"target": ["duffel_flights.order.create"],
"when": { "field": "body.data.payments[0].amount", "operator": ">", "value": 100 },
"action": { "type": "REQUIRES_APPROVAL",
"message": "Book {{body.data.metadata.flight}} for {{body.data.passengers[0].given_name}} {{body.data.passengers[0].family_name}}, {{body.data.payments[0].amount}} {{body.data.payments[0].currency}}?" },
"approval_timeout": "10m"
}
]
}The others allow only the 13 calls the demo uses, keep bookings in USD, cap bookings at 2 a day, ask before cancelling, limit Google access to the one sheet, and cap emails at 10 a day.
How Swytchcode did
What worked well
- No keys in the bridge. The bridge has no Duffel, Google or Resend key, and Instinct never sees one. Keys go in once with
swytchcode auth connect(a hidden prompt, or a sign-in link for Google). - Duffel needed no custom code. Swytchcode made tools from Duffel's API spec, like
duffel_flights.order.create, and the bridge just calls them by name. - The rules did what they said. A business-class booking over $300 was refused before it reached Duffel, with the rule's message, and the bridge passed that message on. [after run: confirm with the recorded run]
- The less obvious rules were easy to express. The daily
sum, thereturned_bycheck that a booking uses a flight a search returned, andon_no_match: denywere a few lines each. - Rules come with tests.
swytchcode policy validateruns 11 tests against the 10 rules, so you can check a rule does what you meant before trusting it. - Approvals carry real detail. The Slack message can include fields from the call, so it shows the flight, the passenger and the price, not just "approve booking?".
What was rough
- Approvals are slow. Swytchcode checks for a decision every 2 minutes, so a booking can take up to 2 minutes after you tap Approve. The bridge tells Instinct the time to expect.
- You don't get the approved call's reply. Swytchcode makes the call itself once it's approved, so the bridge never sees Duffel's response. The bridge tags each booking and looks it up in Duffel instead. It works, but every integration with approvals has to do something like it.
- Slack messages sometimes arrived twice. It looks like a race in our backend; we're looking into it.
- Outcomes were sometimes hard to tell apart. The CLI uses the same exit code when a rule blocks a call and when an approval request itself is refused, so the bridge has to read the error text. And when a call times out, you can't tell whether it went through; the bridge has to check Duffel before saying "not booked". A code review caught that one.
- Registry gaps. Our Resend entry had an empty endpoint, so we patched it in the project's manifest. There's no ready-made Google Sheets connection or shared Slack bot yet, so for now you set up your own Google OAuth client and Slack bot.
- Small setup snags. The project's workspace has to exist before the first call; the setup script now creates it.
None of these stopped the experiment, but they're the difference between "it works for us" and "it works for you in ten minutes". We're working through them.
Not about Swytchcode, but worth knowing
- Instinct reads results when it next checks the sheet, not live. Sometimes you have to ask "did it go through?"
- It used an older sheet with the same name until we gave it the URL.
- Duffel's test mode ignores the departure time, so the bridge filters by time itself.
- Resend's test sender only delivers to your own sign-up address.
What we took from it
A Google Sheet is enough for an assistant with no custom connectors to reach a new API, slowly. Keeping the keys and rules in Swytchcode, outside both the assistant and the bridge, meant the assistant only ever asked for things, and the rules decided. That part held up well. The rough edges were mostly around approvals and setup, and they're on our list.
Next we're trying assistants that support MCP, like OpenClaw and Hermes, which can connect to Swytchcode directly with swytchcode mcp serve, no sheet needed.
Try it
The code is open source and runs in Duffel's test mode:
git clone https://github.com/swytchcodehq/swytchcode-examples
cd swytchcode-examples/instinct-book-my-trip/instinct-bridge
./setup.shIt needs Docker. docker compose run --rm bridge python check.py runs the same flow without Instinct.
This is built on top of Instinct, not an Instinct feature. We have no affiliation with Instinct.
Tried something similar, or want to see another API wired up? Tell us.
More from the blog
I tested 10 self-hosted AI agents against our guardrails. Here's what we had to change.
Making API calls boringly reliable
Retries, idempotency, and settings that survive upgrades.
A Prompt Is Not a Policy: guardrails and human approval for AI agents
Your agent wants to refund $5,000. Who says yes?
