PAYATHON 2026

Upwork 公共 GraphQL 如何获取 agency 周工时并排查 403?

数据小许

结论

先检查公共 schema 中的 organization.agencyContractTimeReport 和 organization.agencyTimeReport。Upwork 公开文档列出了这两个 agency 报表字段,因此公共接口对 agencyCompanyTimeReport 返回 FieldUndefined,并不能说明公共 API 完全不支持 agency 工时报表。Upwork GraphQL API 文档

目前也不能直接认定是 Upwork 配错了权限。现有结果只能确认组织上下文已切换,而所尝试的查询仍未取得目标数据。上述 agency 字段是否对当前应用和用户可用,是否覆盖 UI 中相同的费用口径,都需要继续验证。

为什么切换组织成功,报表仍然被拒绝?

organization 返回正确的 agency,说明当前请求选择了预期组织,但不能证明用户和 API Key 对所有报表都有访问权限。

错误中的关键部分是:

view_gds_hourly_data_from_client
view_gds_charges_data_from_client

从这些 action 名称看,这次调用很可能触发了 client 方向的团队数据权限检查。这只是根据错误文本作出的判断,不能确定 Upwork 内部的权限映射规则,也不能推导出应给 agency 用户添加哪些 client 角色。

UI 角色、API Key scopes 和具体团队的数据访问权限需要分别检查。公开文档为顶层 timeReport 和 contractTimeReport 标注了 TimeSheet - Read-Only Access,并建议使用 contractTimeReport 替代 timeReport;这并不意味着更换字段就能绕过当前的权限拒绝。Upwork 报表与权限说明

先确认 agency 报表字段的实际参数

公共字段位于哪个对象下、需要什么参数,应以公共 schema 为准。不要把内部接口的 operation 名称、参数或 company UID 直接搬到公共接口。

如果公共端点允许 introspection,可以使用相同的公共 OAuth2 token 和 tenant header 执行:

query InspectAgencyReportFields {
  __type(name: "CurrentOrganization") {
    name
    fields(includeDeprecated: true) {
      name
      args {
        name
        defaultValue
        type {
          kind
          name
          ofType {
            kind
            name
            ofType {
              kind
              name
            }
          }
        }
      }
      type {
        kind
        name
        ofType {
          kind
          name
          ofType {
            kind
            name
          }
        }
      }
    }
  }
}

在结果中查找 agencyContractTimeReport 和 agencyTimeReport,确认必填参数、日期类型及返回结构,再构造查询。如果 introspection 不可用,就通过官方文档确认这些信息;introspection 被禁用本身不能证明报表字段不存在。

建议先查询一个已在 UI 中确认有工时的日期或最小日期范围,只请求工时字段。查询成功后,再增加 freelancer、合同和费用字段,以便判断问题出在整个报表入口,还是某个新增字段的访问权限上。

如何理解另外两个结果?

workDiaryCompany 返回空数组,只能说明该次查询没有返回可见的 snapshot,不能证明 agency 没有工时,也不能证明费用报表不可用。文档中该字段用于查询指定公司、指定日期的 WorkDiary 和截图数据,并有独立的权限要求。Upwork WorkDiary 接口说明

排查时,应确认 companyId 的标识符类型符合字段要求,并选择一个已知有截图的日期。<AGENCY_ORG_ID> 与 <AGENCY_COMPANY_UID> 即使指向同一业务组织,也不应被视为可互换。

内部 agencyCompanyTimeReport 能返回数据,说明浏览器会话对应的访问方式可以取得这些记录。这不能证明公共 OAuth2 应用拥有相同权限,也不能证明公共接口存在同名字段。把公共 token 换成内部 token,并不能解决公共 API 的正式接入问题。

什么时候需要 Upwork 介入?

如果使用公共 schema 中的 agency 报表入口后仍然失败,再向 Upwork 提交最小复现,请其重点确认:

  • 当前 API Key 是否获准访问 agency 工时和费用报表。
  • 授权用户在目标 agency 及相关团队上的权限是否覆盖这些字段。
  • 公共报表是否支持所需的 freelancer、工时、费用及合同维度。
  • 若仍出现 _from_client action 拒绝,是否属于该查询的预期权限校验。

附上查询、variables、组织标识、日期范围和完整错误信息即可,不要附带 token 或浏览器 session。Upwork 文档也将扩展或调整 API Key scopes 的问题指向其支持团队。Upwork API 权限排查说明

取得数据后怎样生成周汇总?

报表成功返回目标记录后,读取完整分页,再按目标周和 freelancer 分组汇总工时,收集对应合同。汇总前应确认记录粒度,避免把同一合同的不同日期、任务或条款记录误当作重复数据删除。

费用需要单独核对口径。UI 中的费用、报表中的 totalCharges 和 agency 实际收入,不能仅凭名称相近就当作同一指标;应先确认币种、费用组成及日期归属,再与 UI 对账。原示例只覆盖 20260406 至 20260407,正式生成周报时还需要确认完整周范围及起止边界。

下一步应验证公共 agency 报表入口。还需结合该入口的查询结果和 Upwork 对应用权限的确认,才能进一步区分权限配置问题与当前公共 API 的覆盖限制。

备注:内容仅供参考。