# Outside APIs: declared origins, OAuth and published adapters

Call outside APIs from a module Block with each person's own key or OAuth sign-in, through declared origins and reviewed OpenAPI adapters, with keys kept on StillMade's servers.

Module Blocks can call any outside API with the account of the person running
them. Three pieces work together:

- **Declared origins.** `permissions.network` lists the exact https origins the
  Block calls (up to 8), each with the key it needs. Code calls
  `stillmade.net.fetch(url, {method, headers, body})` and gets a standard
  `Response`.
- **OAuth.** A key can be an OAuth sign-in through a reviewed StillMade OAuth
  app: `{scheme: "oauth2", app: "notion", label: "Notion workspace"}`.
- **Published adapters.** `permissions.connections` lists approved OpenAPI
  adapters by app ID (`app:openapi:notion`). Code calls
  `stillmade.connections.call(appId, operationId, input)` and gets the checked
  JSON result.
- **Catalog apps.** `permissions.connections` can also list apps from
  StillMade's connections catalog (Composio) by app ID, such as `app:notion`,
  `app:gmail` or `app:google-sheets`. Code calls
  `stillmade.connections.call(appId, actionName, input)` with the service's
  action name, and the action runs with the account the person connected. See
  [catalog apps](#catalog-apps).

```json
"permissions": {
  "project": [],
  "connections": ["app:openapi:notion"],
  "network": [
    "https://api.example.com",
    {"origin": "https://api.airtable.com", "auth": {"scheme": "bearer", "label": "Airtable personal access token", "help": "https://airtable.com/create/tokens"}}
  ],
  "filesystem": [],
  "secrets": []
}
```

```js
const page = await stillmade.connections.call('app:openapi:notion', 'retrievePage', { path_page_id: input.pageId });
const response = await stillmade.net.fetch(`https://api.airtable.com/v0/${input.baseId}/Pages`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ records: [{ fields: { Name: 'New row' } }] }),
});
if (!response.ok) throw new Error(`Airtable answered ${response.status}`);
```

### Catalog apps

```js
const page = await stillmade.connections.call('app:notion', 'NOTION_CREATE_NOTION_PAGE', {
  parent_id: input.parentPage,
  title: input.title,
});
```

- The first time a person runs the Block, StillMade asks them to connect their
  account for the app (they sign in to the app in a new window) and to allow this
  Block to use it. Then, once per session for each action, it shows what the
  action does (reads, makes changes, sends, publishes or deletes, or that
  StillMade has not reviewed it) and its price.
- The action runs on StillMade's servers with that account. The Block gets the
  checked result; the account and its tokens never reach it. People remove a
  Block's access in its network access panel.
- Credits are charged to the person running the Block: the action's reviewed
  price, or 1 credit per action when StillMade has not reviewed it. If the app
  clearly fails, the credits are returned.
  If it may have carried out the action (for example a timeout), the credits
  are kept and the person is told to check the app before running it again.
- Actions reviewed as spending money on the person's account, or priced by
  usage, run only as reviewed workflow Steps. Actions that look like moving
  money (payments, charges, transfers, orders, refunds) and payment apps such
  as Stripe or PayPal need a reviewed price first. For any other action
  StillMade has not reviewed, the approval tells the person it may change,
  send or pay for things in their account.
- Offline, answer calls in `tests/mocks.json` under
  `connections["app:notion/NOTION_CREATE_NOTION_PAGE"]`.

### Keys belong to the people running the Block

Key schemes are `bearer` (the `Authorization` header), `header` (a named header,
for example `X-Api-Key`), `query` (a named URL parameter) and `oauth2`. The first
time a person runs the Block, StillMade asks for their key in its own dialog, or
opens the service's sign-in in a new window. The key is stored encrypted for that
person and that Block only (another Block declaring the same service gets
nothing), added to requests on StillMade's servers, and never reaches the
Block's code. People can replace or remove keys from the Block's Network access
panel. Listing pages and previews show the declared services before anyone runs
the Block.

### What the host enforces

- Requests go only to declared origins of a saved Block the person can open, over
  https on the standard port, to public internet addresses. Each connection is
  pinned to the address that was checked, and redirects are followed (up to 3)
  only to declared origins; a key is never sent across a redirect.
- Block code cannot set the key's header or parameter, host-owned headers
  (`Host`, `Cookie`, `Origin`, `Content-Length` and similar) or a body on GET.
- Limits: 4 MiB per request, 8 MiB per response (compressed responses are
  measured after decompression), 30 seconds per request including redirects, and
  120 requests a minute per person and Block.
- A response that contains the key (raw, URL-encoded or base64) is withheld.

### OAuth apps

A creator submits the provider's authorization and token endpoints, scopes,
client ID and secret with `POST /api/blocks/network/oauth-apps`. StillMade
reviews the app before any Block can use it. Sign-in uses the authorization code
flow with PKCE; StillMade keeps the tokens and refreshes them.

### Published OpenAPI adapters

Import an API's OpenAPI document as an API connection, then submit it with
`POST /api/blocks/network/adapters`: `{connectionId, slug, name, auth, headers}`,
where `auth` is the key it needs and `headers` are constant headers the API
requires (such as a version header). The definition is copied when submitted.
After StillMade approves it, the adapter has a public app ID, appears in
`GET /api/blocks/network/adapters`, and any module Block can declare it. An
approved adapter's origin also counts as reviewed for API connection read tests.

`examples/module-notion-to-airtable` reads a Notion page through a published
adapter (with the person's Notion sign-in) and adds an Airtable row through a
declared origin (with their own token).
