# Connect an API that isn't listed

> Connect an API that isn't in the supported services list with a generic service, from bearer token to OAuth 2.0 client credentials, OAuth 1.0a, HMAC, or AWS.

If the API you need isn't in the [supported services](/documentation/reference/supported-services/) list, connect it with one of seven generic services: **HTTP Bearer Token**, **HTTP Basic Authentication**, **Custom Token Authentication**, **HTTP OAuth 2.0 Bearer**, **HTTP OAuth 1.0a**, **HTTP HMAC signature**, or **HTTP AWS Signature v4**. You enter the credential once in the dashboard, and your function calls the API with `ctx.http.authed('alias')` like any other connected service. The platform adds the credential to the request, so the secret never reaches your code.

This page walks through the whole path with a made-up credential against a public test API, so you can run it without a third-party account.

## Pick a generic service

| If the API expects | Pick |
|---|---|
| `Authorization: Bearer <token>` | **HTTP Bearer Token** |
| A username and password, or an API token used as the password | **HTTP Basic Authentication** |
| A token or API key in a header you name, or in the query string | **Custom Token Authentication** |
| An OAuth 2.0 token from a client ID and client secret (the client credentials grant) | **HTTP OAuth 2.0 Bearer** |
| An OAuth 1.0a signed request | **HTTP OAuth 1.0a** |
| A signature computed over the request, such as `X-Signature: ...` | **HTTP HMAC signature** |
| An AWS Signature v4 signed request | **HTTP AWS Signature v4** |

**HTTP OAuth 2.0 Bearer** covers only the client credentials grant, where your server exchanges a client ID and secret for a token and Helix refreshes it for you. OAuth 2.0 with a user sign-in is not a generic service. If the service offers it, use its own entry in the supported services list, which handles sign-in and refresh.

## What to enter in the form

| Service | Fields |
|---|---|
| HTTP Bearer Token | Token |
| HTTP Basic Authentication | Username, Password |
| Custom Token Authentication | Token, Location, Location Value, Pin to domain (optional) |
| HTTP OAuth 2.0 Bearer | Client ID, Client Secret, Token URL. See [OAuth 2.0 client credentials](#oauth-20-client-credentials) below |
| HTTP OAuth 1.0a, HTTP HMAC signature, HTTP AWS Signature v4 | Described in [signed requests](#signed-requests) below |

For Custom Token Authentication:

| Field | Value |
|---|---|
| Token | The API key or token |
| Location | `header` (the default) or `query` for the query string |
| Location Value | The header name, such as `X-Api-Key`, or the query parameter name, such as `api_key` |
| Pin to domain | Optional, and recommended. The only domain this credential may be used with, such as `api.vendor.com`. Subdomains are included. Enter the bare domain: no `https://`, path, wildcard, or port |

## OAuth 2.0 client credentials

HTTP OAuth 2.0 Bearer is for APIs that issue tokens from a client ID and client secret. You enter the credentials and the token URL once. Helix requests the token, stores it, refreshes it when it expires, and sends `Authorization: Bearer <token>` on every call.

| Field | Value |
|---|---|
| Client ID | The client ID the API issued to you |
| Client Secret | The client secret the API issued to you |
| Token URL | The full URL of the token endpoint, such as `https://login.vendor.com/oauth2/token` |

The token URL does not have to be on the same host as the API. If tokens come from `login.vendor.com` and you call `api.vendor.com`, enter the token URL as above and pass the API URL in your code as usual.

When you create the authentication you also choose how the API expects the client to identify itself at the token endpoint:

| Choice | Pick it when the API expects |
|---|---|
| **Client credentials (credentials in request body)**, the default | The client ID and secret as form fields in the token request |
| **Client credentials (HTTP Basic client authentication)** | The client ID and secret in an `Authorization: Basic` header on the token request |

Your API's documentation says which. If one is refused, the other is the one to try.

Two limits to know:

- **No domain restriction.** Unlike the signed services below, this credential is not limited to one domain, so it is sent to whatever host your code calls with this alias. Use an alias only for the API you connected it to.
- **No scope field.** The token is requested without a scope. If the API requires one, this service can't connect it yet.

Any other header the API needs, such as a profile or tenant header, is set by your code on the request. Helix only adds the `Authorization` header.

## Signed requests

OAuth 1.0a, HMAC, and AWS Signature v4 sign each request instead of sending a fixed token. You enter the secrets once, and the platform computes the signature on every call.

### Pin to domain is required

A signed credential can only be used against the domain you pin it to, and its subdomains. `api.vendor.com` allows `eu.api.vendor.com`, not `vendor.com`. A request to any other host fails instead of sending the credential. Enter the bare domain: no `https://`, path, wildcard, or port.

These services also refuse to send a request when a required value is missing.

### HTTP OAuth 1.0a

| Field | Value |
|---|---|
| Consumer key | The key issued to your application |
| Consumer secret | The secret issued to your application |
| Token | Optional. Leave Token and Token secret empty for two-legged OAuth |
| Token secret | Optional, as above |
| Signature method | `HMAC-SHA1` (the default) or `HMAC-SHA256` |
| Pin to domain | Required |

You supply the credentials. Helix signs each request with them but doesn't run the OAuth 1.0a token exchange, so for an API that needs a user's access token you must already hold the token and token secret.

Not supported:

- The `realm` parameter
- The `RSA-SHA1` and `PLAINTEXT` signature methods
- Requests with a form-urlencoded body

### HTTP HMAC signature

| Field | Value |
|---|---|
| Secret | The shared secret the API verifies against |
| Algorithm | `sha1`, `sha256` (the default), or `sha512` |
| Signature encoding | `hex` (the default) or `base64` |
| Header name | The header that carries the signature, such as `X-Signature` |
| Signature format | The header value, which must contain `{{signature}}`, such as `sha256={{signature}}` |
| Sign the request body | `true` (the default) or `false` |
| Signed headers | Optional, comma-separated, in order. Your function sets these headers on the request |
| Pin to domain | Required |

The signature covers the exact bytes your function sends. If the API verifies a re-serialized version of the body, the signatures won't match. Sign the body, or list at least one signed header.

### HTTP AWS Signature v4

| Field | Value |
|---|---|
| Access key | The AWS access key ID |
| Secret key | The AWS secret access key |
| Session token | Optional, for temporary credentials |
| Signing service name | Optional. Only needed when the host doesn't name the service |
| Region | Optional. Only needed when the host doesn't name the region |
| Pin to domain | Required, and prefilled with `amazonaws.com` |

The region and service come from a regional host such as `sts.us-east-1.amazonaws.com`. Set them yourself when the host doesn't name them, or names a different service than the one that signs. For example, `bedrock-runtime` signs as `bedrock`.

## Worked example

This example uses [httpbin.org](https://httpbin.org), a public API that echoes back what it receives. The token below is made up. Don't enter a real secret while you try this.

### 1. Create the authentication

```bash
helix auth connect --new
```

The command opens the dashboard's connect page for your workspace. Add `--no-open` to print the link instead. In the dashboard, choose **HTTP Bearer Token**, enter `demo-token-123` as the token, and name the authentication.

Back in the terminal, the CLI finds the new authentication and writes an alias into `helix.config.ts`. The CLI suggests an alias from the service name, so here it is `http_bearer_token`. You can type a different one at the prompt, using lowercase letters, digits, and underscores, starting with a letter. Without a terminal, such as in an agent loop, the command only prints the link. Once you've created the authentication, wire it up with `helix auth connect` or `helix auth add <alias> <uuid>`.

### 2. Call the API

```typescript
// functions/api/v1/demo.get.ts
import { defineFunction } from '@trayai/helix-sdk';

export default defineFunction(async (ctx) => {
  const res = await ctx.http
    .authed('http_bearer_token')
    .get('https://httpbin.org/bearer');

  return res.body;
});
```

An authed call returns `{ status, body, headers }`. `body` is already parsed JSON for a JSON response. A non-2xx answer from the API is returned, not thrown, so check `res.status` when you need to handle errors.

### 3. Run it

```bash
helix dev
curl http://localhost:3000/api/v1/demo
```

Success looks like this:

```json
{ "authenticated": true, "token": "demo-token-123" }
```

The test API received `Authorization: Bearer demo-token-123`, which your function never wrote.

### Try the other two

The same steps work with the other generic services. Change the alias and the URL in step 2, and return `res.body` as before.

| Service | Entered in the form | Call | Response shows |
|---|---|---|---|
| HTTP Basic Authentication | Username `tester`, password `s3cret-pass` | `https://httpbin.org/basic-auth/tester/s3cret-pass` | `{"authenticated": true, "user": "tester"}` |
| Custom Token Authentication | Location `query`, Location Value `api_key` | `https://httpbin.org/anything` | `args` contains `api_key` with your token |
| Custom Token Authentication | Location `header`, Location Value `X-Api-Key` | `https://httpbin.org/headers` | The `X-Api-Key` header with your token |

## Try the signed services

Each example uses a public test API and made-up or published test keys, so you need no real account. Create the authentication as in step 1 above. The aliases below are the CLI's suggested defaults (`http_oauth1`, `http_hmac`, `http_aws_sigv4`), and you can edit them at the prompt. Call each from a function that returns the result:

```typescript
// functions/api/v1/signed.get.ts
import { defineFunction } from '@trayai/helix-sdk';

export default defineFunction(async (ctx) => {
  const res = await ctx.http.authed('http_oauth1').get('https://postman-echo.com/oauth1');
  return { status: res.status, body: res.body };
});
```

### OAuth 1.0a

Postman Echo checks the signature with its published test key.

| Field | Value |
|---|---|
| Consumer key | `RKCGzna7bv9YD57c` |
| Consumer secret | `D+EdQ-gs$-%@2Nu7` |
| Token and Token secret | Leave empty |
| Signature method | `HMAC-SHA1` |
| Pin to domain | `postman-echo.com` |

Call `GET https://postman-echo.com/oauth1`. Success is status `200` with this body:

```json
{ "status": "pass", "message": "OAuth-1.0a signature verification was successful" }
```

### HMAC signature

| Field | Value |
|---|---|
| Secret | `dummy-hmac-secret` |
| Algorithm | `sha256` |
| Signature encoding | `hex` |
| Header name | `X-Signature` |
| Signature format | `{{signature}}` |
| Sign the request body | `true` |
| Signed headers | Leave empty |
| Pin to domain | `httpbin.org` |

Send a JSON body to the echo endpoint:

```typescript
const res = await ctx.http
  .authed('http_hmac')
  .post('https://httpbin.org/anything', { body: { event: 'order.created', id: 42 } });
```

The echo shows the exact string sent in `body.data` and the signature in `body.headers['X-Signature']`. For this body and secret the signature is `eec200cc0004a0c3ceb9aeb1a4ddea237a549f1f65a11c40352347cbfdc394b4`. Check it yourself:

```bash
printf '%s' '{"event":"order.created","id":42}' | openssl dgst -sha256 -hmac 'dummy-hmac-secret'
```

### AWS Signature v4

| Field | Value |
|---|---|
| Access key | `AKIDEXAMPLE` |
| Secret key | `wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY` |
| Session token | Leave empty |
| Pin to domain | `amazonaws.com` |

These are the example keys from AWS's own documentation, so AWS rejects them. Call `GET https://sts.us-east-1.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15`. Success for this exercise is status `403` with `<Code>InvalidClientTokenId</Code>` in the body. That shows AWS parsed a correctly formed signed request and rejected only the key. An unsigned request returns `MissingAuthenticationToken` instead. This does not prove the signature is valid, only that it is well formed. With a real key, the same call returns your account details.

## Troubleshooting

**The API answers 401.** The call doesn't throw: the 401 comes back as `res.status === 401`. The values or the location are wrong. Check the Token, Location, and Location Value you entered against what the API's documentation asks for.

**You want to see what is sent.** Point a made-up credential at an echo endpoint such as `https://httpbin.org/anything`. Never do this with a real secret, because the echo site would receive it.

**An OAuth 2.0 authentication won't connect or the API answers 401.** Check the Token URL first: it must be the token endpoint, not the API's base URL. Then try the other client authentication choice in the table above. The credentials are sent to the token URL you entered, so a typo there can also leak the client secret to the wrong host. Re-enter the authentication if you suspect it.

**A signed request is refused.** HTTP OAuth 1.0a, HTTP HMAC signature, and HTTP AWS Signature v4 send nothing when a required value is missing or the host is outside the pinned domain. Check the form values and the Pin to domain against the URL you call.

**A missing alias error.** The alias isn't in `helix.config.ts`. The error lists the aliases that are configured, so compare it with the name your code uses.

For how aliases, refresh, and request logging work, see [connected services](/documentation/guides/connected-services/).

---

Canonical: https://helix.tray.ai/documentation/guides/connect-any-api/
Any link on this page is available as markdown by appending .md to its URL.
Full corpus: https://helix.tray.ai/documentation/llms-full.txt