Skill v1.0.0
currentTrusted Publisher100/100version: "1.0.0" name: external-api-workflows description: > Build a Universal App workflow that calls a third-party API or custom model endpoint through a server-side function. Use for API keys, webhooks, Azure OpenAI-compatible endpoints, payment, messaging, or any provider credential that must stay out of the browser.
External API workflows
Use a Rayfin function as the trust boundary between the UI and the external provider. Read functions-capability and apply its pack before implementing this workflow.
Secret contract
Declare only the secret name and a non-sensitive description in rayfin/rayfin.yml. Never put the value in YAML:
secrets:- name: THIRD_PARTY_API_KEYdescription: Credential used by the server-side fulfillment request.
Deploy the app once before provisioning the value. Obtain the user's confirmation for the target workspace and remote deployment. Read app-deployment and complete its version-matched Rayfin CLI workflow. The current secret command requires a deployed AppBackend endpoint.
Then ask the user to run:
npx rayfin secret set THIRD_PARTY_API_KEY
This command requires an interactive terminal for its masked prompt. If the host can spawn an independent interactive terminal, open it at the app root and start this command there so the masked prompt is immediately waiting. Do not claim that a terminal was opened unless the launch succeeded. Otherwise, pause and tell the user where to run the exact command. Do not pass the value through a non-interactive shell tool. The user enters the value directly into that command's masked prompt. Do not ask the user to paste a secret into chat. Do not place the value in source, YAML, frontend environment variables, command arguments, logs, or function output. Wait for the user to confirm that provisioning completed before invoking the deployed workflow.
After the value is provisioned, open the deployed fabricUrl and have the user invoke the workflow in the authenticated UI. Confirm that the UI receives only the bounded result contract. Do not report the workflow complete before this deployed invocation succeeds unless the user explicitly defers runtime validation.
Retrieve the production value only inside the function. The secret is declared with rayfin secret set, so it is typed — read it as a property:
const apiKey: string = ctx.Secrets.THIRD_PARTY_API_KEY;
ctx.Secrets.<NAME> is typed string, not string | undefined: declaring the secret is what makes it expected, and a name that was never declared is a compile error. If the value was not supplied at invoke time the read throws a diagnosable error rather than silently yielding undefined, so no SECRET_NOT_CONFIGURED branch is needed. Catch it at the function boundary if you want to map it onto the bounded result contract:
try {const apiKey: string = ctx.Secrets.THIRD_PARTY_API_KEY;// ...call the external service...} catch {return {ok: false,code: 'SECRET_NOT_CONFIGURED',message: 'The external service credential is not configured.',};}
Never hard-code the secret name anywhere else — it comes from rayfin.yml via the generated registry. ctx.getSecret('NAME') is deprecated and flagged by @typescript-eslint/no-deprecated; it remains valid only for a value deliberately not modelled in rayfin.yml.
Do not return apiKey, compare it against browser input, or include it in an exception. An environment fallback may be used only for local debugging through the functions project's ignored local settings.
Provider boundary
- Do not call generic cloud or provider best-practices tools for a
provider-neutral request. Use a provider-specific tool only after the user identifies that provider and the tool is needed to establish its contract.
- Validate and bound every browser-supplied value before making a request.
- Set an explicit outbound deadline appropriate for the provider.
- Send only required headers and fields.
- Treat non-success provider responses as expected domain outcomes when the UI
can act on them.
- Do not return raw provider bodies or stack traces.
- Select and bound the provider fields returned to the browser.
- Do not log the request body, response body, authorization header, secret,
prompt, or user content.
Use a bounded result rather than exposing provider data directly:
type ExternalCallResult =| { ok: true; value: string }| {ok: false;code:| 'SECRET_NOT_CONFIGURED'| 'AUTHENTICATION_FAILED'| 'PROVIDER_REJECTED'| 'PROVIDER_TIMEOUT';message: string;};
Expected provider failures use the safe result. Unexpected failures may still throw, but the thrown message must not contain a secret, request body, raw provider response, or sensitive exception detail.
UI contract
Show an in-progress state for the complete invocation. Render safe, actionable outcomes for missing configuration, invalid authentication, provider rejection, and timeout. Keep an unexpected-failure state for failures outside the known domain result.
If the provider can take longer than the browser client's configured deadline, do not increase the global timeout for unrelated app operations. Use a dedicated client only as a documented temporary workaround until the SDK supports a per-invocation timeout.