{
  "openapi": "3.1.0",
  "info": {
    "title": "Self-Service API",
    "description": "Decide on end-user-initiated credential requests.\n\nThis API is for tenants whose self-service configs are in `webhook` verification mode. When an end-user submits a request through a share-link, DeepCredentials delivers a `self_service.request.submitted` webhook to your endpoint; you decide approve / reject and POST your decision back here. The `GET` endpoint lets you re-fetch the full request payload out-of-band — useful when a webhook delivery failed or you want to inspect history.",
    "version": "1.0.0",
    "x-metadata": {
      "title": "Self-Service API",
      "description": "Decide on end-user-initiated credential requests"
    }
  },
  "servers": [
    {
      "url": "https://api.dev.deepcredentials.swiss",
      "description": "Development"
    },
    {
      "url": "https://api.int.deepcredentials.swiss",
      "description": "Integration"
    }
  ],
  "tags": [
    {
      "name": "Self-Service",
      "description": "Approve or reject end-user-initiated credential requests."
    }
  ],
  "paths": {
    "/b2b/v1/self-service/requests/{requestId}": {
      "get": {
        "description": "Returns the full snapshot of a request: status, credential definition, email, captured claims, extra fields, identification result (when applicable), offer id, and timestamps. The primary path for tenants to fetch details after receiving a self-service webhook (which carries only \\`request\\_id\\`).\n\nAuthentication: \\`self\\_service\\` scope.\n",
        "operationId": "GET:b2b.GetSelfServiceRequest",
        "parameters": [
          {
            "$ref": "#/components/parameters/XOrgIdHeader"
          },
          {
            "$ref": "#/components/parameters/XEnvironmentHeader"
          },
          {
            "allowEmptyValue": true,
            "explode": false,
            "in": "path",
            "name": "requestId",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            },
            "style": "simple"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "admin_filled_fields": {
                      "items": {
                        "type": "string"
                      },
                      "description": "Claim keys the admin is expected to fill before approving.",
                      "type": "array"
                    },
                    "admin_filled_required": {
                      "items": {
                        "type": "string"
                      },
                      "description": "Subset of admin_filled_fields that are mandatory for approval.",
                      "type": "array"
                    },
                    "admin_filled_types": {
                      "additionalProperties": {
                        "type": "string"
                      },
                      "description": "Value type per admin-filled claim key (e.g. \"string\", \"boolean\").",
                      "type": "object"
                    },
                    "claims": {
                      "description": "Claim values captured up to the moment of the fetch.",
                      "type": "object"
                    },
                    "completed_at": {
                      "format": "date-time",
                      "description": "When the request left the pending state (RFC3339), if applicable.",
                      "type": "string"
                    },
                    "config_id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "UUID v4 identifier.",
                      "example": "550e8400-e29b-41d4-a716-446655440000"
                    },
                    "credential_definition": {
                      "$ref": "#/components/schemas/b2b.credentialDefinitionRefSlim"
                    },
                    "editable_field_sources": {
                      "additionalProperties": {
                        "type": "string"
                      },
                      "description": "Assignment source per editable key: \"user_filled\", \"admin_filled\", or\n\"identification_filled\".",
                      "type": "object"
                    },
                    "editable_field_types": {
                      "additionalProperties": {
                        "type": "string"
                      },
                      "description": "Value type per editable claim key.",
                      "type": "object"
                    },
                    "editable_fields": {
                      "items": {
                        "type": "string"
                      },
                      "description": "All claim keys the admin may supply or override on approval.",
                      "type": "array"
                    },
                    "email": {
                      "description": "Email address supplied by the applicant, if any.",
                      "type": "string"
                    },
                    "environment": {
                      "description": "Swiyu environment: \"beta\" or \"production\".",
                      "type": "string"
                    },
                    "extra_fields": {
                      "description": "Additional free-form fields submitted by the applicant.",
                      "type": "object"
                    },
                    "identification": {
                      "$ref": "#/components/schemas/selfservice.IdentificationDetail"
                    },
                    "offer_id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "UUID v4 identifier.",
                      "example": "550e8400-e29b-41d4-a716-446655440000"
                    },
                    "rejection_reason": {
                      "description": "Reason provided when the request was rejected, if any.",
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "UUID v4 identifier.",
                      "example": "550e8400-e29b-41d4-a716-446655440000"
                    },
                    "status": {
                      "description": "Current lifecycle status of the request.",
                      "type": "string",
                      "enum": [
                        "awaiting_identification",
                        "awaiting_verification",
                        "invitation_sent",
                        "issued",
                        "credential_issued",
                        "rejected",
                        "expired",
                        "failed"
                      ]
                    },
                    "submitted_at": {
                      "format": "date-time",
                      "description": "When the applicant submitted the request (RFC3339).",
                      "type": "string"
                    }
                  },
                  "required": [
                    "request_id",
                    "config_id",
                    "environment",
                    "status",
                    "credential_definition",
                    "claims",
                    "extra_fields",
                    "admin_filled_fields",
                    "admin_filled_types",
                    "admin_filled_required",
                    "editable_fields",
                    "editable_field_types",
                    "editable_field_sources",
                    "submitted_at"
                  ],
                  "type": "object"
                },
                "example": {
                  "request_id": "9c9a1f7b-2a3e-4f4a-9d22-1a2b3c4d5e6f",
                  "config_id": "c0nfig1d-aaaa-bbbb-cccc-1111deadbeef",
                  "environment": "beta",
                  "status": "awaiting_verification",
                  "email": "anna.example@partner.test",
                  "credential_definition": {
                    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                    "vct": "urn:ietf:params:oauth:vct:PersonId",
                    "name": "Person ID"
                  },
                  "claims": {
                    "given_name": "Anna",
                    "family_name": "Example",
                    "date_of_birth": "1990-04-12"
                  },
                  "extra_fields": {
                    "purpose": "Account opening"
                  },
                  "admin_filled_fields": [
                    "is_employee"
                  ],
                  "admin_filled_types": {
                    "is_employee": "boolean"
                  },
                  "admin_filled_required": [
                    "is_employee"
                  ],
                  "editable_fields": [
                    "given_name",
                    "family_name",
                    "date_of_birth",
                    "is_employee"
                  ],
                  "editable_field_types": {
                    "given_name": "string",
                    "family_name": "string",
                    "date_of_birth": "string",
                    "is_employee": "boolean"
                  },
                  "editable_field_sources": {
                    "given_name": "user_filled",
                    "family_name": "user_filled",
                    "date_of_birth": "user_filled",
                    "is_employee": "admin_filled"
                  },
                  "identification": {
                    "method": "eid_internal",
                    "verified_attributes": {
                      "given_name": "Anna",
                      "family_name": "Example",
                      "date_of_birth": "1990-04-12"
                    }
                  },
                  "submitted_at": "2026-05-20T14:30:00Z"
                }
              }
            },
            "description": "Success response"
          },
          "400": {
            "description": "Invalid request. Common causes: missing or malformed X-Org-Id / X-Environment headers, or a missing required field in the request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "invalid_argument",
                  "message": "X-Org-Id header is required."
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated. The Bearer token failed validation (signature, issuer, audience, or expiry).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "unauthenticated",
                  "message": "Missing or invalid Bearer token."
                }
              }
            }
          },
          "403": {
            "description": "Permission denied. The token does not carry the scope required by this endpoint. Check the `scope` claim on the token you received from the DeepCloud SSO token endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "permission_denied",
                  "message": "Token lacks the required scope."
                }
              }
            }
          },
          "404": {
            "description": "Resource not found. Either the resource ID is unknown, or the X-Org-Id you supplied is not an organization the authenticated service user is a member of.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "not_found",
                  "message": "Resource not found."
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "resource_exhausted",
                  "message": "Rate limit exceeded. Retry with backoff."
                }
              }
            }
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "internal",
                  "message": "An unexpected error occurred."
                }
              }
            }
          }
        },
        "summary": "Get a self-service request.\n",
        "tags": [
          "Self-Service"
        ]
      }
    },
    "/b2b/v1/self-service/requests/{requestId}/decision": {
      "post": {
        "description": "The webhook-mode counterpart of the portal's approve / reject buttons: the tenant's backend posts the decision here after acting on a \\`self\\_service.request.submitted\\` event. Validation, merge, offer or invitation creation, status transition and pub-sub all live in the selfservice package — this handler is a thin auth + scope check (\\`self\\_service\\`).\n",
        "operationId": "POST:b2b.DecideSelfServiceRequest",
        "parameters": [
          {
            "$ref": "#/components/parameters/XOrgIdHeader"
          },
          {
            "$ref": "#/components/parameters/XEnvironmentHeader"
          },
          {
            "allowEmptyValue": true,
            "explode": false,
            "in": "path",
            "name": "requestId",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            },
            "style": "simple"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "admin_claims": {
                    "description": "Optional in the contract because configs with no admin-filled claims approve without one. The server-side coverage check (selfservice package) is what enforces presence when the config does have admin-filled keys — a missing key returns InvalidArgument. Wrapped in Option so the generated OpenAPI spec marks it optional; a plain json.RawMessage gets emitted as required by Encore regardless of json:omitempty.",
                    "type": "object"
                  },
                  "decision": {
                    "description": "\"approve\" or \"reject\".",
                    "type": "string"
                  },
                  "reason": {
                    "description": "Required on reject. Stored as the request's rejection_reason and shown to the end-user in the rejection email.",
                    "type": "string"
                  }
                },
                "required": [
                  "decision"
                ],
                "type": "object"
              },
              "example": {
                "decision": "approve",
                "admin_claims": {
                  "is_employee": true
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "offer_id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "UUID v4 identifier.",
                      "example": "550e8400-e29b-41d4-a716-446655440000"
                    },
                    "request_id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "UUID v4 identifier.",
                      "example": "550e8400-e29b-41d4-a716-446655440000"
                    },
                    "status": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "request_id",
                    "status",
                    "offer_id"
                  ],
                  "type": "object"
                },
                "example": {
                  "request_id": "9c9a1f7b-2a3e-4f4a-9d22-1a2b3c4d5e6f",
                  "status": "approved",
                  "offer_id": "d4e5f6a7-b8c9-0123-4567-89abcdef0123"
                }
              }
            },
            "description": "Success response"
          },
          "400": {
            "description": "Invalid request. Common causes: missing or malformed X-Org-Id / X-Environment headers, or a missing required field in the request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "invalid_argument",
                  "message": "X-Org-Id header is required."
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated. The Bearer token failed validation (signature, issuer, audience, or expiry).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "unauthenticated",
                  "message": "Missing or invalid Bearer token."
                }
              }
            }
          },
          "403": {
            "description": "Permission denied. The token does not carry the scope required by this endpoint. Check the `scope` claim on the token you received from the DeepCloud SSO token endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "permission_denied",
                  "message": "Token lacks the required scope."
                }
              }
            }
          },
          "404": {
            "description": "Resource not found. Either the resource ID is unknown, or the X-Org-Id you supplied is not an organization the authenticated service user is a member of.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "not_found",
                  "message": "Resource not found."
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "resource_exhausted",
                  "message": "Rate limit exceeded. Retry with backoff."
                }
              }
            }
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "internal",
                  "message": "An unexpected error occurred."
                }
              }
            }
          }
        },
        "summary": "Approve or reject a self-service request.\n",
        "tags": [
          "Self-Service"
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "DeepAdmin-issued Keycloak access token obtained via the OAuth 2.0\nResource Owner Password Credentials grant against the DeepCloud SSO\nrealm. See the Service Users guide for the exchange flow."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "Machine-readable error code."
          },
          "message": {
            "type": "string",
            "description": "Human-readable error description."
          },
          "details": {
            "type": "object",
            "description": "Optional structured data with additional context."
          }
        }
      },
      "selfservice.IdentificationDetail": {
        "properties": {
          "method": {
            "type": "string"
          },
          "verified_attributes": {
            "type": "object"
          }
        },
        "required": [
          "method",
          "verified_attributes"
        ],
        "description": "IdentificationDetail is non-nil only when the request went through an identification step. We expose the method key and the verified\\_attributes blob the provider returned (already decrypted at this point). Consumers can ignore it when present and identification was disabled at config time.",
        "type": "object"
      },
      "b2b.credentialDefinitionRefSlim": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "description": "Credential definition UUID. UUID v4 identifier."
          },
          "name": {
            "description": "Human-readable credential definition name.",
            "type": "string"
          },
          "vct": {
            "description": "Verifiable credential type (vct).",
            "type": "string"
          }
        },
        "required": [
          "id",
          "vct",
          "name"
        ],
        "description": "A compact reference to the credential definition (id, vct, and name only).",
        "type": "object"
      }
    },
    "parameters": {
      "XOrgIdHeader": {
        "name": "X-Org-Id",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "Organization the call is acting on. The authenticated service user must be a member of this organization in DeepAdmin; otherwise the request returns 404."
      },
      "XEnvironmentHeader": {
        "name": "X-Environment",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string",
          "enum": [
            "beta",
            "production"
          ]
        },
        "description": "Environment the call is scoped to. Resources are isolated per environment."
      }
    }
  },
  "security": [
    {
      "BearerAuth": []
    }
  ]
}