> ## Documentation Index
> Fetch the complete documentation index at: https://docs.querying.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Amazon S3, Google Cloud Storage, BigQuery로 결과 내보내기

> 완료된 모든 태스크와 채점된 모니터 답변을 내 버킷의 시간별 또는 일별 JSON Lines 파일이나 BigQuery 테이블 파티션으로 받습니다.

내보내기는 한 시간 또는 UTC 하루마다 그 구간의 결과를 gzip 압축 JSON Lines 파일로 Amazon S3나 Google
Cloud Storage 버킷에 쓰거나, BigQuery 테이블 파티션으로 적재합니다. 데이터 웨어하우스는 다른 소스와
같은 방식으로 읽으므로 폴링 루프가 필요 없습니다.

버킷이나 데이터셋에 우리 내보내기 ID의 쓰기 권한만 주면 되고, 키나 비밀번호는 공유하지 않습니다. 이
페이지의 키, 버킷, 계정 ID, role은 모두 예시 값입니다.

## 내보내는 데이터

| 데이터 세트 | 행 | 구간 기준 | 같은 필드 |
| - | - | - | - |
| `tasks` | `COMPLETED` 또는 `FAILED`로 끝난 계정의 모든 태스크 | `completedAt` | [`GET /v1/async/task/{id}`](/ko/api-reference/get-task) 및 웹훅 |
| `monitor_results` | 계정의 모든 모니터가 채점한 모든 답변 | `ranAt` | [`GET /v1/monitors/{id}/results?includeEvidence=true`](/ko/api-reference/monitors/export-results) |

계정의 어떤 키로 보낸 태스크든, Playground와 모니터가 만든 태스크까지 모두 들어갑니다. 한 구간에는
시각이 `[windowStart, windowEnd)`에 속하는 행만 정확히 담깁니다.

**전달 시점.** 구간은 닫히고 10분 뒤에 전달 대상이 되므로, 경계 시각에 정산된 과금도 그 구간에
들어갑니다. 전달 대상 구간은 15분마다 도는 작업이 가져가므로, 보통 구간이 닫히고 10–25분 뒤에 전달이
시작됩니다.

## 내보내기 설정

**1. 내보내기 ID 확인**

```bash theme={"theme":"css-variables"}
curl https://api.querying.ai/v1/exports/identity \
  -H "Authorization: Bearer $QUERYING_API_KEY"
```

```json theme={"theme":"css-variables"}
{
  "success": true,
  "identity": {
    "accountKey": "acct_3f9a1c2b7d4e",
    "gcp": { "serviceAccount": "querying-export@example-project.iam.gserviceaccount.com" },
    "aws": {
      "federatedPrincipal": "accounts.google.com",
      "audience": "117000000000000000001",
      "oaud": "querying-result-export",
      "roleSessionName": "querying-acct_3f9a1c2b7d4e",
      "trustPolicy": { "Version": "2012-10-17", "Statement": ["..."] }
    }
  }
}
```

`accountKey`는 고객 폴더와 테이블의 이름입니다. 우리가 쓰는 모든 경로와 테이블에 이 값이 들어가므로,
querying.ai 계정 여러 개가 버킷이나 데이터셋 하나를 함께 써도 각자 자기 파일만 씁니다. 위의 예시
값이 아니라 응답에 나온 ID에 권한을 주세요.

**2. 권한 부여**

대상 저장소에 이 ID의 권한을 줍니다. S3, Cloud Storage, BigQuery별 방법은
[권한 부여](#권한-부여)를 참고하세요.

**3. 내보내기 만들기**

`POST /v1/exports`가 권한을 확인하고 내보내기를 저장합니다.
[내보내기 만들기](#내보내기-만들기)를 참고하세요.

**4. 첫 전송 확인**

`GET /v1/exports/{id}/runs`가 구간별 상태를 나열합니다. [전송 이력](#전송-이력)을 참고하세요.

## 권한 부여

### Amazon S3

우리 Google ID가 맡을 수 있는 IAM role을 AWS 계정에 만듭니다. ID 응답의 `trustPolicy`를 role의 신뢰
정책으로 쓰세요. 이 정책은 우리 내보내기 ID가 고객 계정의 세션 이름으로 요청할 때만 받아들입니다.

```json theme={"theme":"css-variables"}
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Federated": "accounts.google.com" },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": { "StringEquals": {
      "accounts.google.com:aud": "117000000000000000001",
      "accounts.google.com:oaud": "querying-result-export",
      "sts:RoleSessionName": "querying-acct_3f9a1c2b7d4e"
    } }
  }]
}
```

role이 접두사 아래에 쓸 수 있도록 권한 정책을 붙입니다.

```json theme={"theme":"css-variables"}
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "s3:PutObject",
    "Resource": "arn:aws:s3:::acme-geo/querying/*"
  }]
}
```

버킷이 고객 관리 KMS 키로 암호화한다면 그 키에 `kms:GenerateDataKey`도 허용합니다.

### Google Cloud Storage

버킷에 내보내기 서비스 계정의 **Storage Object User**(`roles/storage.objectUser`) 권한을 줍니다. 이
권한으로 파일을 만들고, 구간을 다시 보낼 때 파일을 교체합니다.

```bash theme={"theme":"css-variables"}
gcloud storage buckets add-iam-policy-binding gs://acme-geo \
  --member="serviceAccount:querying-export@example-project.iam.gserviceaccount.com" \
  --role="roles/storage.objectUser"
```

권한을 고객 폴더로 좁히려면
`resource.name.startsWith("projects/_/buckets/acme-geo/objects/querying/acct_3f9a1c2b7d4e/")` IAM
조건을 붙입니다.

### BigQuery

데이터셋에 내보내기 서비스 계정의 **BigQuery Data Editor**(`roles/bigquery.dataEditor`) 권한을 줍니다.
적재 job은 우리 프로젝트에서 실행되므로 적재 비용은 들지 않고, 저장 비용과 고객이 실행하는 쿼리 비용만
냅니다.

```bash theme={"theme":"css-variables"}
bq add-iam-policy-binding \
  --member="serviceAccount:querying-export@example-project.iam.gserviceaccount.com" \
  --role="roles/bigquery.dataEditor" acme-analytics:geo_raw
```

## 내보내기 만들기

엔진 범위가 없는 API 키를 쓰세요. 내보내기는 계정의 모든 결과를 담으므로, 일부 엔진으로 제한된 키는
`403 KEY_SCOPE`를 받습니다.

```bash Amazon S3 theme={"theme":"css-variables"}
curl -X POST https://api.querying.ai/v1/exports \
  -H "Authorization: Bearer $QUERYING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "destination": {
      "type": "s3",
      "bucket": "acme-geo",
      "region": "us-east-1",
      "roleArn": "arn:aws:iam::123456789012:role/querying-export",
      "prefix": "querying"
    },
    "interval": "daily",
    "data": ["tasks", "monitor_results"]
  }'
```

```bash Google Cloud Storage theme={"theme":"css-variables"}
curl -X POST https://api.querying.ai/v1/exports \
  -H "Authorization: Bearer $QUERYING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "destination": { "type": "gcs", "bucket": "acme-geo", "prefix": "querying" },
    "interval": "hourly",
    "data": ["tasks"]
  }'
```

```bash BigQuery theme={"theme":"css-variables"}
curl -X POST https://api.querying.ai/v1/exports \
  -H "Authorization: Bearer $QUERYING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "destination": { "type": "bigquery", "projectId": "acme-analytics", "datasetId": "geo_raw" },
    "interval": "daily",
    "startAt": "2026-10-01T00:00:00Z"
  }'
```

| 필드 | 필수 | 설명 |
| - | - | - |
| `destination.type` | 예 | `s3`, `gcs` 또는 `bigquery`. |
| `destination.bucket` | S3, GCS | 버킷 이름. |
| `destination.prefix` | 아니요 | 버킷 안의 폴더. 최대 256자의 상대 경로입니다(맨 앞 `/`, `.`이나 `..` 세그먼트 불가). 끝에 `/`가 붙습니다. |
| `destination.region` | S3 | 버킷의 AWS 리전. 예: `us-east-1`. |
| `destination.roleArn` | S3 | [권한 부여](#amazon-s3)에서 만든 role, `arn:aws:iam::<account>:role/<name>`. |
| `destination.projectId`, `destination.datasetId` | BigQuery | Google Cloud 프로젝트와 데이터셋. |
| `interval` | 아니요 | `hourly` 또는 `daily`(기본값). 내보내기가 있는 동안 바꿀 수 없습니다. |
| `data` | 아니요 | `tasks`, `monitor_results` 또는 둘 다(기본값). |
| `startAt` | 아니요 | ISO 8601 시각. 미래일 수 없고 최대 30일 전까지입니다. 이 시각이 속한 구간이 첫 구간입니다. 기본값은 지금입니다. |

저장하기 전에 권한을 확인합니다. S3와 Cloud Storage는 `<prefix><accountKey>/_querying/connection-check.json`을
쓰고, BigQuery는 데이터셋을 읽어 위치를 기록합니다. 확인에 실패하면 아무것도 저장하지 않습니다. 계정당
내보내기는 10개까지 만들 수 있습니다.

응답은 `201`과 내보내기입니다.

```json theme={"theme":"css-variables"}
{
  "success": true,
  "export": {
    "id": "6a1d0f3c-...",
    "destination": {
      "type": "bigquery",
      "projectId": "acme-analytics",
      "datasetId": "geo_raw",
      "location": "US",
      "tables": [
        "acme-analytics.geo_raw.querying_tasks_v1_acct_3f9a1c2b7d4e",
        "acme-analytics.geo_raw.querying_monitor_results_v1_acct_3f9a1c2b7d4e"
      ]
    },
    "interval": "daily",
    "data": ["tasks", "monitor_results"],
    "enabled": true,
    "schemaVersion": 1,
    "nextWindowStart": "2026-10-01T00:00:00.000Z",
    "createdAt": "2026-10-19T09:00:00.000Z",
    "deliveredThrough": null,
    "failedRuns": 0,
    "retryingRuns": 0
  }
}
```

S3와 Cloud Storage는 `destination`에 버킷 설정을 그대로 돌려주고, 파일이 들어가는 폴더 `path`를
더합니다. 예: `s3://acme-geo/querying/acct_3f9a1c2b7d4e/`, `gs://acme-geo/querying/acct_3f9a1c2b7d4e/`.
`nextWindowStart`는 다음에 계획될 구간의 시작입니다.

### 오류

| 상태 | 코드 | 원인 |
| - | - | - |
| `400` | `VALIDATION_ERROR` | JSON이 올바르지 않거나 필드 값이 범위를 벗어났습니다. 메시지에 해당 필드가 나옵니다. 예: `destination.region: region is the bucket's AWS region, e.g. us-east-1`. |
| `403` | `KEY_SCOPE` | API 키가 일부 엔진으로 제한되어 있습니다. |
| `409` | `EXPORT_LIMIT` | 계정에 이미 내보내기가 10개 있습니다. |
| `422` | `EXPORT_DESTINATION_UNREACHABLE` | 연결 확인에 실패했습니다. 메시지에 저장소가 알려 준 이유가 들어 있으며, 대개 권한이 빠진 경우입니다. |
| `502` | `EXPORT_CHECK_FAILED` | 대상 저장소에 연결할 수 없습니다. 다시 시도하세요. |
| `503` | `EXPORTS_NOT_CONFIGURED` | 이 유형의 대상 저장소로는 내보낼 수 없습니다. |

## 파일과 테이블

### S3와 Cloud Storage의 파일

```text theme={"theme":"css-variables"}
s3://acme-geo/querying/acct_3f9a1c2b7d4e/tasks/v1/dt=2026-10-08/part-00000.jsonl.gz
s3://acme-geo/querying/acct_3f9a1c2b7d4e/tasks/v1/dt=2026-10-08/_manifest.json
s3://acme-geo/querying/acct_3f9a1c2b7d4e/monitor_results/v1/dt=2026-10-08/hour=13/part-00000.jsonl.gz
```

* 디렉터리는 Hive 파티션 규칙(`dt=`, 시간별 내보내기는 `hour=`도)을 따르므로 Athena, BigQuery 외부
  테이블, Spark가 열로 읽습니다.
* `v1`은 스키마 버전(내보내기의 `schemaVersion`)입니다. 열을 깨는 변경은 `v2`로 나란히 냅니다.
* 한 구간은 시간 순서대로 최대 100,000행씩 part 파일로 나뉩니다.
* `_manifest.json`은 마지막에 씁니다. manifest가 생긴 구간을 적재하고, part 목록은 manifest에서
  읽으세요.

```json theme={"theme":"css-variables"}
{
  "schemaVersion": 1,
  "data": "tasks",
  "windowStart": "2026-10-08T00:00:00.000Z",
  "windowEnd": "2026-10-09T00:00:00.000Z",
  "rows": 20000,
  "files": [{ "key": "querying/acct_3f9a1c2b7d4e/tasks/v1/dt=2026-10-08/part-00000.jsonl.gz", "rows": 20000, "bytes": 1990096 }],
  "deliveredAt": "2026-10-09T00:16:02.114Z"
}
```

manifest의 `rows`는 part 파일의 줄 수와 같고, 전송 이력에 나오는 run의 `rows`와도 같습니다. 파일
이름은 구간에만 달려 있으므로, 다시 보낸 구간은 같은 객체를 교체합니다.

### BigQuery 테이블

BigQuery 내보내기는 `querying_tasks_v1_<accountKey>`와 `querying_monitor_results_v1_<accountKey>`에
적재합니다. 테이블은 첫 적재 때 만들어지고 `completedAt` 또는 `ranAt`으로 파티션됩니다(일별, 시간별
내보내기는 시간별). 구간마다 자기 파티션을 교체하므로 다시 보낸 구간이 두 번 쌓이지 않습니다.
`payload`, `response`, `sources`, `scoringContext`는 `JSON` 열입니다.

```sql theme={"theme":"css-variables"}
SELECT taskType, JSON_VALUE(payload.prompt) AS prompt, JSON_VALUE(response.text) AS answer
FROM `acme-analytics.geo_raw.querying_tasks_v1_acct_3f9a1c2b7d4e`
WHERE DATE(completedAt) = "2026-10-08" AND status = "COMPLETED";
```

## 행 스키마 (v1)

### `tasks`

| 필드 | 타입 | BigQuery 타입 | 설명 |
| - | - | - | - |
| `id` | string | `STRING` | 태스크 ID. |
| `taskType` | string | `STRING` | 엔진. 예: `CHATGPT`. |
| `status` | string | `STRING` | `COMPLETED` 또는 `FAILED`. |
| `priority` | integer | `INT64` | 제출한 값. |
| `idempotencyKey` | string 또는 null | `STRING` | 보낸 경우 고객의 멱등 키. |
| `createdAt`, `completedAt` | timestamp | `TIMESTAMP` | UTC, 마이크로초 정밀도. |
| `monitorId` | string 또는 null | `STRING` | 태스크를 실행한 모니터. |
| `creditsCharged` | decimal | `NUMERIC` | 태스크에 청구된 크레딧. 소수점 둘째 자리까지 있습니다(예: `1.25`). 실패한 태스크는 `0`. 정수가 아니라 소수로 읽으세요. |
| `payload` | object | `JSON` | 제출한 payload. |
| `response` | object 또는 null | `JSON` | `GET /v1/async/task/{id}`가 돌려주는 결과. 실패하면 `null`. |
| `error` | string 또는 null | `STRING` | 코드로 시작하는 실패 이유. 완료되면 `null`. |

### `monitor_results`

| 필드 | 타입 | BigQuery 타입 | 설명 |
| - | - | - | - |
| `id`, `monitorId`, `taskId` | string | `STRING` | 결과, 모니터, 태스크 ID. |
| `engine`, `prompt` | string | `STRING` | 어디서 무엇을 물었는지. |
| `mentioned`, `cited` | boolean | `BOOL` | 브랜드 언급 여부, 고객 도메인 인용 여부. |
| `position` | integer 또는 null | `INT64` | 첫 브랜드 언급의 문자 위치. |
| `competitorHits` | string array | `STRING`, repeated | 답변에 나온 경쟁사. |
| `sources` | array | `JSON` | `{url, label, position}` 형태의 인용. |
| `answerText` | string 또는 null | `STRING` | 점수를 읽은 답변. |
| `scoringContext` | object 또는 null | `JSON` | 당시의 채점 정의. |
| `latitude`, `longitude` | number 또는 null | `FLOAT64` | 위치 모니터가 질문한 지점. |
| `ranAt` | timestamp | `TIMESTAMP` | UTC, 마이크로초 정밀도. |

## 전송 이력

데이터 세트별 구간 하나가 run 하나입니다. 최신순으로 나열합니다.

```bash theme={"theme":"css-variables"}
curl "https://api.querying.ai/v1/exports/$EXPORT_ID/runs?status=failed" \
  -H "Authorization: Bearer $QUERYING_API_KEY"
```

```json theme={"theme":"css-variables"}
{
  "success": true,
  "runs": [{
    "id": "3c9e1b7a-...",
    "data": "tasks",
    "windowStart": "2026-10-08T00:00:00.000Z",
    "windowEnd": "2026-10-09T00:00:00.000Z",
    "status": "failed",
    "attempts": 8,
    "nextAttemptAt": null,
    "rows": null,
    "bytes": null,
    "files": [],
    "error": "S3 PutObject s3://acme-geo/... failed: HTTP 403 AccessDenied: Access Denied",
    "deliveredAt": null
  }],
  "nextCursor": null
}
```

| `status` | 의미 |
| - | - |
| `pending` | 전송 대기 중이거나, 실패한 시도 뒤 재시도를 기다리는 중입니다. `nextAttemptAt`에 시각이 나옵니다. |
| `running` | 지금 전송 중입니다. |
| `delivered` | 기록됐습니다. `rows`, `bytes`, `files`, `deliveredAt`이 채워집니다. |
| `failed` | 8번 모두 실패했습니다. 재시도하기 전까지는 더 진행되지 않습니다. |

`status`, `data`(`tasks` 또는 `monitor_results`), `limit`(1–200, 기본 50)으로 거릅니다. 다음 페이지는
`nextCursor`를 `cursor`로 다시 보내 받습니다.

* **자동 재시도.** 실패한 시도는 15분 뒤에 다시 시도하고, 이후 간격을 두 배씩 늘려 최대 12시간까지,
  모두 8번 시도합니다. `error`에 저장소가 알려 준 이유가 들어가므로 권한이 빠지면 여기서 먼저 보입니다.
* **구간 다시 보내기.** `POST /v1/exports/{id}/runs/{runId}/retry`는 구간을 지금 보냅니다. 권한을 고친
  뒤 실패한 구간을 보내거나, 이미 전달된 구간을 다시 쓸 때 씁니다. `attempts`를 초기화하고 run을
  `pending`으로 돌려줍니다. 전송 중인 run은 `409 EXPORT_RUN_RUNNING`을 반환합니다.
* **내보내기 상태.** `GET /v1/exports`와 `GET /v1/exports/{id}`는 `deliveredThrough`(마지막으로 전달된
  구간의 끝), `failedRuns`, `retryingRuns`(한 번 이상 실패하고 대기 중인 run)를 더해 보여 줍니다.

## 일시 중지, 재개, 삭제

* `PATCH /v1/exports/{id}`에 `{"enabled": false}`를 보내면 내보내기를 일시 중지합니다.
  `{"enabled": true}`는 재개하며, 최근 30일 안에 빠진 구간을 이어서 보냅니다.
* `DELETE /v1/exports/{id}`는 내보내기를 중지합니다. 이미 전달된 파일과 파티션은 고객 저장소에 남습니다.
* 대상 저장소, 주기, 데이터 세트는 바꿀 수 없습니다. 새 내보내기를 만들고 이전 것을 삭제하세요.

## 엔드포인트

| 메서드와 경로 | 용도 |
| - | - |
| [`GET /v1/exports/identity`](/ko/api-reference/exports/get-identity) | 권한을 줄 ID와 고객 계정 키 |
| [`POST /v1/exports`](/ko/api-reference/exports/create-export) | 대상 저장소 연결 |
| [`GET /v1/exports`](/ko/api-reference/exports/list-exports) | 전송 상태와 함께 내보내기 목록 |
| [`GET /v1/exports/{id}`](/ko/api-reference/exports/get-export) | 내보내기 하나 |
| [`PATCH /v1/exports/{id}`](/ko/api-reference/exports/update-export) | 일시 중지 또는 재개 |
| [`DELETE /v1/exports/{id}`](/ko/api-reference/exports/delete-export) | 내보내기 중지 |
| [`GET /v1/exports/{id}/runs`](/ko/api-reference/exports/list-runs) | 전송 이력 |
| [`POST /v1/exports/{id}/runs/{runId}/retry`](/ko/api-reference/exports/retry-run) | 구간 다시 보내기 |

소유하지 않은 내보내기는 `404 NOT_FOUND`를 반환합니다.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.