# Push learning events

Idempotently append a batch of explicitly typed Agent-owned learning evidence events. Server-owned practice, mock, course, and browser event namespaces are rejected.

- Capability ID: `learning.events.push`
- Operation: `ielts_learning_push_events`
- Category: 学习事件
- Endpoint: `POST /api/v1/agent/capabilities/ielts_learning_push_events`
- Authentication: Bearer Token（通过 /api/v1/agent-bindings 绑定账号）
- Required scopes: learning:write
- Side effect: write
- Human documentation: https://ieltsbuddy.igopx.cn/developers/api/ielts_learning_push_events

## Request parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `events` | record[] | 是 | events |
| `events.deviceId` | string | 是 | deviceId |
| `events.eventId` | string | 是 | eventId |
| `events.eventType` | string | 是 | eventType |
| `events.objectId` | string | 否 | objectId |
| `events.objectType` | string | 否 | objectType |
| `events.occurredAt` | string | 是 | occurredAt |
| `events.payload` | record | 否 | payload |
| `events.schemaVersion` | string | 否 | schemaVersion |
| `events.skillCodes` | unknown[] | 否 | skillCodes |
| `events.subjectCode` | string | 否 | subjectCode |

## Request example

```bash
curl -X POST \
  'https://work.ieltsbuddy.igopx.cn/api/v1/agent/capabilities/ielts_learning_push_events' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer $IELTS_BUDDY_TOKEN' \
  --data '{"events":[]}'
```

## Request schema

```json
{
  "type": "object",
  "properties": {
    "events": {
      "minItems": 1,
      "maxItems": 200,
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "deviceId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "eventId": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "eventType": {
            "type": "string",
            "enum": [
              "agent.learning.evidence_recorded",
              "agent.practice.attempted"
            ]
          },
          "objectId": {
            "type": "string",
            "maxLength": 160
          },
          "objectType": {
            "type": "string",
            "maxLength": 80
          },
          "occurredAt": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})$"
          },
          "payload": {
            "default": {},
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {}
          },
          "schemaVersion": {
            "default": 1,
            "type": "number",
            "const": 1
          },
          "skillCodes": {
            "default": [],
            "maxItems": 20,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            }
          },
          "subjectCode": {
            "type": "string",
            "enum": [
              "listening",
              "reading",
              "writing",
              "speaking"
            ]
          }
        },
        "required": [
          "deviceId",
          "eventId",
          "eventType",
          "occurredAt",
          "payload",
          "schemaVersion",
          "skillCodes"
        ],
        "additionalProperties": false
      }
    }
  },
  "required": [
    "events"
  ],
  "additionalProperties": false
}
```

## Response envelope

```json
{
  "code": 0,
  "data": {},
  "message": "ok"
}
```

## Errors

| HTTP | Code | Meaning |
| --- | --- | --- |
| 400 | `40001` | 请求参数不符合接口契约 |
| 401 | `40101` | 缺少或使用了无效的访问令牌 |
| 403 | `40301` | 访问令牌不包含接口所需权限 |
| 404 | `40401` | 接口或目标资源不存在 |
