CorebanqCorebanq Developer Docs
Personasv1User Persona

Update user's persona

Updates the persona linked to a user. Carries every caveat of PUT /v1/personas/{persona_id} — no validation, and Updates(struct) skips zero values so active: false and empty strings do nothing — plus two of its own. THE LINK IS RESOLVED FROM THE CALLER'S OWN RECORD GRANTS, NOT FROM THE PATH USER'S LINKS. The handler calls links.FetchLinksByType with the AUTHENTICATED CALLER's id and canReadAll hardcoded to false, so it sees only links touching a user record the caller — or one of the caller's roles — holds a row for in rbac.record_permissions. rbac.RecPermission, which guards the route, returns true for an Administrator without consulting that table at all, so an Administrator passes the permission check and then finds no links: the caller must hold an explicit record grant on the user in the path for this route to resolve anything. IT ONLY WORKS WHEN EXACTLY ONE LINK TOUCHES THAT USER. The ids collected from those links are bound to "id = ?", which GORM expands as a parenthesised list: one id gives id = ($1) and updates that row; none gives id = (NULL), which matches nothing; two or more gives id = ($1,$2), which PostgreSQL rejects as a comparison against a row constructor. Every failure lands in the same branch and answers 404 having written nothing. The collected ids are not filtered by record type either — the customer-to-user and signatory-to-user links other modules create count toward the same total, and a single one of them can be handed to First() in place of the persona, which then finds no persona row and also answers 404.

PUT
/v1/personas/user/{user_id}

Authorization

bearerAuth
AuthorizationBearer <token>

In: header

Path Parameters

user_id*string

User id. The persona is found through the user↔persona link, not by a column on the persona itself.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Body of both PUT routes. Sent as Updates(struct), so GORM SKIPS EVERY ZERO VALUE: active: false cannot deactivate, and an empty string cannot clear a name or a nationality. The address members are dropped here too, and their house-number keys are actual_house and reg_house, not actual_house_number and reg_house_number. No validator runs on either PUT.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X PUT "https://example.com/v1/personas/user/497f6eca-6276-4993-bfeb-53cbbbba6f08" \  -H "Content-Type: application/json" \  -d '{    "first_name": "string",    "last_name": "string",    "date_of_birth": "2019-08-24"  }'
{
  "first_name": "string",
  "last_name": "string",
  "date_of_birth": "2019-08-24",
  "date_of_death": "2019-08-24",
  "nationality": "string",
  "hash_id": "string",
  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  "active": true,
  "metadata": {},
  "created_at": "2019-08-24T14:15:22Z",
  "created_by": "ee824cad-d7a6-4f48-87dc-e8461a9201c4",
  "modified_at": "2019-08-24T14:15:22Z",
  "modified_by": "e8d4374d-93a1-4e98-a6c6-fdcf00c5059f"
}
{
  "status": 400,
  "message": "Failed to serialize",
  "code": "personas_m.failed_to_serialize",
  "class": "validation"
}
{
  "status": 401,
  "message": "Unauthorized",
  "code": "common.unauthorized",
  "class": "business"
}
{
  "status": 403,
  "message": "No access to the record",
  "code": "common.rbac_no_rec_access",
  "class": "business"
}
{
  "status": 404,
  "message": "Record not found",
  "code": "common.record_not_found",
  "class": "business"
}
{
  "status": 429,
  "message": "Rate limit exceeded",
  "code": "rate_limits_m.exceeded",
  "class": "temporary",
  "retryable": true
}
{
  "status": 500,
  "message": "Internal server error",
  "code": "common.server_error",
  "class": "business"
}

{
  "overall_status": "unhealthy",
  "message": "Service is shutting down",
  "timestamp": "2026-08-27T15:04:05Z"
}

GETGet user's persona

Resolves the persona linked to the user through common/links, then reads it. The link must be stored with the persona on the x side and the user on the y side, which is the orientation POST /v1/persona-links writes. A user with no such link answers 404. A LINK THAT POINTS AT A ROW THAT IS GONE ANSWERS 500, NOT 404: the read is a bare First() whose error is wrapped as common.database_error without separating gorm.ErrRecordNotFound. Because DELETE /v1/personas/{persona_id} is a hard delete that leaves the links behind, this is a state a client will actually meet. The permission checked is read on the USER record, not on the persona — so a caller who may read the user gets the persona whether or not they hold a grant on the persona itself.

GETList personas (v2)

The shared list envelope — data, total, total_unfiltered, keys — with real filtering, sorting and pagination, which /v1/personas has none of. Filterable fields are id, created_at, created_by, modified_at, modified_by, active, metadata, first_name, last_name, date_of_birth, date_of_death, nationality and hash_id. Any other search.<field> is dropped by the shared parser rather than rejected, so the response comes back UNFILTERED instead of empty. search.linked_to.record_id and search.linked_to.record_type are the exception: they are not columns and would be dropped like any unknown key, so the handler lifts them out of the raw query string and puts them back into the search map by hand before the service runs. The .eq suffix form is accepted for both.