diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 3c2eb095..8ee1b857 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -4781,6 +4781,219 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' + /auth/delegated-keys: + post: + summary: Create a delegated signing key + description: | + Delegate Spark token-transaction signing authority on an Embedded Wallet internal account to a Grid-custodied P-256 API key. Grid generates the keypair server-side, creates a non-root Turnkey user holding the public key, then a policy granting that user signing authority. The private key is custodied by Grid and never returned. Both activities must be authorized by the wallet owner, so creation is a three-leg signed-retry flow: + + 1. Call `POST /auth/delegated-keys` with no signature headers. Grid generates the delegated keypair and the response is `202` with a `payloadToSign` (`step: CREATE_USER`), `requestId`, and `expiresAt`. + + 2. Use the session API keypair of a verified credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry the same request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The response is a second `202` with a new `payloadToSign` (`step: CREATE_POLICY`) and `requestId`. + + 3. Stamp the new `payloadToSign` with the same session keypair and retry once more with the new `Request-Id`. The signed retry returns `201` with the created `DelegatedKey` in `ACTIVE` status. + + The same request body must be sent on all three legs. A flow abandoned after the second leg leaves the key in `PENDING` status: the delegated user exists but holds no policy, so it cannot sign. After activation, Grid uses the custodied key to authorize signing on the account's behalf (for example when the user requests a payment) in place of a session keypair; the platform never handles the key material. + + Each account may have at most one non-revoked delegated key (`ACTIVE` or `PENDING`); revoke the existing key before creating a new one. A delegated key authorizes raw-payload signing for the wallet and cannot be scoped to amounts or recipients. Revoke it with `DELETE /auth/delegated-keys/{id}` when no longer needed. + operationId: createDelegatedKey + tags: + - Embedded Wallet Auth + security: + - BasicAuth: [] + parameters: + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified credential on the same internal account. Required on the signed retries; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in the prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retries; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DelegatedKeyCreateRequest' + examples: + create: + summary: Delegate signing to a Grid-custodied key + value: + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + nickname: Recurring payments key + responses: + '201': + description: Delegated key created and policy granted. The key is `ACTIVE` and Grid may use it to stamp quote executions for this account. + content: + application/json: + schema: + $ref: '#/components/schemas/DelegatedKey' + examples: + delegatedKey: + summary: Active delegated key + value: + id: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + publicKey: 02a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90 + nickname: Settlement service key + status: ACTIVE + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:42Z' + '202': + description: 'Challenge issued for the next leg. `step` identifies which signer activity the stamp will authorize: `CREATE_USER` on the initial call, `CREATE_POLICY` after the user-creation leg completes.' + content: + application/json: + schema: + $ref: '#/components/schemas/DelegatedKeySignedRequestChallenge' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing on a retry, malformed, or does not match the pending challenge, or when the `Request-Id` does not match an unexpired pending challenge. + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: A non-revoked delegated key (`ACTIVE` or `PENDING`) already exists on this account. Revoke it with `DELETE /auth/delegated-keys/{id}` before creating a new one. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + get: + summary: List delegated signing keys + description: List all delegated signing keys on an Embedded Wallet internal account, including `PENDING` keys (user created but policy leg never completed) and `REVOKED` keys. + operationId: listDelegatedKeys + tags: + - Embedded Wallet Auth + security: + - BasicAuth: [] + parameters: + - name: accountId + in: query + required: true + description: The id of the internal account whose delegated keys to list. + schema: + type: string + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + responses: + '200': + description: Delegated keys on the account. Returns an empty `data` array when the account has no embedded wallet, has no delegated keys, or when `accountId` does not match any internal account visible to the caller. + content: + application/json: + schema: + $ref: '#/components/schemas/DelegatedKeyListResponse' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /auth/delegated-keys/{id}: + delete: + summary: Revoke a delegated signing key + description: | + Revoke a delegated signing key. Revocation is a single signed-retry flow: + + 1. Call `DELETE /auth/delegated-keys/{id}` with no headers. The response is `202` with a `payloadToSign` (`step: DELETE_USER`), `requestId`, and `expiresAt`. + + 2. Stamp `payloadToSign` with the session API keypair of a verified credential on the same internal account and retry with `Grid-Wallet-Signature` and `Request-Id` headers. This deletes the delegated user and its API key, after which the key can no longer sign, and the response is `204`. + + Deleting the user is the kill switch: it removes the API key the delegated key authenticated with, so signing stops regardless of the policy. The policy is left in place — its consensus references the now-deleted user, so it can never authorize anything (Turnkey user IDs are never reused), and deleting it is unnecessary for correctness or security. + operationId: revokeDelegatedKey + tags: + - Embedded Wallet Auth + security: + - BasicAuth: [] + parameters: + - name: id + in: path + description: The id of the delegated key to revoke (the `id` field of the `DelegatedKey` returned from `POST /auth/delegated-keys`). + required: true + schema: + type: string + example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified credential on the same internal account. Required on the signed retries; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in the prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retries; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + responses: + '202': + description: 'Challenge issued (`step: DELETE_USER`). Stamp `payloadToSign` and retry to complete revocation.' + content: + application/json: + schema: + $ref: '#/components/schemas/DelegatedKeySignedRequestChallenge' + '204': + description: Delegated key revoked. The key can no longer authorize signing. + '400': + description: Bad request. Returned when the delegated key has already been revoked. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing on a retry, malformed, or does not match the pending challenge, or when the `Request-Id` does not match an unexpired pending challenge. + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Delegated key not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' /agents: post: summary: Create an agent @@ -17888,6 +18101,106 @@ components: pattern: ^04[0-9a-fA-F]{128}$ description: Client-generated P-256 public key, hex-encoded in uncompressed SEC1 format (`04` prefix followed by the 32-byte X and 32-byte Y coordinates; 130 hex characters total). The matching private key must remain on the client. Grid binds this key into the session-creation payload on the initial call and seals the returned `encryptedSessionSigningKey` to it on the signed retry. example: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + DelegatedKeyStatus: + type: string + enum: + - PENDING + - ACTIVE + - REVOKED + description: |- + Status of a delegated signing key. + + - `PENDING`: The delegated user exists but the policy-creation leg never completed. The key cannot sign. + - `ACTIVE`: The policy is granted and the key may stamp quote executions. + - `REVOKED`: The delegated user has been deleted and the key can no longer sign. + example: ACTIVE + DelegatedKey: + title: Delegated Key + type: object + required: + - id + - accountId + - publicKey + - nickname + - status + - createdAt + - updatedAt + description: A delegated signing key on an Embedded Wallet internal account. Returned from `POST /auth/delegated-keys` (on activation) and `GET /auth/delegated-keys` (list). The keypair is generated and custodied by Grid; the private key is never returned. While `ACTIVE`, Grid may use the key to authorize Spark token-transaction signing for the account (e.g. when the user requests a payment) in place of a session keypair. `publicKey` is informational metadata identifying the credential. + properties: + id: + type: string + description: Grid-issued `DelegatedKey:` identifier. + example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + accountId: + type: string + description: The internal account this key is delegated for. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + publicKey: + type: string + description: Compressed P-256 public key (hex) of the delegated API keypair. + example: 02a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90 + nickname: + type: string + description: Human-readable label for the delegated key. + example: Settlement service key + status: + $ref: '#/components/schemas/DelegatedKeyStatus' + createdAt: + type: string + format: date-time + description: When the delegated key was created. + example: '2026-04-08T15:30:01Z' + updatedAt: + type: string + format: date-time + description: When the delegated key was last updated. + example: '2026-04-08T15:30:42Z' + DelegatedKeyListResponse: + title: Delegated Key List Response + type: object + required: + - data + properties: + data: + type: array + description: The delegated signing keys on the account. + items: + $ref: '#/components/schemas/DelegatedKey' + DelegatedKeyCreateRequest: + title: Delegated Key Create Request + type: object + required: + - accountId + - nickname + properties: + accountId: + type: string + description: The id of the Embedded Wallet internal account delegating signing authority. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + nickname: + type: string + minLength: 1 + maxLength: 256 + description: Human-readable label for the delegated key. + example: Recurring payments key + DelegatedKeyChallengeStep: + type: string + enum: + - CREATE_USER + - CREATE_POLICY + - DELETE_USER + description: 'Which signer activity the requested stamp will authorize: `CREATE_USER` and `CREATE_POLICY` during creation (`POST /auth/delegated-keys`), `DELETE_USER` during revocation (`DELETE /auth/delegated-keys/{id}`).' + DelegatedKeySignedRequestChallenge: + title: Delegated Key Signed Request Challenge + description: 202 response returned from the delegated-key endpoints. Carries the signing fields from `SignedRequestChallenge` plus the `step` identifying which signer activity the client is being asked to authorize. Stamp `payloadToSign` with the session API keypair of a verified credential on the same internal account, then retry the same request with the full stamp in `Grid-Wallet-Signature` and the `requestId` echoed in `Request-Id`. + allOf: + - $ref: '#/components/schemas/SignedRequestChallenge' + - type: object + required: + - step + properties: + step: + $ref: '#/components/schemas/DelegatedKeyChallengeStep' AgentPermission: type: string enum: diff --git a/openapi.yaml b/openapi.yaml index 3c2eb095..8ee1b857 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -4781,6 +4781,219 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' + /auth/delegated-keys: + post: + summary: Create a delegated signing key + description: | + Delegate Spark token-transaction signing authority on an Embedded Wallet internal account to a Grid-custodied P-256 API key. Grid generates the keypair server-side, creates a non-root Turnkey user holding the public key, then a policy granting that user signing authority. The private key is custodied by Grid and never returned. Both activities must be authorized by the wallet owner, so creation is a three-leg signed-retry flow: + + 1. Call `POST /auth/delegated-keys` with no signature headers. Grid generates the delegated keypair and the response is `202` with a `payloadToSign` (`step: CREATE_USER`), `requestId`, and `expiresAt`. + + 2. Use the session API keypair of a verified credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry the same request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The response is a second `202` with a new `payloadToSign` (`step: CREATE_POLICY`) and `requestId`. + + 3. Stamp the new `payloadToSign` with the same session keypair and retry once more with the new `Request-Id`. The signed retry returns `201` with the created `DelegatedKey` in `ACTIVE` status. + + The same request body must be sent on all three legs. A flow abandoned after the second leg leaves the key in `PENDING` status: the delegated user exists but holds no policy, so it cannot sign. After activation, Grid uses the custodied key to authorize signing on the account's behalf (for example when the user requests a payment) in place of a session keypair; the platform never handles the key material. + + Each account may have at most one non-revoked delegated key (`ACTIVE` or `PENDING`); revoke the existing key before creating a new one. A delegated key authorizes raw-payload signing for the wallet and cannot be scoped to amounts or recipients. Revoke it with `DELETE /auth/delegated-keys/{id}` when no longer needed. + operationId: createDelegatedKey + tags: + - Embedded Wallet Auth + security: + - BasicAuth: [] + parameters: + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified credential on the same internal account. Required on the signed retries; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in the prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retries; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DelegatedKeyCreateRequest' + examples: + create: + summary: Delegate signing to a Grid-custodied key + value: + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + nickname: Recurring payments key + responses: + '201': + description: Delegated key created and policy granted. The key is `ACTIVE` and Grid may use it to stamp quote executions for this account. + content: + application/json: + schema: + $ref: '#/components/schemas/DelegatedKey' + examples: + delegatedKey: + summary: Active delegated key + value: + id: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + publicKey: 02a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90 + nickname: Settlement service key + status: ACTIVE + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:42Z' + '202': + description: 'Challenge issued for the next leg. `step` identifies which signer activity the stamp will authorize: `CREATE_USER` on the initial call, `CREATE_POLICY` after the user-creation leg completes.' + content: + application/json: + schema: + $ref: '#/components/schemas/DelegatedKeySignedRequestChallenge' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing on a retry, malformed, or does not match the pending challenge, or when the `Request-Id` does not match an unexpired pending challenge. + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: A non-revoked delegated key (`ACTIVE` or `PENDING`) already exists on this account. Revoke it with `DELETE /auth/delegated-keys/{id}` before creating a new one. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + get: + summary: List delegated signing keys + description: List all delegated signing keys on an Embedded Wallet internal account, including `PENDING` keys (user created but policy leg never completed) and `REVOKED` keys. + operationId: listDelegatedKeys + tags: + - Embedded Wallet Auth + security: + - BasicAuth: [] + parameters: + - name: accountId + in: query + required: true + description: The id of the internal account whose delegated keys to list. + schema: + type: string + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + responses: + '200': + description: Delegated keys on the account. Returns an empty `data` array when the account has no embedded wallet, has no delegated keys, or when `accountId` does not match any internal account visible to the caller. + content: + application/json: + schema: + $ref: '#/components/schemas/DelegatedKeyListResponse' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /auth/delegated-keys/{id}: + delete: + summary: Revoke a delegated signing key + description: | + Revoke a delegated signing key. Revocation is a single signed-retry flow: + + 1. Call `DELETE /auth/delegated-keys/{id}` with no headers. The response is `202` with a `payloadToSign` (`step: DELETE_USER`), `requestId`, and `expiresAt`. + + 2. Stamp `payloadToSign` with the session API keypair of a verified credential on the same internal account and retry with `Grid-Wallet-Signature` and `Request-Id` headers. This deletes the delegated user and its API key, after which the key can no longer sign, and the response is `204`. + + Deleting the user is the kill switch: it removes the API key the delegated key authenticated with, so signing stops regardless of the policy. The policy is left in place — its consensus references the now-deleted user, so it can never authorize anything (Turnkey user IDs are never reused), and deleting it is unnecessary for correctness or security. + operationId: revokeDelegatedKey + tags: + - Embedded Wallet Auth + security: + - BasicAuth: [] + parameters: + - name: id + in: path + description: The id of the delegated key to revoke (the `id` field of the `DelegatedKey` returned from `POST /auth/delegated-keys`). + required: true + schema: + type: string + example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified credential on the same internal account. Required on the signed retries; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in the prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retries; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + responses: + '202': + description: 'Challenge issued (`step: DELETE_USER`). Stamp `payloadToSign` and retry to complete revocation.' + content: + application/json: + schema: + $ref: '#/components/schemas/DelegatedKeySignedRequestChallenge' + '204': + description: Delegated key revoked. The key can no longer authorize signing. + '400': + description: Bad request. Returned when the delegated key has already been revoked. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing on a retry, malformed, or does not match the pending challenge, or when the `Request-Id` does not match an unexpired pending challenge. + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Delegated key not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' /agents: post: summary: Create an agent @@ -17888,6 +18101,106 @@ components: pattern: ^04[0-9a-fA-F]{128}$ description: Client-generated P-256 public key, hex-encoded in uncompressed SEC1 format (`04` prefix followed by the 32-byte X and 32-byte Y coordinates; 130 hex characters total). The matching private key must remain on the client. Grid binds this key into the session-creation payload on the initial call and seals the returned `encryptedSessionSigningKey` to it on the signed retry. example: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + DelegatedKeyStatus: + type: string + enum: + - PENDING + - ACTIVE + - REVOKED + description: |- + Status of a delegated signing key. + + - `PENDING`: The delegated user exists but the policy-creation leg never completed. The key cannot sign. + - `ACTIVE`: The policy is granted and the key may stamp quote executions. + - `REVOKED`: The delegated user has been deleted and the key can no longer sign. + example: ACTIVE + DelegatedKey: + title: Delegated Key + type: object + required: + - id + - accountId + - publicKey + - nickname + - status + - createdAt + - updatedAt + description: A delegated signing key on an Embedded Wallet internal account. Returned from `POST /auth/delegated-keys` (on activation) and `GET /auth/delegated-keys` (list). The keypair is generated and custodied by Grid; the private key is never returned. While `ACTIVE`, Grid may use the key to authorize Spark token-transaction signing for the account (e.g. when the user requests a payment) in place of a session keypair. `publicKey` is informational metadata identifying the credential. + properties: + id: + type: string + description: Grid-issued `DelegatedKey:` identifier. + example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + accountId: + type: string + description: The internal account this key is delegated for. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + publicKey: + type: string + description: Compressed P-256 public key (hex) of the delegated API keypair. + example: 02a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90 + nickname: + type: string + description: Human-readable label for the delegated key. + example: Settlement service key + status: + $ref: '#/components/schemas/DelegatedKeyStatus' + createdAt: + type: string + format: date-time + description: When the delegated key was created. + example: '2026-04-08T15:30:01Z' + updatedAt: + type: string + format: date-time + description: When the delegated key was last updated. + example: '2026-04-08T15:30:42Z' + DelegatedKeyListResponse: + title: Delegated Key List Response + type: object + required: + - data + properties: + data: + type: array + description: The delegated signing keys on the account. + items: + $ref: '#/components/schemas/DelegatedKey' + DelegatedKeyCreateRequest: + title: Delegated Key Create Request + type: object + required: + - accountId + - nickname + properties: + accountId: + type: string + description: The id of the Embedded Wallet internal account delegating signing authority. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + nickname: + type: string + minLength: 1 + maxLength: 256 + description: Human-readable label for the delegated key. + example: Recurring payments key + DelegatedKeyChallengeStep: + type: string + enum: + - CREATE_USER + - CREATE_POLICY + - DELETE_USER + description: 'Which signer activity the requested stamp will authorize: `CREATE_USER` and `CREATE_POLICY` during creation (`POST /auth/delegated-keys`), `DELETE_USER` during revocation (`DELETE /auth/delegated-keys/{id}`).' + DelegatedKeySignedRequestChallenge: + title: Delegated Key Signed Request Challenge + description: 202 response returned from the delegated-key endpoints. Carries the signing fields from `SignedRequestChallenge` plus the `step` identifying which signer activity the client is being asked to authorize. Stamp `payloadToSign` with the session API keypair of a verified credential on the same internal account, then retry the same request with the full stamp in `Grid-Wallet-Signature` and the `requestId` echoed in `Request-Id`. + allOf: + - $ref: '#/components/schemas/SignedRequestChallenge' + - type: object + required: + - step + properties: + step: + $ref: '#/components/schemas/DelegatedKeyChallengeStep' AgentPermission: type: string enum: diff --git a/openapi/components/schemas/auth/DelegatedKeyChallengeStep.yaml b/openapi/components/schemas/auth/DelegatedKeyChallengeStep.yaml index 75203ebc..776d1c17 100644 --- a/openapi/components/schemas/auth/DelegatedKeyChallengeStep.yaml +++ b/openapi/components/schemas/auth/DelegatedKeyChallengeStep.yaml @@ -3,9 +3,7 @@ enum: - CREATE_USER - CREATE_POLICY - DELETE_USER - - DELETE_POLICY description: >- - Which signer activity the requested stamp will authorize: `CREATE_USER` / - `CREATE_POLICY` during creation (`POST /auth/delegated-keys`), - `DELETE_USER` / `DELETE_POLICY` during revocation - (`DELETE /auth/delegated-keys/{id}`). + Which signer activity the requested stamp will authorize: `CREATE_USER` and + `CREATE_POLICY` during creation (`POST /auth/delegated-keys`), `DELETE_USER` + during revocation (`DELETE /auth/delegated-keys/{id}`). diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 7c509543..3aed8755 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -225,6 +225,10 @@ paths: $ref: paths/auth/auth_sessions_{id}.yaml /auth/sessions/{id}/refresh: $ref: paths/auth/auth_sessions_{id}_refresh.yaml + /auth/delegated-keys: + $ref: paths/auth/auth_delegated-keys.yaml + /auth/delegated-keys/{id}: + $ref: paths/auth/auth_delegated-keys_{id}.yaml /agents: $ref: paths/agents/agents.yaml /agents/approvals: diff --git a/openapi/paths/auth/auth_delegated-keys.yaml b/openapi/paths/auth/auth_delegated-keys.yaml new file mode 100644 index 00000000..6064c28d --- /dev/null +++ b/openapi/paths/auth/auth_delegated-keys.yaml @@ -0,0 +1,197 @@ +post: + summary: Create a delegated signing key + description: > + Delegate Spark token-transaction signing authority on an Embedded Wallet + internal account to a Grid-custodied P-256 API key. Grid generates the + keypair server-side, creates a non-root Turnkey user holding the public + key, then a policy granting that user signing authority. The private key + is custodied by Grid and never returned. Both activities must be + authorized by the wallet owner, so creation is a three-leg signed-retry + flow: + + + 1. Call `POST /auth/delegated-keys` with no signature headers. Grid + generates the delegated keypair and the response is `202` with a + `payloadToSign` (`step: CREATE_USER`), `requestId`, and `expiresAt`. + + + 2. Use the session API keypair of a verified credential on the same + internal account to build an API-key stamp over `payloadToSign`, then + retry the same request with that full stamp as the + `Grid-Wallet-Signature` header and the `requestId` echoed back as the + `Request-Id` header. The response is a second `202` with a new + `payloadToSign` (`step: CREATE_POLICY`) and `requestId`. + + + 3. Stamp the new `payloadToSign` with the same session keypair and retry + once more with the new `Request-Id`. The signed retry returns `201` + with the created `DelegatedKey` in `ACTIVE` status. + + + The same request body must be sent on all three legs. A flow abandoned + after the second leg leaves the key in `PENDING` status: the delegated + user exists but holds no policy, so it cannot sign. After activation, + Grid uses the custodied key to authorize signing on the account's behalf + (for example when the user requests a payment) in place of a session + keypair; the platform never handles the key material. + + + Each account may have at most one non-revoked delegated key (`ACTIVE` + or `PENDING`); revoke the existing key before creating a new one. A + delegated key authorizes raw-payload signing for the wallet and cannot + be scoped to amounts or recipients. Revoke it with + `DELETE /auth/delegated-keys/{id}` when no longer needed. + operationId: createDelegatedKey + tags: + - Embedded Wallet Auth + security: + - BasicAuth: [] + parameters: + - name: Grid-Wallet-Signature + in: header + required: false + description: >- + Full API-key stamp built over the prior `payloadToSign` with the + session API keypair of a verified credential on the same internal + account. Required on the signed retries; ignored on the initial + call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: >- + The `requestId` returned in the prior `202` response, echoed back + exactly on the signed retry so the server can correlate it with the + issued challenge. Required on the signed retries; must be paired + with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + requestBody: + required: true + content: + application/json: + schema: + $ref: ../../components/schemas/auth/DelegatedKeyCreateRequest.yaml + examples: + create: + summary: Delegate signing to a Grid-custodied key + value: + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + nickname: Recurring payments key + responses: + '201': + description: >- + Delegated key created and policy granted. The key is `ACTIVE` and + Grid may use it to stamp quote executions for this account. + content: + application/json: + schema: + $ref: ../../components/schemas/auth/DelegatedKey.yaml + examples: + delegatedKey: + summary: Active delegated key + value: + id: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + publicKey: 02a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90 + nickname: Settlement service key + status: ACTIVE + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:42Z' + '202': + description: >- + Challenge issued for the next leg. `step` identifies which signer + activity the stamp will authorize: `CREATE_USER` on the initial + call, `CREATE_POLICY` after the user-creation leg completes. + content: + application/json: + schema: + $ref: ../../components/schemas/auth/DelegatedKeySignedRequestChallenge.yaml + '400': + description: Bad request + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error400.yaml + '401': + description: >- + Unauthorized. Returned when the provided `Grid-Wallet-Signature` is + missing on a retry, malformed, or does not match the pending + challenge, or when the `Request-Id` does not match an unexpired + pending challenge. + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error401.yaml + '404': + description: Account not found + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error404.yaml + '409': + description: >- + A non-revoked delegated key (`ACTIVE` or `PENDING`) already exists + on this account. Revoke it with `DELETE /auth/delegated-keys/{id}` + before creating a new one. + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error409.yaml + '500': + description: Internal service error + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error500.yaml +get: + summary: List delegated signing keys + description: >- + List all delegated signing keys on an Embedded Wallet internal account, + including `PENDING` keys (user created but policy leg never completed) + and `REVOKED` keys. + operationId: listDelegatedKeys + tags: + - Embedded Wallet Auth + security: + - BasicAuth: [] + parameters: + - name: accountId + in: query + required: true + description: The id of the internal account whose delegated keys to list. + schema: + type: string + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + responses: + '200': + description: >- + Delegated keys on the account. Returns an empty `data` array when + the account has no embedded wallet, has no delegated keys, or when + `accountId` does not match any internal account visible to the + caller. + content: + application/json: + schema: + $ref: ../../components/schemas/auth/DelegatedKeyListResponse.yaml + '400': + description: Bad request + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error400.yaml + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error401.yaml + '500': + description: Internal service error + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error500.yaml diff --git a/openapi/paths/auth/auth_delegated-keys_{id}.yaml b/openapi/paths/auth/auth_delegated-keys_{id}.yaml new file mode 100644 index 00000000..1250eac0 --- /dev/null +++ b/openapi/paths/auth/auth_delegated-keys_{id}.yaml @@ -0,0 +1,103 @@ +delete: + summary: Revoke a delegated signing key + description: > + Revoke a delegated signing key. Revocation is a single signed-retry flow: + + + 1. Call `DELETE /auth/delegated-keys/{id}` with no headers. The response + is `202` with a `payloadToSign` (`step: DELETE_USER`), `requestId`, and + `expiresAt`. + + + 2. Stamp `payloadToSign` with the session API keypair of a verified + credential on the same internal account and retry with + `Grid-Wallet-Signature` and `Request-Id` headers. This deletes the + delegated user and its API key, after which the key can no longer sign, + and the response is `204`. + + + Deleting the user is the kill switch: it removes the API key the + delegated key authenticated with, so signing stops regardless of the + policy. The policy is left in place — its consensus references the + now-deleted user, so it can never authorize anything (Turnkey user IDs + are never reused), and deleting it is unnecessary for correctness or + security. + operationId: revokeDelegatedKey + tags: + - Embedded Wallet Auth + security: + - BasicAuth: [] + parameters: + - name: id + in: path + description: >- + The id of the delegated key to revoke (the `id` field of the + `DelegatedKey` returned from `POST /auth/delegated-keys`). + required: true + schema: + type: string + example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + - name: Grid-Wallet-Signature + in: header + required: false + description: >- + Full API-key stamp built over the prior `payloadToSign` with the + session API keypair of a verified credential on the same internal + account. Required on the signed retries; ignored on the initial + call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: >- + The `requestId` returned in the prior `202` response, echoed back + exactly on the signed retry so the server can correlate it with the + issued challenge. Required on the signed retries; must be paired + with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + responses: + '202': + description: >- + Challenge issued (`step: DELETE_USER`). Stamp `payloadToSign` and + retry to complete revocation. + content: + application/json: + schema: + $ref: ../../components/schemas/auth/DelegatedKeySignedRequestChallenge.yaml + '204': + description: >- + Delegated key revoked. The key can no longer authorize signing. + '400': + description: >- + Bad request. Returned when the delegated key has already been + revoked. + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error400.yaml + '401': + description: >- + Unauthorized. Returned when the provided `Grid-Wallet-Signature` is + missing on a retry, malformed, or does not match the pending + challenge, or when the `Request-Id` does not match an unexpired + pending challenge. + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error401.yaml + '404': + description: Delegated key not found + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error404.yaml + '500': + description: Internal service error + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error500.yaml