---
title: "Prove possession and activate a pending wrapper"
method: POST
path: "/agent/v1/source-access/wrappers/{wrapper_id}/activate"
tags: ["Org"]
---

# Prove possession and activate a pending wrapper

`POST /agent/v1/source-access/wrappers/{wrapper_id}/activate`

## Request

- Base URL: `https://api.run402.com`
- URL: `https://api.run402.com/agent/v1/source-access/wrappers/{wrapper_id}/activate`
- Auth: one of:
  - API key in header `SIGN-IN-WITH-X`
  - HTTP bearer

The activation half of wrapper custody: after creating a PENDING wrapper, the client reads the STORED blob back, unwraps those exact bytes locally, compares the FULL derived public key against the published one, and answers the ECDH key-confirmation challenge from the create response — `proof = base64url(HMAC-SHA256(X25519(member_scalar, challenge.epk), possession_message))`. Only a holder of the member private key can answer; the server stores only a hash of the expected proof and learns no key material. Activation is atomic: a pending KEY activates with its first wrapper, and a first wrapper on a legacy-custody key flips the scheme to `wrapped_legacy_scalar_v1` (the migration cutover). Optional `replace_wrapper_id` revokes the named old wrapper in the SAME transaction — the recovery-code replacement rule (old code stays valid until the new one is verified). Challenges are single-use and expire in 15 minutes; nothing pending ever counts toward reads, scoping, or recovery posture.

## Path parameters

- `wrapper_id` string, uuid, required

## Headers

- `SIGN-IN-WITH-X` string, required

## Request body

- object
  - `proof` string, required: base64url possession proof (see description).
  - `replace_wrapper_id` string, uuid: Optional: an ACTIVE wrapper of the same key to revoke atomically with this activation (recovery-code replacement).

## Response `200`

Activated

- object
  - `wrapper_id` string, uuid
  - `kind` 'webauthn_prf' | 'recovery_code'
  - `state` 'active'
  - `key_state` string
  - `custody_scheme` 'wrapped_legacy_scalar_v1' | 'wrapped_random_v1'
  - `activated_at` string, date-time

## Other responses

- `403`: FORBIDDEN | POSSESSION_PROOF_INVALID (a stolen bearer's garbage can never activate)
- `409`: WRAPPER_NOT_PENDING
- `410`: ACTIVATION_EXPIRED — reseal and re-create the wrapper

---

[API](https://skmtc.dev/run402/apis/run402-api.md) · [All operations](https://skmtc.dev/run402/apis/run402-api/llms.txt) · [OpenAPI document](https://skmtc.dev/run402/apis/run402-api/revisions/893607557f50?raw)
