# 调用服务端 API 

本文档介绍通过 Python SDK 构造 API 请求、调用开放平台服务端 API 的详细步骤。

## 步骤一：构建 API Client

通过 SDK 调用飞书开放接口之前，你需要先在代码中创建一个 API Client，用来指定当前使用的应用、日志级别、HTTP 请求超时时间等基本信息。

```python
import lark_oapi as lark
client = lark.Client.builder() \
    .app_id("APP_ID") \
    .app_secret("APP_SECRET") \
    .domain(lark.FEISHU_DOMAIN) \
    .timeout(3) \
    .app_type(lark.AppType.ISV) \
    .app_ticket("xxxx") \
    .enable_set_token(False) \
    .cache(Cache()) \
    .log_level(lark.LogLevel.DEBUG) \
    .build()
```

各配置项的具体含义参见下表。

配置选项 | 配置方式 | 是否必填 | 描述
---|---|---|---
app_id | client.app_id("APP_ID") | 是 | 用于设置应用的 App ID。app_id、app_secret 需要传入真实的应用凭证 App ID 和 App Secret，需登录开发者后台，在应用详情页的 **凭证与基础信息** > **应用凭证** 区域获取。
app_secret | client.app_secret("APP_SECRET") | 是 | 用于设置应用的 App Secret。
domain | client.domain(lark.FEISHU_DOMAIN) | 否 | 用于设置飞书域名。<br>- 飞书（默认值）： `https://open.feishu.cn`<br>- Lark：`https://open.larksuite.com`
timeout | client.timeout(3) | 否 | 用于设置客户端超时时间，单位：秒。不传值默认为永不超时。
app_type | client.app_type(lark.AppType.ISV) | 否 | 用于指定应用类型。<br>- 不传值默认表示企业自建应用<br>- 传入 lark.AppType.ISV 表示商店应用，且需要在 RequestOption 中配置 [tenant_key](https://open.feishu.cn/document/ukTMukTMukTM/uYTM5UjL2ETO14iNxkTN/terminology#495685b5)
app_ticket | client.app_ticket("xxxx") | 否 | 当 app_type 指定商店应用时，需要在该参数内传入应用的 app_access_token。获取方式参见[商店应用获取 app_access_token](https://open.feishu.cn/document/ukTMukTMukTM/ukDNz4SO0MjL5QzM/auth-v3/auth/app_access_token)。
enable_set_token | client.enable_set_token(False) | 否 | 是否允许手动设置 token。<br>- False（默认值）：不允许<br>- True：允许，传入该值后需要在 RequestOption 中手动配置 token
cache | client.cache(Cache()) | 否 | 用于自定义缓存，默认使用预置的本地缓存。
log_level | client.log_level(lark.LogLevel.DEBUG) | 否 | 用于设置日志级别。枚举值：<br>- lark.LogLevel.CRITICAL：严重错误日志，程序可能中断或崩溃。<br>- lark.LogLevel.ERROR：错误日志，程序部分功能无法运行。<br>- lark.LogLevel.WARNING（默认值）：潜在问题或警告，一般不阻塞程序运行。<br>- lark.LogLevel.INFO：程序运行日志，一般用于确认程序是否按预期工作。<br>- lark.LogLevel.DEBUG：调试日志，一般用于调试时诊断问题。

示例配置：

- 对于自建应用使用以下代码创建 API Client。

```python
    import lark_oapi as lark

client = lark.Client.builder() \
        .app_id("APP_ID") \
        .app_secret("APP_SECRET") \
        .build()
    ```

- 对于商店应用，需在创建 API Client 时，使用 `app_type(lark.AppType.ISV)` 方法指定 AppType 为商店应用。

```python
    import lark_oapi as lark

client = lark.Client.builder() \
        .app_id("APP_ID") \
        .app_secret("APP_SECRET") \
        .app_type(lark.AppType.ISV) \
        .build()
    ```

### 步骤二：构造 API 请求

在项目内创建 API Client 后，即可通过 Client 调用[飞书开放接口](https://open.feishu.cn/document/ukTMukTMukTM/uYTM5UjL2ETO14iNxkTN/server-api-list)。如下图示例，你可前往[ API 调试台](https://open.feishu.cn/api-explorer?from=op_doc)，直接获取指定服务端 API 相关示例代码。

![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/317e4c4529074263ba75165e15ee6e79_3GGjTqVFmx.png?height=1392&lazyload=true&maxWidth=600&width=2882)

以[通过手机号或邮箱获取用户 ID](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/contact-v3/user/batch_get_id) 接口为例，调用示例如下所示。

```python
# Code generated by Lark OpenAPI.

import lark_oapi as lark
from lark_oapi.api.contact.v3 import *

client = lark.Client.builder() \
    .app_id("APP_ID") \
    .app_secret("APP_SECRET") \
    .log_level(lark.LogLevel.DEBUG) \
    .build()

# 构造请求对象
request: BatchGetIdUserRequest = BatchGetIdUserRequest.builder() \
    .user_id_type("open_id") \
    .request_body(BatchGetIdUserRequestBody.builder()
                  .emails(["xxxx@bytedance.com"])
                  .mobiles(["15000000000"])
                  .build()) \
    .build()

# 发起请求
response: BatchGetIdUserResponse = client.contact.v3.user.batch_get_id(request)

# 处理失败返回
if not response.success():
    lark.logger.error(
        f"client.contact.v3.user.batch_get_id failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}")

# 处理业务结果
lark.logger.info(lark.JSON.marshal(response.data, indent=4))
```
代码片段解析：

1. 导入接口所属的业务资源包。

查阅[通过手机号或邮箱获取用户 ID](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/contact-v3/user/batch_get_id) 接口文档可知 **HTTP URL** 为 `https://open.feishu.cn/open-apis/contact/v3/users/batch_get_id`，根据 `/open-apis`后的 Path 引入相匹配的包。

```python
    from lark_oapi.api.contact.v3 import *
    ```

2. 配置 API Client，详细配置说明参考上文 **步骤一：配置 API Client** 章节。

```python
    client = lark.Client.builder() \
        .app_id("APP_ID") \
        .app_secret("APP_SECRET") \
        .log_level(lark.LogLevel.DEBUG) \
        .build()
    ```

3. 配置调用接口所需传入的请求对象，接口具体参数说明参见[通过手机号或邮箱获取用户 ID](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/contact-v3/user/batch_get_id)。

```python
    # 构造请求对象
    request: BatchGetIdUserRequest = BatchGetIdUserRequest.builder() \
        .user_id_type("open_id") \
        .request_body(BatchGetIdUserRequestBody.builder()
                      .emails(["xxxx@bytedance.com"])
                      .mobiles(["15000000000"])
                      .build()) \
        .build()
    ```
4. 通过 API client 发起请求。

```python
    # 发起请求
    response: BatchGetIdUserResponse = client.contact.v3.user.batch_get_id(request)
    ```

SDK 内通过 **client.业务域.版本号.资源.方法名称** 来定位具体的 API 方法。你可前往 [API 调试台](https://open.feishu.cn/api-explorer?from=op_doc)，在指定 API 的浏览器地址栏获取相应的参数信息，如下图所示。

- 方式一：查阅指定 API 的示例代码，从代码中直接获取用于构造 API 请求的方法。
	- 方式二：通过指定 API 的浏览器地址栏获取相关参数，以 **client.业务域.版本号.资源.方法名称** 格式拼接 API 方法。

- 下图中 ① project 代表 **业务域**
 	 	- 下图中 ② version 代表 **版本号**
	  	- 下图中 ③ resource 代表 **资源**
 	 	- 下图中 ④ apiName 代表 **方法名称**

![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/b3ef87deb53082b8984fe88cb610457d_TWg5PFQjxY.png?height=747&lazyload=true&maxWidth=600&width=1280)

如果开发工具支持快捷提示，可以通过下图的方式引用出 SDK 内对应的 API 方法。

![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/bee1ecc52e6e1a9465072f2c17030f8a_ovRJr4hFRl.gif?height=1168&lazyload=true&maxWidth=600&width=1774)

更多调用示例参见[请求示例](https://github.com/larksuite/oapi-sdk-python/blob/v2_main/samples/api)。

### （可选）步骤三：设置请求选项

在每次发起 API 调用时，你可以设置请求级别的相关参数，例如传递 user_access_token（用户访问凭证）、自定义 headers 等。在代码中需使用 RequestOptions 的 Builder 模式构建请求级别的参数，参数说明如下。

配置选项 | 描述
---|---
tenant_key | 租户 key，商店应用必须设置该选项。
user_access_token | 用户身份 Token，创建 Client 时 enable_set_token 如果设置为 True，则需要根据接口实际所需选择传入该值。获取方式：[获取 user_access_token](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/authentication-management/access-token/get-user-access-token)
tenant_access_token | 应用身份 Token，创建 Client 时 enable_set_token 如果设置为 True，则需要根据接口实际所需选择传入该值。获取方式：<br>- [商店应用获取 tenant_access_token](https://open.feishu.cn/document/ukTMukTMukTM/ukDNz4SO0MjL5QzM/auth-v3/auth/tenant_access_token)<br>- [自建应用获取 tenant_access_token](https://open.feishu.cn/document/ukTMukTMukTM/ukDNz4SO0MjL5QzM/auth-v3/auth/tenant_access_token_internal)
app_access_token | 应用身份短期令牌，创建 Client 时 enable_set_token 如果设置为 True，则需要根据接口实际所需选择传入该值。获取方式：<br>- [商店应用获取 app_access_token](https://open.feishu.cn/document/ukTMukTMukTM/ukDNz4SO0MjL5QzM/auth-v3/auth/app_access_token)<br>- [自建应用获取 app_access_token](https://open.feishu.cn/document/ukTMukTMukTM/ukDNz4SO0MjL5QzM/auth-v3/auth/app_access_token_internal)
headers | 自定义请求头，`Key:Value` 形式。这些请求头会被透传到飞书开放平台服务端。

以下提供了创建自定义 headers 的示例代码供参考：

```python
import lark_oapi as lark
from lark_oapi.api.contact.v3 import *

# 创建client
client = lark.Client.builder() \
    .enable_set_token(True) \
    .log_level(lark.LogLevel.DEBUG) \
    .build()

# 构造请求对象
request: BatchGetIdUserRequest = BatchGetIdUserRequest.builder() \
    .user_id_type("open_id") \
    .request_body(BatchGetIdUserRequestBody.builder()
                  .emails(["xxxx@bytedance.com"])
                  .mobiles(["15000000000"])
                  .build()) \
    .build()

# 设置请求选项
headers = {"key1": "value1", "key2": "value2"}
req_opt = lark.RequestOption.builder()\
    .user_access_token("u-dqBLyo0Mp7Ar7j2x1d.Qi71gjoR0kgTxUy00050E2zTz")\
    .headers(headers)\
    .build()

# 发起请求
response: BatchGetIdUserResponse = client.contact.v3.user.batch_get_id(request, req_opt)

# 处理失败返回
if not response.success():
    lark.logger.error(
        f"client.contact.v3.user.batch_get_id failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}")

# 处理业务结果
lark.logger.info(lark.JSON.marshal(response.data, indent=4))
```

### 步骤四：运行项目

完成以上步骤后，即可运行项目调用 API，如下图所示通过开发工具运行项目，你也可以根据项目部署情况在命令行内通过 `python {.py 项目文件}` 命令运行。

![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/1c861e2aa8e73c891a6a1b15cd9a248a_wlctgrEIUQ.gif?height=840&lazyload=true&maxWidth=600&width=2470)

- 如果调用成功，会返回成功状态码以及 API 的响应参数。

![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/db6339b5f99eff5d548b8aefeb12d622_gLLxi9s7Gd.png?height=748&lazyload=true&maxWidth=600&width=2660)

- 如果调用失败，会返回指定的错误码与错误信息。

![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/af138b29a4547a230999b9dbe79b4b7a_pJCTburhlf.png?height=884&lazyload=true&maxWidth=600&width=2620)

## 常见问题

### 如何快速获取接口对应的示例代码？

飞书开放平台提供了 [API 调试台](https://open.feishu.cn/api-explorer)，通过该平台可以快速调试服务端 API，快速获取资源 ID 及生成多语言示例代码的能力，为您节省开发成本。例如，通过 API 调试台调用[通过手机号或邮箱获取用户 ID](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/contact-v3/user/batch_get_id) 接口，在调试台成功完成测试后，可通过 **示例代码** 页面查阅 Python SDK 对应的接口调用代码。

![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/16cdda32b6fa983eb0c8a994d760e66b_suwL8db3cp.png?height=1418&lazyload=true&maxWidth=600&width=2882)

### 如何调用历史版本 API、API 调试台搜索不到的 API、SDK 内找不到方法的 API ？

可以使用 SDK 提供的原生模式调用 API。以调用[通过手机号或邮箱获取用户 ID](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/contact-v3/user/batch_get_id) 接口为例，示例代码如下。

```python
import lark_oapi as lark

# 创建client
client = lark.Client.builder() \
    .app_id("APP_ID") \
    .app_secret("APP_SECRET") \
    .log_level(lark.LogLevel.DEBUG) \
    .build()

# 构造请求对象
request: lark.BaseRequest = lark.BaseRequest.builder() \
    .http_method(lark.HttpMethod.POST) \
    .uri("/open-apis/contact/v3/users/batch_get_id") \
    .token_types({lark.AccessTokenType.TENANT}) \
    .queries([("user_id_type", "open_id")]) \
    .body({"emails": ["xxxx@bytedance.com"], "mobiles": ["15000000000"]}) \
    .build()

# 发起请求
response: lark.BaseResponse = client.request(request)

# 处理失败返回
if not response.success():
    lark.logger.error(
        f"client.request failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}")

# 处理业务结果
lark.logger.info(str(response.raw.content, lark.UTF_8))
```

通过 `request: lark.BaseRequest = lark.BaseRequest.builder()` 构造请求对象，配置说明如下：

配置项 | 说明
---|---
lark.BaseRequest.http_method(lark.HttpMethod.POST) | 设置 HTTP Method。格式为 `lark.HttpMethod.XXX`，例如，POST 请求取值 `lark.HttpMethod.POST`、GET 请求取值 `lark.HttpMethod.GET`。
lark.BaseRequest.uri("/open-apis/contact/v3/users/batch_get_id") | 设置 HTTP URL，仅取值域名后的 Path。例如 `https://open.feishu.cn/open-apis/contact/v3/users/batch_get_id` 实际取值为 `/open-apis/contact/v3/users/batch_get_id`。<br>如果接口有路径参数（Path），则需要拼接在 HTTP URL 内。
lark.BaseRequest.token_types({lark.AccessTokenType.TENANT} | 设置所用的身份 token。枚举值：<br>- lark.AccessTokenType.TENANT：使用 tenant_access_token<br>- lark.AccessTokenType.USER：使用 user_access_token<br>- lark.AccessTokenType.APP：使用 app_access_token
lark.BaseRequest.queries([("user_id_type", "open_id")]) | 如果接口有查询参数（Query），则需要通过该配置项传入。
lark.BaseRequest.body({"emails": ["xxxx@example.com"], "mobiles": ["1500000xxxx"]}) | 接口请求体（Body）通过该配置项传入。

其他调用示例参见[原生调用](https://github.com/larksuite/oapi-sdk-python/blob/v2_main/samples/api/raw.py)。

### 如何准确选择 SDK 内 API 对应的方法？

使用 API Client 调用 API 时，对应的方法建议你借助 [API 调试台](https://open.feishu.cn/api-explorer/)获取，可通过指定接口的地址栏参数拼接方法，也可以直接参考接口提供的示例代码。以[通过手机号或邮箱获取用户 ID](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/contact-v3/user/batch_get_id) 接口为例，获取方式如下图所示。

![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/5b42cd293c1e26079d8ec616349f25b1_LkgXlTfmG5.png?height=1684&lazyload=true&maxWidth=600&width=2882)

### 如何异步调用 API？

在原有 SDK 调用代码中，调用的方法名前面加上一个 `a` 前缀，即可变成 async/await 模式的异步方法。示例代码如下：

```python
import lark_oapi as lark
from lark_oapi.api.im.v1 import *

# 同步调用版本的「发送消息」
def main():
    # 创建client
    client = lark.Client.builder() \
        .app_id(lark.APP_ID) \
        .app_secret(lark.APP_SECRET) \
        .log_level(lark.LogLevel.DEBUG) \
        .build()

# 构造请求对象
    request: CreateMessageRequest = CreateMessageRequest.builder() \
        .receive_id_type("open_id") \
        .request_body(CreateMessageRequestBody.builder()
                      .receive_id("ou_7d8a6e6df7621556ce0d21922b676706ccs")
                      .msg_type("text")
                      .content("")
                      .uuid("a0d69e20-1dd1-458b-k525-dfeca4015204")
                      .build()) \
        .build()

# 发起请求
    response: CreateMessageResponse = client.im.v1.message.create(request)

# 处理失败返回
    if not response.success():
        lark.logger.error(
            f"client.im.v1.message.create failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}")
        return

# 处理业务结果
    lark.logger.info(lark.JSON.marshal(response.data, indent=4))

# 异步方式版本的「发送消息」
async def amain():
    # 创建client
    client = lark.Client.builder() \
        .app_id(lark.APP_ID) \
        .app_secret(lark.APP_SECRET) \
        .log_level(lark.LogLevel.DEBUG) \
        .build()

# 构造请求对象
    request: CreateMessageRequest = CreateMessageRequest.builder() \
        .receive_id_type("open_id") \
        .request_body(CreateMessageRequestBody.builder()
                      .receive_id("ou_7d8a6e6df7621556ce0d21922b676706ccs")
                      .msg_type("text")
                      .content("")
                      .uuid("a0d69e20-1dd1-458b-k525-dfeca4015204")
                      .build()) \
        .build()

# 发起请求
    response: CreateMessageResponse = await client.im.v1.message.acreate(request)

# 处理失败返回
    if not response.success():
        lark.logger.error(
            f"client.im.v1.message.acreate failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}")
        return

# 处理业务结果
    lark.logger.info(lark.JSON.marshal(response.data, indent=4))

if __name__ == "__main__":
    asyncio.run(amain()) # 异步方式
    # main()
````
