Related companies
Read and write the ownership relations between one company and others, including each relation's company roles.
A relation links two companies: a parent and a subsidiary. It can carry an ownership percentage, a voting percentage, notes, and a list of company roles and reasons.
These endpoints manage the relations of one company, given by companyId in the path:
| Endpoint | What it does |
|---|---|
GET /companies/{companyId}/related-companies | Lists the company's relations, in both directions |
POST /companies/{companyId}/related-companies | Creates relations |
PUT /companies/{companyId}/related-companies | Replaces all of the company's relations, in both directions |
GET /companies/{companyId}/related-companies/{relationId} | Returns one relation |
PATCH /companies/{companyId}/related-companies/{relationId} | Changes some fields of one relation |
PUT /companies/{companyId}/related-companies/{relationId} | Replaces the fields of one relation |
DELETE /companies/{companyId}/related-companies/{relationId} | Deletes one relation |
companyId is the company's Avallone ID. relationId is the relation's own ID, returned as id. It is not the ID of either company.
Before you start
Reading relations needs the VIEW_COMPANY permission. Writing them needs UPDATE_COMPANY. See Permissions.
Company roles are set up in Avallone, under Settings → Company Roles. A relation can only use roles that already exist in your tenant.
The relation object
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"type": "parent",
"companyReferenceId": "acme-holding",
"companyId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"ownership": 51,
"voting": 51,
"notes": "Majority shareholder",
"details": [
{ "role": "General Partner", "reason": "Manages the fund" },
{ "reason": "Common management" }
],
"reasons": ["Common management"],
"url": "/api/v1/companies/7c9e6679-7425-40de-944b-e07fc1f90ae7/related-companies/3fa85f64-5717-4562-b3fc-2c963f66afa6"
}| Field | Description |
|---|---|
id | The relation's ID |
type | Where the other company sits, seen from companyId. parent means it is above, as an owner. subsidiary means it is below. |
companyId | The other company's Avallone ID |
companyReferenceId | The other company's reference ID, when it has one |
ownership | Ownership percentage, from 0 to 100 |
voting | Voting percentage, from 0 to 100 |
notes | Free text |
details | The relation's company roles and reasons. See Roles and reasons. |
reasons | Deprecated. The reasons that have no role. details lists them too. Removed on 2027-06-30. |
url | The path of this relation |
A field without a value is left out of the response.
Roles and reasons
details is a list. Each entry is a company role, a reason, or a role with a reason:
"details": [
{ "role": "General Partner", "reason": "Manages the fund" },
{ "role": "Guarantor" },
{ "reason": "Common management" }
]These rules apply when you send details:
roleis the name of a company role in your tenant. Case, surrounding spaces, and the difference between a space and a dash are ignored, sogeneral-partnermatchesGeneral Partner.- A role that doesn't exist returns
404, and nothing is written. - A relation can have each role only once. Listing a role twice returns
400. - Each entry needs a
role, areason, or both.
How each method uses details:
| Method | With details | Without details |
|---|---|---|
POST | The relation gets these roles and reasons. | The relation has no roles. |
PUT | Replaces every role and reason on the relation. | The roles are kept. reasons replaces the reasons that have no role. |
PATCH | Replaces every role and reason on the relation. Send null or [] to remove them all. | The roles are kept. reasons, when sent, replaces the reasons that have no role. |
Deprecated reasons
reasons is a list of strings, and still works. It is deprecated and will be removed on 2027-06-30. Use details instead.
When a request sends both, reasons must list the same reasons as the entries in details that have no role. Otherwise the request returns 400, and in sync the relation fails. Reasons are compared without regard to case and surrounding spaces. A response lists those reasons in both fields, so you can send a relation back unchanged.
List relations
GET /api/v1/companies/{companyId}/related-companiesReturns the company's relations in both directions: its parents and its subsidiaries. Use type to tell them apart.
Permission: VIEW_COMPANY
Query parameters:
| Param | Description |
|---|---|
type | Optional. parent or subsidiary returns only that direction. Any other value returns 400. |
Response: 200 with a list of relation objects.
Create relations
POST /api/v1/companies/{companyId}/related-companiesCreates a relation for each item in the body. If one item fails, none are created.
Permission: UPDATE_COMPANY
Request body: a list of items with these fields:
| Field | Required | Description |
|---|---|---|
companyId | One of the two IDs | The other company's Avallone ID |
companyReferenceId | One of the two IDs | The other company's reference ID |
type | Yes | parent or subsidiary: where the other company sits |
ownership | No | Ownership percentage, from 0 to 100 |
voting | No | Voting percentage, from 0 to 100 |
notes | No | Free text |
details | No | Company roles and reasons. See Roles and reasons. |
reasons | No | Deprecated. Use details. |
When you send both companyId and companyReferenceId, they must name the same company.
POST /api/v1/companies/{companyId}/related-companies
Content-Type: application/json
[
{
"companyReferenceId": "acme-holding",
"type": "parent",
"ownership": 51,
"voting": 51,
"details": [{ "role": "General Partner", "reason": "Manages the fund" }]
}
]Response: 201 with the relations that were created.
A relation that already exists returns 409. So does one that would make a company own itself, directly or through other companies.
Replace all relations
PUT /api/v1/companies/{companyId}/related-companiesMakes the company's relations match the body, in both directions. The body has the same items as Create relations.
- An item that matches an existing relation, with the same other company and
type, updates it. The relation keeps itsid. - An item with no match creates a relation.
- An existing relation that isn't in the body is deleted.
Sending [] deletes every relation of the company.
Permission: UPDATE_COMPANY
Response: 200 with all of the company's relations after the change.
Get one relation
GET /api/v1/companies/{companyId}/related-companies/{relationId}Permission: VIEW_COMPANY
Response: 200 with the relation object.
Change one relation
PATCH /api/v1/companies/{companyId}/related-companies/{relationId}
PUT /api/v1/companies/{companyId}/related-companies/{relationId}PATCH changes only the fields you send. PUT replaces all of them, and clears any field you leave out. The exception is the roles: a PUT without details keeps them.
The two companies and the direction can't change. These fields can:
| Field | Description |
|---|---|
ownership | Ownership percentage, from 0 to 100 |
voting | Voting percentage, from 0 to 100 |
notes | Free text |
details | Company roles and reasons. See Roles and reasons. |
reasons | Deprecated. Use details. |
delete on a details entry returns 400. To remove a role, send the roles to keep.
Permission: UPDATE_COMPANY
Response: 200 with the relation object.
Delete one relation
DELETE /api/v1/companies/{companyId}/related-companies/{relationId}Permission: UPDATE_COMPANY
Response: 204 with no body.
Relations in PUT /sync
PUT /sync also writes relations, in relatedCompanies on each company. It accepts the same details, with these differences:
- Without
cleanup, the entries are added to the roles and reasons the relation already has. An entry withdelete: trueremoves one. - With
cleanup,detailsreplaces every role and reason, asPUTdoes here. - A role listed twice keeps the last entry, instead of returning
400.
Errors
| Status | When |
|---|---|
400 | The body or a parameter is invalid. This includes a missing type, two IDs that name different companies, reasons and details that disagree, and a role listed twice. |
401 | The token is missing, malformed, expired or revoked. |
403 | The token doesn't have the permission. |
404 | companyId doesn't exist, a company or a role named in the body doesn't exist, or relationId isn't a relation of this company. |
409 | The relation already exists, or it would make a company own itself. |
These endpoints don't follow every API design guideline yet. For example, a list is returned as a bare array, and errors don't use the new error format.