Make API calls from functions(从函数中发起 API 调用)¶
It is possible to make API calls to external sources from TypeScript v1, TypeScript v2 and Python functions, but doing so requires additional configuration. This configuration and external source usage are detailed below.
:::callout{theme="neutral" title="Source aliases"} For TypeScript v2 and Python functions, we recommend referencing sources through source aliases. A source alias is a portable, named reference that you can use as the source identifier in place of a specific source. When your function is distributed through a Marketplace product, the alias can be remapped to a different source per environment, keeping your function code portable. TypeScript v1 functions use generated source symbols and do not support aliases. :::
Configure access to external APIs¶
By default, functions are not allowed to call external APIs. To enable calling external systems from your function, you must configure a source in Data Connection to allow Foundry to connect with an external system.
For functions to connect to your source's external system securely, your source must be configured to enable exports and allow the import of your source into Code Repositories. Both of these can be configured by navigating to the source in Data Connection and opening the Connection settings section.
For TypeScript v1 functions, the source's API name (configured on the Code import configuration tab under Connection settings) is the identifier you reference in code.
:::callout{theme="neutral"}
Make sure to fully configure the certificate chain in your source.
Webhook and function runtime environments are not identical.
Sometimes, a webhook will work correctly while the API call from a function might encounter an UNABLE_TO_GET_ISSUER_CERT error.
Refer to our documentation on the openssl command in the source terminal to verify certificates.
:::
Use an external source in a function¶
To make API calls from a function, you must first import your source into a functions repository using the resource imports sidebar. For TypeScript v2 and Python functions, we recommend then creating a source alias and using its alias key as the source identifier. TypeScript v1 functions reference the imported source directly. You must then declare that your function uses the source, as shown in the examples below.
Examples of this are shown below:
```typescript tab="TypeScript v1" import { ExternalSystems } from "@foundry/functions-api"; import { MySource } from "@foundry/external-systems/sources";
export class MyExternalFunctions {
@ExternalSystems({ sources: [MySource] })
@Function()
public async myExternalFunction(): Promise
return response.text();
}
}
typescript tab="TypeScript v2"
import { getSource, getHttpsConnection, getFetch } from "@palantir/functions-sources";
export const config = {
sources: ["mySourceAlias"]
}
async function MyExternalFunction(): Promise
```python tab="Python" from functions.api import function from functions.sources import get_source
@function(sources=["mySourceAlias"]) def my_external_function() -> str: source = get_source("mySourceAlias") url = source.get_https_connection().url client = source.get_https_connection().get_client() response = client.get(url) return response.text
You can test your function in live preview and use it to make external calls once published.
:::callout{theme="warning"}
**Third-party clients are not yet supported for serverless execution or live preview without overriding the fetch function or HTTP agent.** To ensure your API calls function properly across all environments, you must use the relevant library methods to make requests with the correct configuration. Direct API calls to external sources or internal Foundry URLs are not guaranteed to work in all environments.
:::
## Access source attributes and credentials
You can access source attributes provided by each function type's corresponding library.
The example below shows how to obtain the base URL of the source in the example above.
```typescript tab="TypeScript v1"
const { url } = MySource.getHttpsConnection();
```typescript tab="TypeScript v2" const { url } = getHttpsConnection(source);
```python tab="Python"
url = get_source("mySourceAlias").get_https_connection().url
You can also access additional secrets or credentials stored on the source by using the following syntax to access secrets:
```typescript tab="TypeScript v1" const secret = MySource.getSecret("MySecret");
```typescript tab="TypeScript v2"
const secret = source.secrets["MySecret"];
```python tab="Python" secret = get_source("mySourceAlias").get_secret("MySecret")
## Use the pre-configured clients
For sources that provide a REST API, the source object allows you to retrieve a client. This client will be pre-configured with the server and client certificates specified on the source. It will also include additional proxy configurations which allow egress from the environment functions are executed in. You should always use this client, if possible, to guarantee your function can egress to the source from all environments.
```typescript tab="TypeScript v1"
const fetch = MySource.fetch;
```typescript tab="TypeScript v2" const fetch = await getFetch(source);
```python tab="Python"
client = source.get_https_connection().get_client()
Alternatively, you can use your own client or third-party libraries which make external requests, and use the source object to retrieve attributes and credentials.
TypeScript v2 functions provide a pre-configured HTTP agent as an additional integration point for usage with third party libraries which accept a custom HTTP agent.
The following example demonstrates retrieving this agent and using it with axios ↗.
```typescript tab="TypeScript v2" import { getHttpAgent, getHttpsConnection } from "@palantir/functions-sources"; import axios from 'axios';
const agent = await getHttpAgent(source); const { url } = getHttpsConnection(source);
const response = await axios.get(url, { httpsAgent: agent, });
:::callout{theme="neutral"}
Currently, it is impossible to access source attributes that are not credentials unless the source provides an HTTPS client. For example, you will not be able to access the `hostname` or other non-secret attributes on a [PostgreSQL source](https://palantir.com/docs/foundry/available-connectors/postgresql/).
:::
## Use OAuth 2.0 with outbound applications
If your external API requires OAuth 2.0 authorization, you can configure an [outbound application](https://palantir.com/docs/foundry/administration/configure-outbound-applications/) in Control Panel and use it as the authentication method for a REST API source. When your function runs, the source exposes the calling user's OAuth access token as session credentials. Your function can then use the token to call the external API on the user's behalf.
This pattern is supported in Python and TypeScript v2 functions. See [Use the source's pre-configured client](#use-the-sources-pre-configured-client) below for code examples.
### Limitations
* **TypeScript v1:** TypeScript v1 functions cannot retrieve OAuth tokens directly from a source. To authenticate with an OAuth 2.0 API from a TypeScript v1 function, wrap the call in a [webhook](https://palantir.com/docs/foundry/functions/webhooks/) on a REST API source configured with the outbound application. Consider [migrating to TypeScript v2](https://palantir.com/docs/foundry/functions/typescript-v2-migration/) for direct token access.
* **Deployed mode:** OAuth token refreshing is not available when the function is running in [deployed mode](https://palantir.com/docs/foundry/functions/functions-deployed/). If the calling user's access token expires during execution, the function cannot refresh it automatically. Run your function in [serverless mode](https://palantir.com/docs/foundry/functions/functions-deployed/#choose-between-deployed-and-serverless-execution-modes) to use OAuth-backed outbound applications.
* **Direct function usage in Workshop:** Functions used directly in a [Workshop](https://palantir.com/docs/foundry/workshop/overview/) module, such as [function-backed variables](https://palantir.com/docs/foundry/workshop/functions-use/#function-backed-variables-in-workshop) or functions that populate widget content, cannot trigger the OAuth 2.0 interactive authorization prompt. If a user has not already authorized the outbound application, the function will fail rather than display the prompt. To use an OAuth-backed function from Workshop, wrap it in a [function-backed action](https://palantir.com/docs/foundry/action-types/function-actions-overview/). Alternatively, ensure the user completes the authorization flow from another interactive interface (such as a function-backed action against the same outbound application) before the function is invoked directly in Workshop.
### Use the source's pre-configured client
The simplest approach is to use the HTTP client provided by the source. The `Authorization` header is injected automatically.
```python tab="Python"
from functions.api import function
from functions.sources import get_source
@function(sources=["myOAuthSourceAlias"])
def call_external_api() -> str:
source = get_source("myOAuthSourceAlias")
url = source.get_https_connection().url
client = source.get_https_connection().get_client()
response = client.get(url + "/api/v1/resource", timeout=10)
return response.text
```typescript tab="TypeScript v2" import { getSource, getHttpsConnection, getFetch } from "@palantir/functions-sources";
export const config = { sources: ["myOAuthSourceAlias"] };
export default async function callExternalApi(): Promise
const response = await fetch(url + "/api/v1/resource");
return response.text();
} ```
Use a native HTTP client with manual token injection¶
If you need to use your own HTTP client instead of the source-provided one, retrieve the OAuth token from session credentials and set the Authorization header manually.
python tab="Python"
import requests
from functions.api import function
from functions.sources import get_source
from external_systems.sources import OauthCredentials, Refreshable, SourceCredentials
@function(sources=["myOAuthSourceAlias"])
def call_external_api() -> str:
source = get_source("myOAuthSourceAlias")
url = source.get_https_connection().url
refreshable_credentials: Refreshable[SourceCredentials] = source.get_session_credentials()
session_credentials: SourceCredentials = refreshable_credentials.get()
if not isinstance(session_credentials, OauthCredentials):
raise ValueError("Expected OAuth credentials")
access_token: str = session_credentials.access_token
response = requests.get(
url + "/api/v1/resource",
headers={"Authorization": f"Bearer {access_token}"},
timeout=10,
)
return response.text
```typescript tab="TypeScript v2" import { getSource, getHttpsConnection } from "@palantir/functions-sources";
export const config = { sources: ["myOAuthSourceAlias"] };
export default async function callExternalApi(): Promise
if (!credentials || credentials.type !== "oauth") {
throw new Error("Expected OAuth credentials");
}
const accessToken: string = credentials.accessToken;
const { url } = getHttpsConnection(source);
const response = await fetch(url + "/api/v1/resource", {
headers: { Authorization: `Bearer ${accessToken}` },
});
return response.text();
} ```
Use OAuth-backed functions in actions¶
A common pattern is to call an OAuth-backed external API and feed the result into an Ontology edit. You can then expose that function through a function-backed action. When a user runs the action from Workshop or AIP Studio, their OAuth token is used to make the API call, and the resulting object edits are attributed to them.
For example, the function below uses an OAuth token to fetch the calling user's profile from a third-party identity service. It then creates a new ontology object with that information:
python tab="Python"
from functions.api import function, OntologyEdit
from functions.sources import get_source
from ontology_sdk import FoundryClient
from ontology_sdk.ontology.objects import UserProfile
@function(sources=["myOAuthSourceAlias"], edits=[UserProfile])
def link_user_profile() -> list[OntologyEdit]:
source = get_source("myOAuthSourceAlias")
url = source.get_https_connection().url
client = source.get_https_connection().get_client()
response = client.get(url + "/v1/me", timeout=10)
response.raise_for_status()
profile = response.json()
ontology_edits = FoundryClient().ontology.edits()
ontology_edits.objects.UserProfile.create(
profile["id"],
display_name=profile["display_name"],
)
return ontology_edits.get_edits()
```typescript tab="TypeScript v2" import { getSource, getHttpsConnection, getFetch } from "@palantir/functions-sources"; import { UserProfile } from "@ontology/sdk"; import { Client } from "@osdk/client"; import { createEditBatch, Edits } from "@osdk/functions";
type OntologyEdit = Edits.Object
export const config = { sources: ["myOAuthSourceAlias"], edits: [UserProfile], };
export default async function linkUserProfile(client: Client): Promise
const response = await fetch(url + "/v1/me");
if (!response.ok) {
throw new Error(`Failed to fetch profile: ${response.status}`);
}
const profile = await response.json();
const batch = createEditBatch<OntologyEdit>(client);
batch.create(UserProfile, {
userProfileId: profile.id,
displayName: profile.display_name,
});
return batch.getEdits();
} ```
Troubleshoot common errors¶
For OAuth authorization errors, such as HTTP 401: Unauthorized, Credentials expired and no refresh handler provided, or Resolved source credentials are not present on the Source, see OAuth and outbound applications in the Data Connection troubleshooting reference.
HTTP 407: Proxy authentication required¶
Function network requests must be covered by your source's egress policies. If the destination hostname does not match an allowed policy, the request may return HTTP 407: Proxy Authentication Required.
If your egress policies look correct, check how the request URL is built. The URL from getHttpsConnection() has no trailing slash, so an appended path that omits the leading / is fused to the hostname:
text
"https://example.com" + "api/v1"
→ "https://example.comapi/v1"
The resulting hostname (example.comapi) is not covered by any egress policy, so the request is rejected. Prefix the path with / (for example, url + "/api/v1").
中文翻译¶
从函数中发起 API 调用¶
可以从 TypeScript v1、TypeScript v2 和 Python 函数中向外部源发起 API 调用,但这需要额外的配置。以下将详细介绍此配置及外部源的使用方法。
:::callout{theme="neutral" title="源别名(Source aliases)"} 对于 TypeScript v2 和 Python 函数,我们建议通过源别名来引用源。源别名是一个可移植的命名引用,您可以用它作为源标识符来替代特定的源。当您的函数通过Marketplace 产品分发时,该别名可以根据不同环境重新映射到不同的源,从而保持函数代码的可移植性。TypeScript v1 函数使用生成的源符号,不支持别名。 :::
配置对外部 API 的访问¶
默认情况下,函数不允许调用外部 API。要允许从您的函数调用外部系统,您必须在数据连接(Data Connection)中配置一个源,以使 Foundry 能够与外部系统连接。
为了让函数安全地连接到您源的外部系统,您的源必须配置为启用导出(enable exports)并允许将您的源导入代码仓库(Code Repositories)。这两项都可以通过在数据连接中导航到该源并打开连接设置(Connection settings)部分进行配置。
对于 TypeScript v1 函数,源的 API 名称(在连接设置下的代码导入配置(Code import configuration)选项卡中配置)是您在代码中引用的标识符。
:::callout{theme="neutral"}
请确保在您的源中完整配置证书链。
Webhook 和函数运行时环境并不相同。
有时,Webhook 可以正常工作,而从函数发起的 API 调用可能会遇到 UNABLE_TO_GET_ISSUER_CERT 错误。
请参考我们关于在源终端中使用 openssl 命令的文档来验证证书。
:::
在函数中使用外部源¶
要从函数发起 API 调用,您必须首先使用资源导入侧边栏(resource imports sidebar)将您的源导入到函数仓库中。对于 TypeScript v2 和 Python 函数,我们建议随后创建一个源别名,并使用其别名键作为源标识符。TypeScript v1 函数则直接引用导入的源。然后,您必须声明您的函数使用了该源,如下面的示例所示。
示例如下:
```typescript tab="TypeScript v1" import { ExternalSystems } from "@foundry/functions-api"; import { MySource } from "@foundry/external-systems/sources";
export class MyExternalFunctions {
@ExternalSystems({ sources: [MySource] })
@Function()
public async myExternalFunction(): Promise
return response.text();
}
}
typescript tab="TypeScript v2"
import { getSource, getHttpsConnection, getFetch } from "@palantir/functions-sources";
export const config = {
sources: ["mySourceAlias"]
}
async function MyExternalFunction(): Promise
```python tab="Python" from functions.api import function from functions.sources import get_source
@function(sources=["mySourceAlias"]) def my_external_function() -> str: source = get_source("mySourceAlias") url = source.get_https_connection().url client = source.get_https_connection().get_client() response = client.get(url) return response.text
您可以在实时预览中测试您的函数,并在发布后使用它进行外部调用。
:::callout{theme="warning"}
**在不覆盖 fetch 函数或 HTTP 代理(HTTP agent)的情况下,第三方客户端尚不支持无服务器执行(serverless execution)或实时预览(live preview)。** 为确保您的 API 调用在所有环境中正常运行,您必须使用相关的库方法,以正确的配置发起请求。直接对外部源或内部 Foundry URL 发起的 API 调用不能保证在所有环境中都能正常工作。
:::
## 访问源属性和凭证
您可以访问每种函数类型对应库提供的源属性。
下面的示例展示了如何获取上述示例中源的基础 URL。
```typescript tab="TypeScript v1"
const { url } = MySource.getHttpsConnection();
```typescript tab="TypeScript v2" const { url } = getHttpsConnection(source);
```python tab="Python"
url = get_source("mySourceAlias").get_https_connection().url
您还可以使用以下语法访问存储在源上的其他机密或凭证:
```typescript tab="TypeScript v1" const secret = MySource.getSecret("MySecret");
```typescript tab="TypeScript v2"
const secret = source.secrets["MySecret"];
```python tab="Python" secret = get_source("mySourceAlias").get_secret("MySecret")
## 使用预配置的客户端
对于提供 REST API 的源,源对象允许您检索一个客户端。该客户端将使用源上指定的服务器和客户端证书进行预配置。它还将包含额外的代理配置,允许从函数执行的环境进行出站访问。如果可能,您应始终使用此客户端,以确保您的函数能够从所有环境出站访问该源。
```typescript tab="TypeScript v1"
const fetch = MySource.fetch;
```typescript tab="TypeScript v2" const fetch = await getFetch(source);
```python tab="Python"
client = source.get_https_connection().get_client()
或者,您可以使用自己的客户端或发起外部请求的第三方库,并使用源对象来检索属性和凭证。
TypeScript v2 函数提供了一个预配置的 HTTP 代理(HTTP agent),作为与接受自定义 HTTP 代理的第三方库集成的额外接入点。
以下示例演示了如何检索此代理并将其与 axios ↗ 一起使用。
```typescript tab="TypeScript v2" import { getHttpAgent, getHttpsConnection } from "@palantir/functions-sources"; import axios from 'axios';
const agent = await getHttpAgent(source); const { url } = getHttpsConnection(source);
const response = await axios.get(url, { httpsAgent: agent, });
:::callout{theme="neutral"}
目前,除非源提供了 HTTPS 客户端,否则无法访问非凭证类的源属性。例如,您将无法访问 [PostgreSQL 源](https://palantir.com/docs/foundry/available-connectors/postgresql/)上的 `hostname` 或其他非机密属性。
:::
## 使用带有出站应用程序(Outbound Applications)的 OAuth 2.0
如果您的外部 API 需要 OAuth 2.0 授权,您可以在控制面板(Control Panel)中配置一个[出站应用程序](https://palantir.com/docs/foundry/administration/configure-outbound-applications/),并将其用作 REST API 源的身份验证方法。当您的函数运行时,源会将调用用户的 OAuth 访问令牌作为会话凭证(session credentials)暴露出来。然后,您的函数可以使用该令牌代表用户调用外部 API。
此模式在 Python 和 TypeScript v2 函数中受支持。请参阅下面的[使用源的预配置客户端](#使用源的预配置客户端)部分获取代码示例。
### 限制
* **TypeScript v1:** TypeScript v1 函数无法直接从源检索 OAuth 令牌。要从 TypeScript v1 函数向 OAuth 2.0 API 进行身份验证,请将对 REST API 源(已配置出站应用程序)的调用包装在 [webhook](https://palantir.com/docs/foundry/functions/webhooks/) 中。考虑[迁移到 TypeScript v2](https://palantir.com/docs/foundry/functions/typescript-v2-migration/) 以直接访问令牌。
* **部署模式(Deployed mode):** 当函数在[部署模式](https://palantir.com/docs/foundry/functions/functions-deployed/)下运行时,OAuth 令牌刷新不可用。如果调用用户的访问令牌在执行期间过期,函数无法自动刷新它。请以[无服务器模式](https://palantir.com/docs/foundry/functions/functions-deployed/#choose-between-deployed-and-serverless-execution-modes)运行您的函数,以使用 OAuth 支持的出站应用程序。
* **在 Workshop 中直接使用函数:** 直接在 [Workshop](https://palantir.com/docs/foundry/workshop/overview/) 模块中使用的函数,例如[函数支持的变量(function-backed variables)](https://palantir.com/docs/foundry/workshop/functions-use/#function-backed-variables-in-workshop)或填充小部件内容的函数,无法触发 OAuth 2.0 交互式授权提示。如果用户尚未授权出站应用程序,函数将失败而不会显示提示。要从 Workshop 使用 OAuth 支持的函数,请将其包装在[函数支持的操作(function-backed action)](https://palantir.com/docs/foundry/action-types/function-actions-overview/)中。或者,确保用户在 Workshop 中直接调用函数之前,通过其他交互界面(例如针对同一出站应用程序的函数支持的操作)完成授权流程。
### 使用源的预配置客户端
最简单的方法是使用源提供的 HTTP 客户端。`Authorization` 标头会自动注入。
```python tab="Python"
from functions.api import function
from functions.sources import get_source
@function(sources=["myOAuthSourceAlias"])
def call_external_api() -> str:
source = get_source("myOAuthSourceAlias")
url = source.get_https_connection().url
client = source.get_https_connection().get_client()
response = client.get(url + "/api/v1/resource", timeout=10)
return response.text
```typescript tab="TypeScript v2" import { getSource, getHttpsConnection, getFetch } from "@palantir/functions-sources";
export const config = { sources: ["myOAuthSourceAlias"] };
export default async function callExternalApi(): Promise
const response = await fetch(url + "/api/v1/resource");
return response.text();
} ```
使用原生 HTTP 客户端并手动注入令牌¶
如果您需要使用自己的 HTTP 客户端而不是源提供的客户端,请从会话凭证中检索 OAuth 令牌并手动设置 Authorization 标头。
python tab="Python"
import requests
from functions.api import function
from functions.sources import get_source
from external_systems.sources import OauthCredentials, Refreshable, SourceCredentials
@function(sources=["myOAuthSourceAlias"])
def call_external_api() -> str:
source = get_source("myOAuthSourceAlias")
url = source.get_https_connection().url
refreshable_credentials: Refreshable[SourceCredentials] = source.get_session_credentials()
session_credentials: SourceCredentials = refreshable_credentials.get()
if not isinstance(session_credentials, OauthCredentials):
raise ValueError("Expected OAuth credentials")
access_token: str = session_credentials.access_token
response = requests.get(
url + "/api/v1/resource",
headers={"Authorization": f"Bearer {access_token}"},
timeout=10,
)
return response.text
```typescript tab="TypeScript v2" import { getSource, getHttpsConnection } from "@palantir/functions-sources";
export const config = { sources: ["myOAuthSourceAlias"] };
export default async function callExternalApi(): Promise
if (!credentials || credentials.type !== "oauth") {
throw new Error("Expected OAuth credentials");
}
const accessToken: string = credentials.accessToken;
const { url } = getHttpsConnection(source);
const response = await fetch(url + "/api/v1/resource", {
headers: { Authorization: `Bearer ${accessToken}` },
});
return response.text();
} ```
在操作(Actions)中使用 OAuth 支持的函数¶
一种常见模式是调用 OAuth 支持的外部 API,并将结果输入到本体编辑(Ontology edit)中。然后,您可以通过函数支持的操作公开该函数。当用户从 Workshop 或 AIP Studio 运行该操作时,将使用他们的 OAuth 令牌进行 API 调用,并且生成的对象编辑将归属于他们。
例如,下面的函数使用 OAuth 令牌从第三方身份服务获取调用用户的个人资料。然后,它使用该信息创建一个新的本体对象:
python tab="Python"
from functions.api import function, OntologyEdit
from functions.sources import get_source
from ontology_sdk import FoundryClient
from ontology_sdk.ontology.objects import UserProfile
@function(sources=["myOAuthSourceAlias"], edits=[UserProfile])
def link_user_profile() -> list[OntologyEdit]:
source = get_source("myOAuthSourceAlias")
url = source.get_https_connection().url
client = source.get_https_connection().get_client()
response = client.get(url + "/v1/me", timeout=10)
response.raise_for_status()
profile = response.json()
ontology_edits = FoundryClient().ontology.edits()
ontology_edits.objects.UserProfile.create(
profile["id"],
display_name=profile["display_name"],
)
return ontology_edits.get_edits()
```typescript tab="TypeScript v2" import { getSource, getHttpsConnection, getFetch } from "@palantir/functions-sources"; import { UserProfile } from "@ontology/sdk"; import { Client } from "@osdk/client"; import { createEditBatch, Edits } from "@osdk/functions";
type OntologyEdit = Edits.Object
export const config = { sources: ["myOAuthSourceAlias"], edits: [UserProfile], };
export default async function linkUserProfile(client: Client): Promise
const response = await fetch(url + "/v1/me");
if (!response.ok) {
throw new Error(`Failed to fetch profile: ${response.status}`);
}
const profile = await response.json();
const batch = createEditBatch<OntologyEdit>(client);
batch.create(UserProfile, {
userProfileId: profile.id,
displayName: profile.display_name,
});
return batch.getEdits();
} ```
排查常见错误¶
对于 OAuth 授权错误,例如 HTTP 401: Unauthorized、Credentials expired and no refresh handler provided 或 Resolved source credentials are not present on the Source,请参阅数据连接故障排除参考中的 OAuth 和出站应用程序部分。
HTTP 407: 需要代理身份验证(Proxy authentication required)¶
函数网络请求必须受您的源出站策略(egress policies)的覆盖。如果目标主机名与允许的策略不匹配,请求可能会返回 HTTP 407: Proxy Authentication Required。
如果您的出站策略看起来正确,请检查请求 URL 的构建方式。getHttpsConnection() 返回的 URL 没有尾部斜杠,因此如果附加的路径省略了前导 /,则会被拼接到主机名上:
text
"https://example.com" + "api/v1"
→ "https://example.comapi/v1"
生成的主机名 (example.comapi) 不受任何出站策略的覆盖,因此请求被拒绝。请在路径前加上 /(例如,url + "/api/v1")。