Update Persona Link
Delegates to the shared common/links helper, whose body is links.UpdateLinkInput. THE 200 ABOVE IS UNREACHABLE TODAY. Before the link row is read, UpdateLink calls validateRecordExistence, which hands checkRecExistence the RECORD-TYPE id — not rec_idx or rec_idy — and that counts rows with that id in the table <type>.<type>. A record-type id is never a row id in the entity table, so the count is zero and every well-formed body ends as 404 common.record_not_found without the link being touched. What the body must still satisfy to get that far, and what will apply once the handler is fixed: each side needs exactly one record-type selector — neither and both are the same 400 common.invalid_input, so a body of only {"active": false} is refused earlier still — and the write is a full replace, not a patch, since updateLinkFields assigns rec_idx, rec_idy, metadata and active unconditionally before Save.
Authorization
bearerAuth In: header
Path Parameters
Link id.
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Body of PUT /v1/persona-links/{id}: common/links.UpdateLinkInput, NOT models.UpdatePersonaLinkInput.
NO BODY REACHES THE UPDATE TODAY: the handler checks record existence with the record-TYPE id against . before it reads the link, so a well-formed body answers 404 common.record_not_found. Everything below is what the body must satisfy to reach that point, and what governs the write once the handler is fixed.
THIS IS A FULL REPLACE, NOT A PATCH. updateLinkFields assigns rec_idx, rec_idy, metadata and active unconditionally before Save, so a key you omit is written as its zero value — omitting rec_idx and rec_idy overwrites both not-null columns with the nil UUID, and omitting metadata clears it. Send the whole link.
Each side needs EXACTLY ONE record-type selector: rec_type_x or rec_type_x_id, and rec_type_y or rec_type_y_id. Neither and both are the same 400 common.invalid_input.
A record-type NAME must be one that is registered; for personas that is the plural "personas" (constants.PersonaRecName). An unregistered name is not a 400 — rbac.GetRecTypeID finds no row and the handler answers 500 common.server_error.
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/persona-links/497f6eca-6276-4993-bfeb-53cbbbba6f08" \ -H "Content-Type: application/json" \ -d '{ "rec_idx": "550e8400-e29b-41d4-a716-446655440000", "rec_type_x": "personas", "rec_idy": "123e4567-e89b-12d3-a456-426614174000", "rec_type_y": "organizations", "metadata": { "role": "advisor", "start_date": "2024-04-01" }, "active": false }'{
"rec_idx": "fa292dda-ae2a-45ee-b97b-e8ab8329b907",
"rec_type_x": "5037db46-337a-4e82-97d6-129819a818a7",
"rec_idy": "f9543843-55a0-4858-ae41-eb146ddf8536",
"rec_type_y": "4f9a126c-2c2a-4b25-91ed-5614db497338",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"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",
"active": true,
"metadata": {}
}{
"status": 400,
"message": "Invalid input",
"code": "common.invalid_input",
"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"
}Retrieve a specific persona's details
Partial update. TWO THINGS THIS ROUTE CANNOT DO. (1) IT CANNOT DEACTIVATE A PERSONA, AND IT CANNOT CLEAR A FIELD. The update is issued as Updates(struct), and GORM skips every zero-valued member of a struct — so active: false is indistinguishable from active omitted, and "first_name": "" leaves the stored name alone. Only non-zero values are written. date_of_birth and date_of_death are pointers and are copied only when present, which is the same rule stated differently. (2) IT DOES NOT VALIDATE. validateCreatePersonaInput runs on POST only, so this route will happily write a name over the length limit, a future date of birth, or an age under 18 — all of which the create refuses. The 200 body is the struct read before the update with the written columns assigned back into it: GORM points its update at the model it was handed and assigns each column it sets, so the fields the request changed do come back with their new values. It is NOT a fresh read, so anything the database produced on its own — a trigger, a default, a computed column — is not reflected.