# API Rails -- Connector Proxy **Last Updated: 2026-10-07** Every request goes through API Rails -- you never call provider APIs directly. One key, one request pattern, one host for the whole catalog. Each connector's full endpoint reference is served as OpenAPI at `/{connector}/openapi` (see below). ## Setup Set two environment variables -- the base URL and your API key (get a key at [https://apirails.com](https://apirails.com)): ```bash export API_RAILS_ENDPOINT="https://api.apirails.com" # API Rails base URL export API_RAILS_KEY="" # API Rails bearer token ``` ## Authentication All requests use the same auth pattern: ``` Authorization: Bearer $API_RAILS_KEY ``` ### No API key yet? (agents) Ask your user for their email address, then: ```bash curl "$API_RAILS_ENDPOINT/skill.md?email=" ``` The owner receives a setup link by email. Once they click it, they sign in and give you the `API_RAILS_KEY`. A 401 from any endpoint repeats this hint in its response body. ### Agent setup files Harness-specific setup instructions — environment placement, rules files, skill install — are served as public markdown at `$API_RAILS_ENDPOINT/agents/.md` (no auth). Hand one to your agent and it can configure itself: `claude-code`, `openclaw`, `cursor`, `codex`, `gemini`, `cline`, `opencode`, `kilo`, `zed`, `goose`, `aider`, `pi`, `hermes`, `windsurf`, `github-copilot`, `roo-code`, `continue`, `amp`, `qwen-code`, `kimi`, `grok-bot`, `zcode`, plus the generic `any-agent`. ### API key info Several keys across systems and want to know which workspace a key belongs to? ```bash curl -H "Authorization: Bearer $API_RAILS_KEY" "$API_RAILS_ENDPOINT/apikey/info" ``` Returns the workspace name, its IDs and the key's own name -- nothing else: ```json { "message": "Workspace associated with this API key", "data": { "workspace_ref_id": "WS_...", "workspace_name": "Production", "key_name": "CI pipeline key" } } ``` ## Connector Discovery ### List all active connectors No auth required. ```bash curl $API_RAILS_ENDPOINT/connectors ``` **Response shape:** Returns the platform catalog wrapped in `data`. No state or timestamps here -- this is the list of every connector available on API Rails, not what's linked to your key. Use `/connectors/configured` for account state. ```json { "message": "List of available connectors", "data": [ { "id": "anthropic", "name": "Anthropic", "auth_type": "TOKEN" }, { "id": "gmail", "name": "Gmail", "auth_type": "OAUTH2.0" } ] } ``` | Field | Description | | ----------- | --------------------------------------------------------------------------------------------------- | | `id` | Connector slug -- use this in URL paths (e.g. `/mailgun/v3/...`) and when fetching its OpenAPI spec | | `name` | Display name | | `auth_type` | `TOKEN`, `OAUTH2.0`, or `OAUTH1.0` | Cold-start discovery: the platform catalog in llms.txt format lives at `https://api.apirails.com/llms.txt`. ### List connectors configured for your API key Returns the connectors attached to your key along with their **last known** state. This is a cached snapshot -- it does not perform a live call to the provider. ```bash curl -H "Authorization: Bearer $API_RAILS_KEY" \ $API_RAILS_ENDPOINT/connectors/configured ``` **Response fields per connector:** | Field | Description | | -------------- | ----------------------------------------------------------------- | | `auth_type` | `TOKEN`, `OAUTH2.0`, or `OAUTH1.0` | | `connected` | `true` if a credential is attached to your key for this connector | | `authorized` | `true` if the last known state of the credential was valid | | `connected_at` | Timestamp when the credential was linked | | `last_used_at` | Timestamp of the last successful call, or `null` if never used | Each connector appears as one entry in `data` keyed by its `id`, carrying exactly these five fields and nothing else -- this response shape is a fixed contract. **State combinations:** | `connected` | `authorized` | Meaning | | ----------- | ------------ | ------------------------------------------------------------------ | | `true` | `true` | Ready to use | | `true` | `false` | Credential attached but reported invalid -- replace or reauthorize | | `false` | `false` | Not configured for your key | Note: for OAuth connectors, `authorized: true` reflects the last cached state and may be stale if the provider revoked the token since the last call. Use the check endpoint below for a live answer. ### Check a connector's health (live) Performs a live call to the provider to verify the credential works **right now**. Use this before any production-critical call, especially for OAuth connectors whose `last_used_at` is old. ```bash curl -H "Authorization: Bearer $API_RAILS_KEY" \ $API_RAILS_ENDPOINT/{connector}/connector/check ``` **Response shape:** Returns a flat object (not wrapped in `data`). Read the `status` field, not the HTTP code -- several not-ready states also return HTTP 200. Success (HTTP 200): ```json { "status": "connected", "provider": "mailgun", "connected_at": "2026-04-04T11:28:02.000Z", "last_used_at": "2026-06-22T08:49:19.298Z", "checked_at": "2026-06-22T09:58:25Z" } ``` Failure (HTTP 503): ```json { "status": "failed", "provider": "gmail", "error": { "code": "MISSING_CONFIG", "message": "No validation endpoint configured" }, "connected_at": "2026-04-01T22:19:39.000Z", "last_used_at": "2026-06-12T09:06:26.659Z", "checked_at": "2026-06-22T09:58:31Z" } ``` Common `error.code` values: | Code | Meaning | | ---------------- | ------------------------------------------------------------------------------------------------ | | `MISSING_CONFIG` | No validation endpoint defined for this connector -- a platform-side gap, not a credential issue | | `UNKNOWN` | Unspecified check failure -- retry, and contact support if it persists | All `status` values: | `status` | HTTP | Meaning | | ----------------------- | ------- | ------------------------------------------------------------------------------------------ | | `connected` | 200 | Credential works right now | | `failed` | 503/504 | Provider rejected the credential (see `error`) | | `not_configured` | 200 | No credential linked to your key for this connector | | `credentials_saved` | 200 | OAuth app credentials saved, authorization not completed yet -- finish it in the dashboard | | `authorization_pending` | 200 | OAuth token expired -- reauthorize via dashboard | | `unknown` | 200 | Connector defines no validation endpoint -- it cannot be live-checked | ### Check guardrails status Returns the guardrail allowlist for your workspace on a given connector. If `guardrails_enabled` is `true`, only routes listed in `allowed_routes` will succeed; all others return 403. When guardrails are disabled, every route the connector defines is reachable. ```bash curl -H "Authorization: Bearer $API_RAILS_KEY" \ $API_RAILS_ENDPOINT/{connector}/guardrails ``` **Response shape (guardrails enabled):** ```json { "provider": "stripe", "guardrails_enabled": true, "allowed_count": 15, "total_routes": 142, "allowed_routes": [ { "method": "GET", "route": "/v1/charges/:id", "label": "Retrieve a charge", "category": "Charges" }, { "method": "POST", "route": "/v1/charges", "label": "Create a charge", "category": "Charges" } ] } ``` When disabled, the response carries `guardrails_enabled: false` and a message instead of the arrays. ### Get connector context Returns the per-connection dynamic context for a connector — the values you need to fill `{param}` path variables (domain, account id, subdomain, org id) plus any user-authored instructions. Check this before guessing or asking the user for IDs a connector route requires. ```bash curl -H "Authorization: Bearer $API_RAILS_KEY" \ $API_RAILS_ENDPOINT/{connector}/connector/context ``` **Response shape (connected):** ```json { "provider": "mailgun", "status": "connected", "json": { "values": { "domain_name": { "value": "mg.example.com", "source": "user" }, "account_id": { "value": "023e105f4ecef8ad9ca31a8372d0c353", "source": "connection" }, "subdomain": { "value": "acme", "source": "connection" } }, "oauth_profile": { "id": 12345, "login": "acme-dev" }, "connection": { "account_id": "023e105f4ecef8ad9ca31a8372d0c353", "subdomain": "acme", "endpoint": "https://acme.example.com" }, "unresolved_path_params": [ { "param": "zone_id", "route_count": 12, "examples": ["/zones/{zone_id}"] } ] }, "markdown": "# mailgun connector context\n\n## Values\n\n| Key | Value | Source |\n..." } ``` When the connector has no credentials, the response is `{"status": "not_configured", "provider": "..."}` (HTTP 200); a bad token returns the usual 401 shape. The `json` sections: | Key | Meaning | | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `values` | Merged key/value map to use for path parameters. Each entry tags its `source`: `user` (authored in the dashboard — authoritative), `oauth` (from the connected account's profile), `connection` (fields stored when the connector was configured — account id, email, subdomain, endpoint …). Later sources win conflicts in that same order. | | `oauth_profile` | Safe fields from the connected OAuth account (sensitive keys like tokens/secrets are stripped). | | `connection` | Connect-time fields stored with the credential. Secret-looking keys are stripped in every spelling (`api_key`, `api_token`, `accessToken`, `client_id`, `secret_key`, bare `key`, …) — only non-secret primitives (ids, emails, subdomains) are ever returned. OAuth app credentials (CLIENT_ID/CLIENT_SECRET) are never included. | | `unresolved_path_params` | Path parameters that appear in the connector's routes but have no value in `values` yet — each with the number of routes using it and example intents. Fill these by asking the user or discovering them via list endpoints. | `markdown` renders the same information as instructions: a Values table (`Key | Value | Source`), profile/connection details, the unresolved-parameter callout, and any additional instructions the workspace owner wrote verbatim. Users author the `user`-sourced values and instructions in the dashboard's connector Configure panel ("Agent context"). Context is informational only — API Rails does not substitute parameters for you; take values from `json.values` and fill `{param}` variables yourself when building the endpoint path. ### Auth types and failure modes Different auth types fail differently. Knowing the type tells you what recovery to expect. | `auth_type` | Failure mode | Recovery | | ----------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------ | | `TOKEN` | Key rejected only when revoked or expired -- predictable | Replace the key in dashboard | | `OAUTH2.0` | Refresh token can expire silently -- `authorized` may still read `true` until the next call | Reauthorize via dashboard to get a fresh refresh token | | `OAUTH1.0` | Token/secret pair invalid | Reauthorize via dashboard | ## Snippets Snippets are your account's saved code blocks (Gist-like, multi-file) -- persist reusable request bodies, scripts, and integration snippets under your API key. Every key of the same account shares one snippet space with the account's dashboard. Rules: - Snippets you create are **private by default** -- only your account's keys can list or read them. - A snippet made `public` is viewable by any key holder **via its `ref_id` only** -- it is never listed to anyone else and slugs do not resolve on this API. - Snippets are **never deleted**. The terminal state is `archive`, which is reversible via `unarchive`. ### List your snippets ```bash curl -H "Authorization: Bearer $API_RAILS_KEY" \ "$API_RAILS_ENDPOINT/snippets" ``` Query filters: `?connector=` (only snippets tagged with that connector) and `?archived=1` (the archived set instead of the active one). List responses carry file metadata only (name, language, size) -- no content. ### Create a snippet ```bash curl -X POST -H "Authorization: Bearer $API_RAILS_KEY" \ -H "Content-Type: application/json" \ "$API_RAILS_ENDPOINT/snippets" \ -d '{ "name": "Stripe checkout", "description": "Server + webhook wiring", "connector": "stripe", "visibility": "private", "files": [ {"filename": "checkout.js", "language": "javascript", "content": "const x = 1;"} ] }' ``` | Field | Rules | | ------------- | ----------------------------------------------------------------------------------------------------- | | `name` | Required, 1-255 characters | | `description` | Optional, at most 5000 characters | | `visibility` | `private` (default) or `public` | | `connector` | Optional tag naming a connector id (e.g. `stripe`); 422 for unknown ids | | `files` | 1-20 files; unique `filename` (1-255 chars), `content` at most 64KB each, `language` at most 50 chars | ### View, modify, archive ```bash # View (owner always; non-owner only when public) -- by ref_id only curl -H "Authorization: Bearer $API_RAILS_KEY" \ "$API_RAILS_ENDPOINT/snippets/{ref_id}" # Modify (name/description/visibility/connector/files -- all optional) curl -X PUT -H "Authorization: Bearer $API_RAILS_KEY" \ -H "Content-Type: application/json" \ "$API_RAILS_ENDPOINT/snippets/{ref_id}" \ -d '{"visibility": "public"}' # Archive (reversible; snippets are never deleted) curl -X POST -H "Authorization: Bearer $API_RAILS_KEY" \ "$API_RAILS_ENDPOINT/snippets/{ref_id}/archive" # Restore curl -X POST -H "Authorization: Bearer $API_RAILS_KEY" \ "$API_RAILS_ENDPOINT/snippets/{ref_id}/unarchive" ``` A private snippet requested by a non-owner returns 404 (ids are not enumerable). Responses never include the owner's user id. ## Per-Connector Skill and Examples Every connector ships a generated documentation package, served as plain markdown -- no auth required: - `$API_RAILS_ENDPOINT/{connector}/skill.md` -- the connector's entry doc: base endpoint, authentication, and its most common endpoints - `$API_RAILS_ENDPOINT/{connector}/examples` -- examples index for every endpoint - `$API_RAILS_ENDPOINT/{connector}/endpoints/` -- one file per endpoint: parameters, headers, request/response examples, and a parameter reference (endpoint file names are listed in the skill/examples index) Start with the connector's `skill.md`; it links to everything else. | Connector | Skill + examples | Endpoints | |---|---|---| | Anthropic | [`/anthropic/skill.md`]($API_RAILS_ENDPOINT/anthropic/skill.md) | 33 | | AWS SES | [`/awsses/skill.md`]($API_RAILS_ENDPOINT/awsses/skill.md) | 31 | | Discord | [`/discord/skill.md`]($API_RAILS_ENDPOINT/discord/skill.md) | 175 | | Gmail | [`/gmail/skill.md`]($API_RAILS_ENDPOINT/gmail/skill.md) | 81 | | Google Calendar | [`/googlecalendar/skill.md`]($API_RAILS_ENDPOINT/googlecalendar/skill.md) | 38 | | Google Docs | [`/googledocs/skill.md`]($API_RAILS_ENDPOINT/googledocs/skill.md) | 3 | | Google Drive | [`/googledrive/skill.md`]($API_RAILS_ENDPOINT/googledrive/skill.md) | 60 | | Google Forms | [`/googleforms/skill.md`]($API_RAILS_ENDPOINT/googleforms/skill.md) | 10 | | Google Search Console API | [`/googlesearchconsole/skill.md`]($API_RAILS_ENDPOINT/googlesearchconsole/skill.md) | 11 | | Google Sheets | [`/googlesheets/skill.md`]($API_RAILS_ENDPOINT/googlesheets/skill.md) | 17 | | Google Slides | [`/googleslides/skill.md`]($API_RAILS_ENDPOINT/googleslides/skill.md) | 3 | | Mailgun | [`/mailgun/skill.md`]($API_RAILS_ENDPOINT/mailgun/skill.md) | 254 | | MiniMax | [`/minimax/skill.md`]($API_RAILS_ENDPOINT/minimax/skill.md) | 7 | | Mistral AI | [`/mistralai/skill.md`]($API_RAILS_ENDPOINT/mistralai/skill.md) | 55 | | Notion | [`/notion/skill.md`]($API_RAILS_ENDPOINT/notion/skill.md) | 20 | | NVIDIA NIM | [`/nvidia/skill.md`]($API_RAILS_ENDPOINT/nvidia/skill.md) | 26 | | OpenAI | [`/openai/skill.md`]($API_RAILS_ENDPOINT/openai/skill.md) | 74 | | Razorpay | [`/razorpay/skill.md`]($API_RAILS_ENDPOINT/razorpay/skill.md) | 60 | | Resend | [`/resend/skill.md`]($API_RAILS_ENDPOINT/resend/skill.md) | 40 | | Sarvam AI | [`/sarvam/skill.md`]($API_RAILS_ENDPOINT/sarvam/skill.md) | 28 | | Stripe | [`/stripe/skill.md`]($API_RAILS_ENDPOINT/stripe/skill.md) | 55 | | Telegram | [`/telegram/skill.md`]($API_RAILS_ENDPOINT/telegram/skill.md) | 68 | | Trello | [`/trello/skill.md`]($API_RAILS_ENDPOINT/trello/skill.md) | 248 | | YouTube | [`/youtube/skill.md`]($API_RAILS_ENDPOINT/youtube/skill.md) | 83 | | YouTube Analytics | [`/youtubeanalytics/skill.md`]($API_RAILS_ENDPOINT/youtubeanalytics/skill.md) | 8 | | YouTube Reporting | [`/youtubereporting/skill.md`]($API_RAILS_ENDPOINT/youtubereporting/skill.md) | 8 | | Z.AI | [`/zai/skill.md`]($API_RAILS_ENDPOINT/zai/skill.md) | 13 | ## Per-Connector Reference: OpenAPI (fallback) When a connector has no generated package, or you need machine-readable schemas, each connector's complete endpoint reference -- paths, methods, parameters, request bodies, schemas -- is served as OpenAPI 3.0 JSON, generated live from the connector definition. No auth required: ```bash curl $API_RAILS_ENDPOINT/{connector}/openapi ``` Works for every loaded connector. Route groupings appear as OpenAPI `tags` (derived from each route's category); rate-limit notes appear in each operation's description. Large connectors can be sliced with query filters instead of pulling the full spec: | Filter | Example | Effect | | ---------- | ----------------------- | ---------------------------------------------------------------------------------------------- | | `keys` | `?keys=1` | Lightweight index: paths, methods, summaries, tags -- no parameter/body schemas | | `category` | `?category=ai-models` | Only routes in one category (the tag lowercased, spaces as dashes) | | `paths` | `?paths=v1-charges~get` | A single endpoint: the route path in dash form, optionally pinned to one method with `~method` | Dash form: take the connector path, drop the leading slash, replace every remaining `/` with `-`, lowercase -- `/v1/charges/{id}` becomes `v1-charges-{id}`. Append `~get`, `~post`, `~patch`, `~put`, or `~delete` to pin the method; without it, all methods on that path are returned. Putting it together on a large connector: ```bash # 1. See what is there -- lightweight index, no schemas curl "$API_RAILS_ENDPOINT/stripe/openapi?keys=1" # 2. Pull one category that matches the task curl "$API_RAILS_ENDPOINT/stripe/openapi?category=charges" # 3. Pull one endpoint with full parameter/body schemas curl "$API_RAILS_ENDPOINT/stripe/openapi?paths=v1-charges~post" ``` ## Request Pattern ```bash curl -X METHOD \ -H "Authorization: Bearer $API_RAILS_KEY" \ -H "Content-Type: application/json" \ "$API_RAILS_ENDPOINT/{connector}{endpoint_path}" \ -d '{"field": "value"}' ``` Replace `{connector}` with the provider slug (e.g. `notion`, `stripe`, `googlesheets`) and `{endpoint_path}` with the provider-specific path from the connector's OpenAPI spec (e.g. `/v1/pages`, `/v1/charges`). **Example 1 -- list a collection (GET):** ```bash curl -H "Authorization: Bearer $API_RAILS_KEY" \ "$API_RAILS_ENDPOINT/notion/v1/databases" ``` **Example 2 -- create a resource (POST with JSON body):** ```bash curl -X POST \ -H "Authorization: Bearer $API_RAILS_KEY" \ -H "Content-Type: application/json" \ -d '{"parent": {"database_id": "db-123"}, "properties": {"Name": {"title": [{"text": {"content": "New row"}}]}}}' \ "$API_RAILS_ENDPOINT/notion/v1/pages" ``` **Example 3 -- act on one resource (path parameter):** ```bash curl -X PATCH \ -H "Authorization: Bearer $API_RAILS_KEY" \ -H "Content-Type: application/json" \ -d '{"archived": true}' \ "$API_RAILS_ENDPOINT/notion/v1/pages/page-123" ``` Path parameters from the OpenAPI spec (`{id}`, `{database_id}`, ...) are substituted directly into the URL path -- no query parameters, no sanitized forms. > **Tip:** Some connectors require discovering a provider-scoped resource before documented endpoints are usable (e.g. Mailgun needs a domain via `/mailgun/v4/domains` before `/mailgun/v3/{domain_name}/messages`). Check the connector's OpenAPI categories and operation descriptions for such prerequisites. ## Error Handling ### HTTP status codes | Status | Meaning | | ------- | ----------------------------------------------------------------------------- | | 200 | Success | | 400/422 | Invalid request body or parameters -- fix your payload | | 401 | Missing or invalid API key | | 403 | Connector not configured, credentials expired, or route blocked by guardrails | | 404 | Endpoint or resource not found | | 405 | Method not allowed | | 429 | Rate limited -- retry after cooldown | | 500 | Provider API error | ### Error response body Error responses use a consistent JSON shape -- a human-readable `message` plus, when the provider supplies more detail, `error` (provider error object or code), `code`, or `data` fields: ```json { "message": "Human-readable description of the failure", "error": "provider_error_code", "data": {} } ``` For OAuth connectors whose refresh token has expired, the response looks like: ```json { "message": "System tried regenerating your accessToken but failed. Reauthorize the application using dashboard to generate fresh accessToken and refreshToken.", "error": "invalid_grant", "data": "Bad Request" } ``` Recovery in this case is to reauthorize the connector in the API Rails dashboard. A 401 from any endpoint also includes an `onboarding` hint in the body -- follow it if you do not have a key yet.