Guides
Connect a Custom API
Store a credential for any header-authenticated API — an internal service, a niche vendor, a self-hosted deployment — with the Custom managed-secret template.
Use a catalog template whenever one exists — it ships a verified injection contract, save-time credential verification, and (for many providers) an operation catalog that content rules can bind to. For everything else — an internal platform service, a regional SaaS vendor, a self-hosted deployment — the Custom template stores the credential with the same vault storage, grants, rotation, audit trail, and policy enforcement; the operator supplies the injection contract the catalog would otherwise provide. A custom provider can also carry its own logo, a path its keys are checked against when they are saved, and the API operations its policy rules name — pulled from the API’s OpenAPI document or added by hand.
This also covers an integration where each end user brings their own key (a lender’s integration key, for example): the end user enters it on the hosted Connect page, under the end-user key setup the operator configures, and it is checked against the same path.
| Credential type | Injected as |
|---|---|
bearer_token | Authorization: Bearer <token> (or any format containing {token}) |
header_key | A configured header, e.g. X-API-Key: <key> |
basic_auth | Authorization: Basic <token> — store the base64-encoded username:password pair as the credential value |
aws_sig_v4 | AWS Signature V4 signing — intended for AWS-compatible APIs; see the AWS reference |
The injection contract
Section titled “The injection contract”Three settings define how the credential reaches the API:
- Injection header — the header name the credential is written into (default
Authorization). Security-critical and hop-by-hop headers (Host,Cookie,Content-Type,X-Forwarded-For, …) are rejected. - Injection format — the header value template. It must contain the
{token}placeholder, which is replaced with the stored credential:Bearer {token},Token {token}, or just{token}. - Additional injection rules — up to 10 extra injections for APIs that need more than one value per request, or that authenticate via query parameter. Each rule targets a
headeror aquery_param, names itskey, and takes its value fromtoken(the primary credential) oradditional_credentials.<field>(a named secondary credential, stored in the vault alongside the primary value and equally write-only):
[ {"target": "header", "key": "X-App-Key", "value_source": "additional_credentials.app_key"}, {"target": "query_param", "key": "api_key", "value_source": "token"}]Rotating the primary credential keeps each additional_credentials.<field> an injection rule reads, unless the rotation sends a new value for it. An aws_sig_v4 credential keeps a stored region and service the same way.
Walkthrough
Section titled “Walkthrough”1. Store the credential
Section titled “1. Store the credential”Dashboard
Open the app, go to Managed Secrets → Add Provider, and pick the Custom card. Name the provider after the API (the name generates the slug), choose the Credential Type, set the Injection Header and Injection Format, add any Additional Injection Rules, paste the credential and a value for each field a rule reads (a rule sends nothing while its field has no value; the dashboard and the CLI warn and still save, so the field can be set later with a fields-only update), click Next, list the Allowed API hosts on the Access Policy step, optionally upload a Logo, set a Credential check path and an OpenAPI document URL (see steps 3 and 5), and click Create Provider.
With the AWS SigV4 credential type, Alter signs each request and sets the header itself, so there is no injection header or format to set: paste the Access Key ID, fill Secret Access Key, and add a Session Token, Region or Service only when the API needs them (see the AWS reference). For AWS itself, the AWS template is the usual choice.
CLI
Omit --template to create a custom provider. This example stores an API key for an internal billing service that authenticates with X-Api-Key and requires a workspace id on every call:
printf '%s' "$BILLING_API_KEY" | alter managed-secrets create \ --name "Internal Billing API" \ --credential-type header_key \ --injection-header X-Api-Key \ --injection-format '{token}' \ --injection-rule @./injection-rules.json \ --credential-field workspace_id=ws_12345 \ --credential-value -where injection-rules.json contains:
[{"target": "header", "key": "X-Workspace-Id", "value_source": "additional_credentials.workspace_id"}]2. Set the allowed hosts
Section titled “2. Set the allowed hosts”The host allowlist bounds where the credential can be sent — exact hosts (billing.internal.example.com) or leading-wildcard subdomains (*.example.com).
The dashboard’s create form includes the Allowed Hosts field. The CLI sets it as a follow-up step (or in one call via an --input JSON body carrying allowed_hosts):
alter managed-secrets set-allowed-hosts <provider> --host billing.internal.example.com3. Check keys when they are saved
Section titled “3. Check keys when they are saved”Name a credential check path: a path on the API that answers 401 to a bad key, such as /v1/me. Whenever a key is saved (created, rotated, or entered by an end user on the hosted Connect page), Alter sends a GET with the key, injected the provider’s way, to that path on the first exact allowed host. A 401 refuses the save; any other answer saves the key, and a response that does not prove the key was checked (an endpoint that also answers without a key, a network failure) is reported as inconclusive. With no path, a provider whose key travels in several headers, or allowed hosts that are all wildcards, the check is reported as skipped with the reason, and the key is verified on first use.
alter managed-secrets set-integration <provider> --preflight-path /v1/meIn the dashboard, the provider’s Integration card edits the path and the logo. The logo is an uploaded PNG, JPEG or WebP image (the dashboard resizes it to 128px); end users see it on the hosted Connect page and in their wallet.
4. Issue a grant and call the API
Section titled “4. Issue a grant and call the API”From here a custom provider behaves exactly like any catalog provider. Issue a grant binding it to a principal — system, user, group, or agent — then call the API with the SDK:
import asyncio, osfrom alter_sdk import App, HttpMethod
async def main(): async with App(api_key=os.environ["ALTER_API_KEY"]) as app: response = await app.request( HttpMethod.GET, "https://billing.internal.example.com/v1/invoices", grant_id=os.environ["BILLING_GRANT_ID"], ) print(response.status_code, response.json())
asyncio.run(main())import { App, HttpMethod } from "@alter-ai/alter-sdk";
const app = new App({ apiKey: process.env.ALTER_API_KEY! });
try { const response = await app.request( HttpMethod.GET, "https://billing.internal.example.com/v1/invoices", { grantId: process.env.BILLING_GRANT_ID! }, ); console.log(response.status, await response.json());} finally { await app.close();}Alter injects X-Api-Key: <key> and X-Workspace-Id: ws_12345 on the outgoing call. Grant issuance, per-user and group binding, rotation, and slug-based resolution all work as described in Provision secrets for backend services.
5. Map the API’s operations
Section titled “5. Map the API’s operations”Operation and family content rules name an API’s operations. For a custom provider, the operations are yours:
- From the API’s OpenAPI document. Name its https URL (up to 2 MB). Alter pulls it at once and again every day, and adopts its operations on every pull — new operations become nameable without a review step, and operations the document drops stop matching. Each pulled operation’s family comes from its method:
GETisread,DELETEisdelete,POST/PUT/PATCHarewrite. - By hand. Add an operation — an id, a method, a path template such as
/loans/{loanId}, and one or more families (read,write,delete,send,admin,payment). An operation you add replaces a pulled one with the same id or the same route, which is how a pulledPOST /loans/{loanId}/disclosuresbecomessendso a “no outbound messages” family rule binds it.
alter managed-secrets operations set-spec <provider> --url https://docs.example.com/openapi.jsonalter managed-secrets operations set <provider> disclosures.send \ --method POST --path '/loans/{loanId}/disclosures' --family sendalter managed-secrets operations list <provider>In the dashboard, the provider’s API operations card shows the document’s status and every operation, with Sync now, Add operation and Override.
Policies on custom APIs
Section titled “Policies on custom APIs”Every request-shape rule type works on a custom provider’s traffic, at every level: method/endpoint restrictions (“GET only”, “/v1/invoices/** only”), attribute match rules, IP allowlists, rate limits, time windows, human-in-the-loop approvals, and per-request rules from an SDK constrained client.
Content rules match the operations you map (step 5): a rule that names an operation or a family applies to the requests that match it. Content rules are fail-closed, so an operation-scoped rule denies a request that matches no mapped operation, and denies everything on a provider that maps none. Custom operations carry no parameters, so a rule condition on a parameter (recipients, amounts) and field redaction cannot be judged on a custom provider and deny the request; express those with method/endpoint restrictions or attribute match rules.
Changing operations changes what rules bind to, and a remap can make a deny rule stop matching, so authoring them and naming or pulling the document need dashboard_secrets:operations. No wildcard grants it, dashboard_secrets:* and dashboard_secrets:admin included: mint it by typing the scope. Listing them needs dashboard_secrets:read.
Differences from catalog templates
Section titled “Differences from catalog templates”Everything else — storage, grants, delegation, rotation, audit, request-shape policy rules — is identical. The differences:
| Catalog template | Custom template | |
|---|---|---|
| Injection contract | Provider-verified | Operator-configured |
| Allowed hosts | Often supplied by the template | Operator-configured (required for proxied calls) |
| Logo | The template’s | Uploaded by the operator |
| Save-time credential verification | Where the provider supports it | Against the operator’s credential check path; otherwise skipped, and verified on first use |
| Content rules (operations / families) | Where the provider has an operation catalog | Against the operations the operator maps (pulled daily from an OpenAPI document, or added by hand) |
| Content rules (parameters / redaction) | Where the provider’s catalog projects them | Not available — such a rule denies |
| What an approver sees | Attested operation and its security-relevant values | Every value the agent sends, by location (body.…, query.…, header.…), exactly as sent; Alter’s credential is added after approval and never shown, and a request with a value that cannot be shown faithfully is not approvable |