# aipotheosis-labs/aci

> Self-hostable FastAPI + pgvector tool-calling backend: 98 JSON-defined apps, multi-tenant OAuth/API-key accounts, KMS-encrypted secrets.

- Category: [API layer & connectors](https://llms-technical-reviews.com/connectors/)
- Repository: https://github.com/aipotheosis-labs/aci (reviewed at commit `3e4a82fa5fd22f1165af2b39fa3de2b0f031242e`, 2026-05-28)
- Stars: 4906 · Language: Python · License: Apache-2.0
- Canonical page: https://llms-technical-reviews.com/p/aci/

## Overview

ACI.dev is a multi-tenant tool-calling backend. It is a FastAPI server on PostgreSQL with pgvector, plus a Next.js developer portal. Agents use it to search, inspect and run calls against third-party APIs through one API key. In this repo, an integration is data, not code. Each app is an `app.json` plus a `functions.json` under `backend/apps/`. The pinned SHA has 98 apps and 987 functions: 971 of them are plain REST calls described in JSON Schema, and 16 are handled by Python "connector" classes.

The platform has a clear tenancy model. An organization owns projects. A project owns agents, and each agent has one API key. End users connect their own accounts as **linked accounts**, keyed by a `linked_account_owner_id` that you choose. Credentials are encrypted with AWS KMS envelope encryption, and OAuth2 tokens are refreshed at call time. This suits teams that build an agent product for many end users and want hosted-style OAuth on their own infrastructure. The "Unified MCP server" that the README features is **not** in this repo. It lives in `aci-mcp`, a thin client of this API, and the Python SDK is also a separate repo.

## Architecture

```mermaid
flowchart LR
  AG["Agent / SDK / MCP client"] -->|"X-API-KEY"| API["FastAPI server"]
  P["Dev portal (Next.js)"] -->|"PropelAuth JWT"| API
  API --> RL["RateLimitMiddleware"]
  API --> SR["/functions/search"]
  API --> EX["/functions/{name}/execute"]
  SR --> PG[("Postgres + pgvector")]
  SR --> OAI["OpenAI embeddings"]
  EX --> SCM["security_credentials_manager"]
  SCM --> KMS["AWS KMS"]
  EX --> FE["get_executor"]
  FE --> REST["REST executors (httpx)"]
  FE --> CON["Connector classes"]
  REST --> TP["Third-party API"]
  CON --> TP
  CLI["aci CLI upsert-app"] --> PG
```

| Component | Path | Role |
|---|---|---|
| API server | `backend/aci/server/main.py` | FastAPI app, middleware, routers |
| Function routes | `backend/aci/server/routes/functions.py` | Search, get definition, execute |
| Linked accounts | `backend/aci/server/routes/linked_accounts.py` | API-key, no-auth and OAuth2 account linking, plus the callback |
| OAuth2 | `backend/aci/server/oauth2_manager.py` | Authlib `AsyncOAuth2Client` wrapper (PKCE S256, refresh) |
| Credentials | `backend/aci/server/security_credentials_manager.py` | Resolves the scheme and credentials, refreshes expired tokens |
| Executors | `backend/aci/server/function_executors/` | REST (API key / OAuth2 / no-auth) and connector executors |
| Connectors | `backend/aci/server/app_connectors/` | Python classes for Gmail, E2B, Vercel, OneDrive, etc. |
| DB models | `backend/aci/common/db/sql_models.py` | Project, Agent, APIKey, App, Function, AppConfiguration, LinkedAccount |
| Encryption | `backend/aci/common/encryption.py`, `db/custom_sql_types.py` | KMS keyring, encrypted column types, HMAC key lookup |
| Integrations | `backend/apps/<app>/` | `app.json` and `functions.json` definitions |
| CLI | `backend/aci/cli/commands/` | `upsert-app`, `upsert-functions`, `create-project`, etc. |
| Portal | `frontend/` | Next.js portal for app configs, linked accounts, playground, logs |

## How a request flows

1. `POST /v1/functions/{name}/execute` goes through `RateLimitMiddleware`, which applies per-IP per-second and per-day moving windows held in process memory ([ratelimit.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/middleware/ratelimit.py#L19-L72)). Then the router dependencies `validate_api_key` and `validate_project_quota` run ([main.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/main.py#L139-L150)). The key is found by its HMAC-SHA256, not by decrypting stored keys ([dependencies.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/dependencies.py#L48-L80)).
2. `execute_function` loads the `Function`. It checks that the project has an enabled `AppConfiguration`, that the app is in the agent's `allowed_apps`, that the function is enabled, and that an enabled `LinkedAccount` exists for the owner id ([functions.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/routes/functions.py#L318-L433)).
3. `get_security_credentials` chooses the code path by scheme. For OAuth2, it refreshes the token if it has expired, keeps a rotated refresh token, and marks the result `is_updated`. `update_security_credentials` then saves the new tokens ([security_credentials_manager.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/security_credentials_manager.py#L33-L139)).
4. `custom_instructions.check_for_violation` runs only if the agent has an instruction for this function. It asks `gpt-4o-mini` (the default) whether the call breaks the instruction ([custom_instructions.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/custom_instructions.py#L19-L45)).
5. `get_executor` matches `(protocol, security_scheme)` to an executor ([function_executors/__init__.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/function_executors/__init__.py#L21-L32)). `FunctionExecutor.execute` validates the input against the *visible* schema, adds the defaults for invisible required fields and drops `None` values ([base_executor.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/function_executors/base_executor.py#L31-L82)).
6. The REST executor builds the URL from `server_url + path`, fills in the path params, adds the credential to the header, query, body or cookie, and sends one synchronous `httpx` request (10 s connect, 30 s read). An HTTP error becomes `FunctionExecutionResult(success=False)` ([rest_function_executor.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/function_executors/rest_function_executor.py#L36-L103), [rest_oauth2_function_executor.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/function_executors/rest_oauth2_function_executor.py#L15-L52)).
7. The route logs the input and the result to Logfire, truncated if they are too large ([functions.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/routes/functions.py#L214-L240)).

## Key components

### Integration definitions

`app.json` holds the metadata and the `security_schemes`. OAuth client secrets are Jinja placeholders such as `{{ AIPOLABS_GITHUB_APP_CLIENT_ID }}` ([github/app.json](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/apps/github/app.json#L1-L27)). Each function names itself `APP__ACTION` and sets `protocol: "rest"` with `method`, `path` and `server_url`. Its parameters are split into `header`/`path`/`query`/`body` objects, and a `visible` list controls what the LLM sees ([github/functions.json](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/apps/github/functions.json#L1-L45)). `upsert-app` renders the secrets, validates the result with Pydantic, embeds the app with OpenAI and upserts it. It is a dry run unless you pass `--skip-dry-run` ([upsert_app.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/cli/commands/upsert_app.py#L44-L103)). Functions are loaded by a separate `upsert-functions` command. Every definition is written by hand. There is no OpenAPI import.

### Connector functions

When `protocol: "connector"` is set, the function name selects the code: `GMAIL__SEND_EMAIL` becomes module `aci.server.app_connectors.gmail`, class `Gmail`, method `send_email`. The executor imports that module with `importlib` and creates a new instance for each call ([connector_function_executor.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/function_executors/connector_function_executor.py#L31-L88), [base.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/app_connectors/base.py#L19-L75)).

### Discovery

`GET /v1/functions/search` embeds the `intent` and orders the functions by pgvector cosine distance. It can limit the results to functions the agent may use (`allowed_only`), and it returns OpenAI, OpenAI Responses, Anthropic or basic schemas ([functions.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/routes/functions.py#L66-L156), [L277-L315](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/routes/functions.py#L277-L315)). `meta_functions.py` defines `ACI_SEARCH_FUNCTIONS`, `ACI_GET_FUNCTION_DEFINITION` and `ACI_EXECUTE_FUNCTION`, so an agent can do this loop by itself ([meta_functions.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/agent/meta_functions.py#L1-L30)). `/v1/agent/chat` is the portal playground: it streams `gpt-4o` with the functions you selected ([agent.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/routes/agent.py#L33-L68)).

### Credentials and OAuth

`link_oauth2_account` creates a PKCE verifier and puts it, together with the project and owner ids, into a **signed but unencrypted** JWT `state`. Then it returns the provider URL. The callback decodes the state, checks that the `client_id` matches, exchanges the code and stores the linked account ([linked_accounts.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/routes/linked_accounts.py#L312-L398)). A project can replace the app's OAuth client with its own through `security_scheme_overrides` ([security_credentials_manager.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/security_credentials_manager.py#L228-L235)). Linked-account credentials sit in a JSONB column. `EncryptedSecurityCredentials` encrypts only the secret fields in it (`secret_key`, `access_token`, `refresh_token`, `client_secret` and the raw token response) ([custom_sql_types.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/common/db/custom_sql_types.py#L84-L120)). Platform API keys use the `Key` column type, which encrypts on write and decrypts on read through the AWS Encryption SDK with a KMS keyring ([custom_sql_types.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/common/db/custom_sql_types.py#L29-L48), [encryption.py](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/common/encryption.py#L17-L55)).

## Extending it

- **New REST integration:** add `backend/apps/<name>/app.json` and `functions.json` (see `INTEGRATION_GUIDE.md`), then run `upsert-app` and `upsert-functions`. You write no Python.
- **Logic that JSON cannot describe:** add an `AppConnectorBase` subclass in `app_connectors/<app>.py` and set `protocol: "connector"`.
- **Agent policy:** per-agent `allowed_apps`, per-config `enabled_functions`, and per-function natural-language `custom_instructions`.

## Running it

`backend/compose.yml` starts `pgvector/pgvector:pg17`, LocalStack with only KMS enabled (an init script creates the key), a PropelAuth mock, the server on port 8000, and a `runner` container that runs `alembic upgrade head` ([compose.yml](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/compose.yml#L1-L110)). Dev mode bind-mounts a fake `propelauth_fastapi` module into the virtualenv. You need an OpenAI key for embeddings, and you seed apps with the CLI. In production you need a real KMS key, PropelAuth for the portal, and Stripe if you use billing. Sentry and Logfire are optional.

## Strengths and caveats

- **Strength:** adding a REST integration needs only declarative JSON, and the visible/invisible split hides auth and boilerplate parameters from the model.
- **Strength:** the security model is real: KMS envelope encryption, HMAC key lookup, token refresh at call time, and permissions per agent and per function.
- **Caveat:** the REST executor makes a blocking `httpx.Client` call inside an `async` route, and it creates a new client for each call. The code has `TODO: add retry`. There is no retry, backoff or pagination helper.
- **Caveat:** the rate limiter is per IP and uses in-process `MemoryStorage`, so the limits are not shared across replicas.
- **Caveat:** the PKCE `code_verifier` travels inside the readable `state` JWT, and the state has no expiry (there is a `TODO`). This weakens what PKCE is meant to protect.
- **Caveat:** there are no triggers, webhooks for app events or data sync. The only webhooks are PropelAuth sign-up and Stripe billing. MCP and the SDK are in other repos.
- **Caveat:** search, app upsert and custom-instruction checks all call OpenAI, so self-hosting is not provider-neutral.

*Sources: code at 3e4a82f, deepwiki-open wiki (13 pages), OpenDeepWiki wiki (17 pages), verified Q&A.*

## How aipotheosis-labs/aci answers the API layer & connectors questions

### How is third-party authentication implemented? (answered)

Third-party authentication uses three mechanisms. **PropelAuth** handles web portal auth (JWT tokens via HTTPBearer) — every user-facing route depends on `auth.require_user` (main.py:136, acl.py:15-19). **API keys** are for agent/programmatic access: validated by `validate_api_key` in dependencies.py (lines 48-69), which looks up the key via `crud.projects.get_api_key`, checks status (active/disabled/deleted), and returns a UUID key ID. API keys are stored encrypted at rest via the `Key` SQLAlchemy type decorator (custom_sql_types.py:29-48) which calls `encryption.encrypt()` using AWS KMS. A SHA-256 HMAC of the key is also stored for lookups. **Linked accounts** store end-user credentials per app per project (sql_models.py:398-455). The `security_credentials` JSONB column uses `EncryptedSecurityCredentials` (custom_sql_types.py:84-157) which encrypts individual fields (secret_key, access_token, refresh_token) via AWS KMS. The `OAuth2Manager` (oauth2_manager.py:16-162) handles the full OAuth2 code flow — `create_authorization_url` builds state-JWT-encoded redirect URLs, `fetch_token` exchanges codes with PKCE (S256), and `refresh_token` rotates expired tokens. Token expiry is checked in `security_credentials_manager.py:213-216` and auto-refreshed at execution time (lines 98-132). App-level OAuth2 client credentials can be overridden per configuration via `security_scheme_overrides` (sql_models.py:351-354). The multi-tenant model links each API key → agent → project → org (sql_models.py:69-127).


Citations: [backend/aci/server/dependencies.py:48-69](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/dependencies.py#L48-L69) · [backend/aci/server/oauth2_manager.py:16-162](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/oauth2_manager.py#L16-L162) · [backend/aci/common/db/custom_sql_types.py:84-157](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/common/db/custom_sql_types.py#L84-L157) · [backend/aci/common/encryption.py:1-55](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/common/encryption.py#L1-L55) · [backend/aci/server/security_credentials_manager.py:87-139](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/security_credentials_manager.py#L87-L139) · [backend/aci/common/db/sql_models.py:390-460](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/common/db/sql_models.py#L390-L460)

### How is an integration / connector defined? (answered)

An integration (called an **App**) is defined declaratively as two JSON files in `backend/apps/{app_name}/`. The `app.json` file contains metadata: name, display_name, provider, version, description, categories, visibility, active status, security_schemes (with OAuth2 URLs, scopes, and placeholder client_id/client_secret as Jinja2 template variables), and default_security_credentials_by_scheme (apps/github/app.json:1-27). The `functions.json` file is an array of function definitions, each specifying name, description, tags, visibility, active, protocol (`"rest"` or `"connector"`), protocol_data (e.g. `{"method": "GET", "path": "/repos/...", "server_url": "..."}` for REST), and parameters as a JSON Schema (apps/github/functions.json:1-80). Parameters have `visible` and `invisible` properties — invisible params inject security defaults at execution time. The `upsert-app` CLI command (cli/commands/upsert_app.py:44-47) renders templates with environment-specific secrets, validates the JSON via `AppUpsert` Pydantic model, generates an OpenAI embedding for semantic search, and upserts into the `apps` and `functions` tables (upsert_app.py:84-103). There are **98 integrations** in `backend/apps/` (from accredible to zoho_desk). Most REST integrations are pure config; 10 have custom Python connector code in `server/app_connectors/` (base.py, gmail.py, e2b.py, etc.). Versioning is a free-form string field per app; there is no automated OpenAPI codegen — integrations are manually authored.

> **Editor's note.** Correction: `upsert-app` registers the app only. Functions are loaded by a separate `upsert-functions` command.

Citations: [backend/apps/github/app.json:1-27](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/apps/github/app.json#L1-L27) · [backend/apps/github/functions.json:1-80](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/apps/github/functions.json#L1-L80) · [backend/aci/cli/commands/upsert_app.py:44-103](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/cli/commands/upsert_app.py#L44-L103) · [backend/aci/common/schemas/app.py:20-64](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/common/schemas/app.py#L20-L64) · [backend/aci/common/schemas/function.py:32-83](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/common/schemas/function.py#L32-L83)

### How are integrations exposed to LLM agents? (answered)

Integrations are exposed to LLM agents via three mechanisms. **Semantic search** is the primary discovery path: `GET /v1/functions/search` accepts a natural language `intent`, generates an OpenAI embedding, and performs a pgvector similarity search across all function embeddings (functions.py:66-156). Results are returned in OpenAI, Anthropic, or basic formats via `format_function_definition` (functions.py:277-315). **Meta-functions** give agents a structured tool to discover tools: `ACI_SEARCH_FUNCTIONS`, `ACI_GET_FUNCTION_DEFINITION`, and `ACI_EXECUTE_FUNCTION` are defined as OpenAI function-calling schemas in meta_functions.py:10-83. Agents call these meta-functions to find, inspect, and invoke integrations at runtime. **Agent chat** (`POST /v1/agent/chat`) provides a streaming OpenAI-compatible chat endpoint that resolves tool definitions from named functions, converts the conversation format, and streams GPT-4o responses with tool calls (agent.py:33-68, prompt.py:62-108). The Unified MCP server is NOT in this repository — it lives in the separate aci-mcp repo (README.md:21-24), referenced as an external service that wraps the ACI.dev REST API. On the SDK side, the README references a Python SDK (aci-python-sdk). **App-level filtering** ensures agents only see functions from their `allowed_apps` and enabled app configurations (functions.py:96-131).


Citations: [backend/aci/server/routes/functions.py:66-156](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/routes/functions.py#L66-L156) · [backend/aci/server/routes/functions.py:277-315](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/routes/functions.py#L277-L315) · [backend/aci/server/agent/meta_functions.py:1-83](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/agent/meta_functions.py#L1-L83) · [backend/aci/server/agent/prompt.py:62-108](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/agent/prompt.py#L62-L108) · [backend/aci/server/routes/agent.py:25-68](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/routes/agent.py#L25-L68)

### How is a tool call executed? (answered)

Function execution is a multi-step pipeline triggered at `POST /v1/functions/{function_name}/execute`. The `execute_function` in functions.py (318-486) runs these stages: (1) **Lookup** — fetches the Function DB record by name. (2) **Authorization chain** — validates the app's AppConfiguration exists and is enabled, checks the function's app is in the agent's `allowed_apps`, verifies the function is in the enabled list, and confirms the LinkedAccount exists and is enabled (functions.py:364-433). (3) **Credential resolution** — calls `security_credentials_manager.get_security_credentials` which handles OAuth2 token expiry/refresh transparently (security_credentials_manager.py:33-49, 87-139). (4) **Custom instruction validation** — runs the function input against the agent's custom_instructions via GPT-4o (functions.py:451-456). (5) **Execution** — selects an executor via `get_executor(function.protocol, linked_account)` (function_executors/__init__.py:21-32). For REST protocol, `RestFunctionExecutor._execute` constructs an httpx request by combining the server_url, path, path params, query, header, cookie, and body from the function_input, then injects credentials and sends it (rest_function_executor.py:37-103). **No sandboxing** is applied — REST calls go directly to third-party APIs with a 10s/30s timeout. For the CONNECTOR protocol, `ConnectorFunctionExecutor` dynamically imports and instantiates a Python class from `server/app_connectors/` (e.g., `Gmail`, `E2B`, `Vercel`) via importlib (connector_function_executor.py:31-61). BaseExecutor validates input against the JSON Schema and injects invisible default values before delegation (base_executor.py:32-82). **Rate limiting** is IP-based via `RateLimitMiddleware` (ratelimit.py:19-72) with per-second and per-day limits using a moving window. **Daily/monthly quotas** are enforced per project in `validate_project_quota` and `validate_monthly_api_quota` (dependencies.py:86-158). Results and errors are logged with structured telemetry to Logfire, including input/output truncation at 8KB (functions.py:239-273). There is no explicit retry or pagination mechanism in the executor — pagination is delegated to each API's own parameters.

> **Editor's note.** Correction: the custom-instruction check defaults to `gpt-4o-mini`, not GPT-4o (`custom_instructions.check_for_violation`).

Citations: [backend/aci/server/routes/functions.py:318-486](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/routes/functions.py#L318-L486) · [backend/aci/server/function_executors/connector_function_executor.py:31-88](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/function_executors/connector_function_executor.py#L31-L88) · [backend/aci/server/function_executors/base_executor.py:31-82](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/function_executors/base_executor.py#L31-L82) · [backend/aci/server/middleware/ratelimit.py:19-72](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/middleware/ratelimit.py#L19-L72) · [backend/aci/server/dependencies.py:86-158](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/dependencies.py#L86-L158)

### How are data sync, webhooks and triggers implemented? (not applicable)

The repository does not implement scheduled data syncs, incremental cursors, webhook ingestion for external app data, or event triggers for agents. The two webhook endpoints in the repo are platform-internal: `/v1/webhooks/auth/user-created` (webhooks.py:24-124) handles PropelAuth user signup and auto-provisions a project and agent, verified via Svix signatures; `/v1/billing/webhook` (billing.py:183-200) consumes Stripe webhooks for subscription lifecycle. Neither receives external app webhook payloads (e.g., Slack events, GitHub push hooks). There is no background task scheduler, no cron-based sync, and no cursor-based incremental data sync. Each integration call is purely request-driven — the platform acts as a proxy, not a sync engine.


Citations: [backend/aci/server/routes/webhooks.py:24-124](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/routes/webhooks.py#L24-L124) · [backend/aci/server/routes/billing.py:183-200](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/aci/server/routes/billing.py#L183-L200)

### How is it self-hosted and what is open vs proprietary? (answered)

The repository is 100% open-source under **Apache License 2.0** (LICENSE:1-201). The complete backend (FastAPI server, 98+ integrations, CLI, DB models, CRUD), frontend (Next.js dev portal), and Docker Compose setup are in the repo. To self-host, you need **PostgreSQL with pgvector** (compose.yml:5-19), **LocalStack** for AWS KMS emulation (compose.yml:35-46, used for encryption key management), and **PropelAuth** for auth (a mock is provided for dev, compose.yml:47-56). The server depends on **OpenAI** for embeddings — so an API key is required. **Stripe** handles billing (main.py:52). **Logfire** and **Sentry** provide observability but are optional (main.py:77-85). There is **no proprietary cloud dependency** — the ACI.dev hosted cloud (aci.dev) is a separate managed service; everything in this repo runs independently. The **Unified MCP server** is explicitly NOT in this repo — it lives at github.com/aipotheosis-labs/aci-mcp (README.md:21-24) and wraps the ACI.dev API. The Python SDK is also external. The frontend dev portal requires PropelAuth for login, which is the only component that needs an external identity provider when self-hosting (the mock replaces it for local dev).


Citations: [LICENSE:1-201](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/LICENSE#L1-L201) · [README.md:21-26](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/README.md#L21-L26) · [backend/compose.yml:35-56](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/compose.yml#L35-L56) · [backend/compose.yml:1-19](https://github.com/aipotheosis-labs/aci/blob/3e4a82fa5fd22f1165af2b39fa3de2b0f031242e/backend/compose.yml#L1-L19)
